From 883da34010202a0e237dc62af49ba8d288a9788a Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 18 Sep 2026 15:17:48 +0800 Subject: [PATCH 001/228] WIP (vetted) --- .../shared/src/main/scala/hkmc2/Config.scala | 26 +++------ .../main/scala/hkmc2/invalml/InvalML.scala | 10 ++-- .../main/scala/hkmc2/semantics/Importer.scala | 2 +- .../main/scala/hkmc2/semantics/Resolver.scala | 5 ++ .../main/scala/hkmc2/semantics/Split.scala | 2 +- .../hkmc2/semantics/ucs/TermSynthesizer.scala | 2 +- .../main/scala/hkmc2/utils/Publisher.scala | 16 ++++++ hkmc2/shared/src/test/mlscript/.mls | 2 + .../src/test/mlscript/backlog/ToTriage.mls | 56 ++++++++++--------- .../test/mlscript/basics/ObjectExtensions.mls | 4 +- .../codegen/AuxiliaryConstructors.mls | 20 +++++-- .../test/mlscript/codegen/ConfigDirective.mls | 8 +-- .../codegen/CurriedClassInheritance.mls | 4 +- .../src/test/mlscript/dead-param-elim/ups.mls | 2 +- .../src/test/mlscript/flows/BasicFlows.mls | 6 +- .../src/test/mlscript/flows/SelExpansion.mls | 48 ++++++++-------- .../mlscript/flows/SelExpansionImport.mls | 16 +++--- .../mlscript/ucs/patterns/NamePattern.mls | 2 +- .../ups/syntax/InterestingPatterns.mls | 6 +- .../test/mlscript/ups/syntax/PatternBody.mls | 26 ++++----- .../test/mlscript/ups/syntax/Precedence.mls | 12 ++-- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 22 ++++++-- 22 files changed, 172 insertions(+), 125 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala create mode 100644 hkmc2/shared/src/test/mlscript/.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index 22945d022a..a5e7440bb8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -97,6 +97,7 @@ object Config: case class Language( allowUnresolvedAccesses: Bool, useNewResolution: Bool, + strictResolution: Bool, // Whether the new resolution is done pedantically typeCheck: Opt[TypeChecking], )(val versionName: Str) @@ -105,6 +106,7 @@ object Config: val v0_2_x = Language( typeCheck = N, useNewResolution = false, + strictResolution = false, allowUnresolvedAccesses = true, )( versionName = "0.2.x", @@ -113,6 +115,7 @@ object Config: val v0_3_x = Language( typeCheck = N, useNewResolution = true, + strictResolution = false, allowUnresolvedAccesses = false, )( versionName = "0.3.x", @@ -390,20 +393,6 @@ object ConfigParser: source = Diagnostic.Source.Compilation)) N - private def withLanguage( - base: Config.Language, - allowUnresolvedAccesses: Bool, - useNewResolution: Bool, - typeCheck: Opt[Config.TypeChecking], - ): Config.Language = - Config.Language( - allowUnresolvedAccesses, - useNewResolution, - typeCheck, - )( - base.versionName, - ) - private def parsedLanguageModifier[A]( value: Tree, )( @@ -416,13 +405,16 @@ object ConfigParser: private def parseLanguageFieldModifier(tree: Tree)(using Raise): Opt[Config.Language => Config.Language] = tree match case NamedArg("allowUnresolvedAccesses", value) => parsedLanguageModifier(value)(parseBool): v => - language => withLanguage(language, v, language.useNewResolution, language.typeCheck) + lang => lang.copy(allowUnresolvedAccesses = v)(lang.versionName) case NamedArg("useNewResolution", value) => parsedLanguageModifier(value)(parseBool): v => - language => withLanguage(language, language.allowUnresolvedAccesses, v, language.typeCheck) + lang => lang.copy(useNewResolution = v)(lang.versionName) + case NamedArg("strictResolution", value) => + parsedLanguageModifier(value)(parseBool): v => + lang => lang.copy(strictResolution = v)(lang.versionName) case NamedArg("typeCheck", value) => parsedLanguageModifier(value)(tree => parseOpt(tree)(parseTypeChecking)): v => - language => withLanguage(language, language.allowUnresolvedAccesses, language.useNewResolution, v) + lang => lang.copy(typeCheck = v)(lang.versionName) case other => unsupported("Language", other) N diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala index 4f14b8087c..732ec23e1c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala @@ -26,7 +26,7 @@ final case class InvalCtx( env: HashMap[Uid[Symbol], GeneralType], outRegAcc: Type, symbolCache: HashMap[Str, TypeSymbol], -): +)(using Config): def +=(p: Symbol -> GeneralType): Unit = env += p._1.uid -> p._2 def get(sym: Symbol): Option[GeneralType] = env.get(sym.uid) orElse parent.dlof(_.get(sym))(None) def getCls(name: Str): TypeSymbol = symbolCache.getOrElseUpdate(name, @@ -62,14 +62,14 @@ object InvalCtx: ClassLikeType(ctx.getCls("Region"), Wildcard.out(sk) :: Nil) def refTy(ct: Type, sk: Type)(using ctx: InvalCtx): Type = ClassLikeType(ctx.getCls("Ref"), Wildcard(ct, ct) :: Wildcard.out(sk) :: Nil) - def init(raise: Raise)(using Elaborator.State, Elaborator.Ctx): InvalCtx = + def init(raise: Raise)(using Elaborator.State, Elaborator.Ctx)(using Config): InvalCtx = new InvalCtx(raise, summon, None, 1, HashMap.empty, Bot, HashMap.empty) val builtinOps = Elaborator.binaryOps ++ Elaborator.unaryOps ++ Elaborator.aliasOps.keySet end InvalCtx -class InvalTyper(using elState: Elaborator.State, tl: TL)(using Ctx): +class InvalTyper(using elState: Elaborator.State, tl: TL)(using Ctx, Config): import tl.{trace, log} private val infVarState = new InfVarUid.State() @@ -254,7 +254,7 @@ class InvalTyper(using elState: Elaborator.State, tl: TL)(using Ctx): constrain(lhsTy, FunType(rhsTy.reverse, resTy, Bot)) // TODO: right (resTy, lhsCtx | rhsCtx, lhsEff | rhsEff) case sel @ Term.SynthSel(Term.Ref(_: TopLevelSymbol), _) if sel.symbol.isDefined => - val (opTy, eff) = typeCheck(Ref(sel.symbol.get)(sel.nme, N)) // FIXME 666 + val (opTy, eff) = typeCheck(Ref(sel.symbol.get)(sel.nme, N)) (tryMkMono(opTy, sel), Bot, eff) case unq @ Term.Unquoted(body) => val (ty, eff) = typeCheck(body) @@ -612,7 +612,7 @@ class InvalTyper(using elState: Elaborator.State, tl: TL)(using Ctx): case Term.Annotated(Annot.Untyped, _) => (Bot, Bot) case sel @ Term.SynthSel(Ref(_: TopLevelSymbol), nme) if sel.symbol.isDefined => - typeCheck(Ref(sel.symbol.get)(sel.nme, N)) // FIXME 666 + typeCheck(Ref(sel.symbol.get)(sel.nme, N)) case Ref(sym) => ctx.get(sym) match case Some(ty) => (ty, Bot) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala index 41cc73f1e4..2045a29a41 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala @@ -14,7 +14,7 @@ import Elaborator.* import hkmc2.syntax.LetBind -class Importer: +class Importer extends NewResolver: self: Elaborator => import tl.* diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index c0e34d2d69..b1852bace5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -571,6 +571,11 @@ class Resolver(tl: TraceLogger) cls.foreach(traverse(_, expect = Class(N))) (t.callableDefn, ictx) + case Term.DynNew(cls, args) => + traverse(cls, expect = NonModule(N)) + args.foreach(traverse(_, expect = NonModule(N))) + resolveType(t, prefer = prefer) + (t.callableDefn, ictx) case Term.New(cls, args, rft) => // Term.New has only a type, but does not have a symbol. traverse(cls, expect = Class(S("The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation."))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index 06fd034360..72f1059a80 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -175,7 +175,7 @@ object Split: import Term.Ref /** Desugar a `SimpleSplit` into a `Split`. */ - def from(rootSplit: SS)(using tl: TL)(using Ctx, Raise, State): Split = + def from(rootSplit: SS)(using tl: TL)(using Ctx, Raise, State, Config): Split = val compiler = new SplitCompiler() val scrutCache = MutMap.empty[Ref, Scrut] def go(split: SS): Split = split match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala index c97e5ebb71..04afe3714b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala @@ -27,7 +27,7 @@ trait TermSynthesizer(using State): protected final def app(l: Term, r: Term, s: FlowSymbol): Term.App = (Term.App(l, r)(App(Dummy, Dummy), N, s): Term.App).resolve protected final def `new`(cls: Term, args: Ls[Term], label: Str): Term.New = - Term.New(cls, args, N)(N) + Term.New(cls, args, N)(FlowSymbol.neww(), N) protected final def rcd(fields: RcdField*): Term.Rcd = Term.Rcd(false, fields.toList) protected final def splitLet(sym: LocalVarSymbol, term: Term)(inner: Split): Split = diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala new file mode 100644 index 0000000000..dd1b5051cc --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala @@ -0,0 +1,16 @@ +package hkmc2 +package utils + +import scala.collection.mutable.{Buffer, Set as MutSet, LinkedHashSet} + +import hkmc2.utils.*, shorthands.* + + +trait Publisher[A]: + private[hkmc2] val shapeListeners: Buffer[A => Unit] = Buffer.empty + +trait Host[A] extends Publisher[A]: + private[hkmc2] val shapes: LinkedHashSet[A] = LinkedHashSet.empty + def showDbg(using DebugPrinter): Str + + diff --git a/hkmc2/shared/src/test/mlscript/.mls b/hkmc2/shared/src/test/mlscript/.mls new file mode 100644 index 0000000000..f0f0319735 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/.mls @@ -0,0 +1,2 @@ +:js +#lang(0.3.x) diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index fe15640596..db63c47e65 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -151,7 +151,9 @@ class D extends id(C) //│ ║ l.149: class D extends id(C) //│ ║ ^^ //│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[COMPILATION ERROR] Too many parameter lists for parent class +//│ ╔══[COMPILATION ERROR] Too many parameter lists for parent class +//│ ║ l.149: class D extends id(C) +//│ ╙── ^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let D1; //│ (class D extends Predef.id { @@ -178,14 +180,14 @@ let x = 0 :fixme set x += 1; () //│ ╔══[COMPILATION ERROR] Expected a right-hand side for this assignment -//│ ║ l.179: set x += 1; () +//│ ║ l.181: set x += 1; () //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e set (x += 1; ()) //│ ╔══[COMPILATION ERROR] Expected a right-hand side for this assignment -//│ ║ l.186: set (x += 1; ()) +//│ ║ l.188: set (x += 1; ()) //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -194,7 +196,7 @@ set (x += 1; ()) :fixme [x, set x += 1; x] //│ ╔══[COMPILATION ERROR] Expected a right-hand side for this assignment -//│ ║ l.195: [x, set x += 1; x] +//│ ║ l.197: [x, set x += 1; x] //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -205,13 +207,13 @@ import "../../mlscript-compile/Stack.mls" // The parser rejects this, but it should be allowed. open Stack { ::, Nil } //│ ╔══[COMPILATION ERROR] Illegal 'open' statement shape. -//│ ║ l.206: open Stack { ::, Nil } +//│ ║ l.208: open Stack { ::, Nil } //│ ╙── ^^^^^^^^^^^^^^^ // Instead, if we parenthesize the operator, it is rejected by the elaborator. open Stack { (::), Nil } //│ ╔══[COMPILATION ERROR] Illegal 'open' statement element. -//│ ║ l.212: open Stack { (::), Nil } +//│ ║ l.214: open Stack { (::), Nil } //│ ╙── ^^^^ open Stack { Nil, :: } @@ -237,46 +239,46 @@ id(Foo)(1) :e data class FooSpd(...args) //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. -//│ ║ l.238: data class FooSpd(...args) +//│ ║ l.240: data class FooSpd(...args) //│ ╙── ^^^^ FooSpd(1, 2, 3).args //│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 3 -//│ ║ l.243: FooSpd(1, 2, 3).args +//│ ║ l.245: FooSpd(1, 2, 3).args //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Access to required field 'args' yielded 'undefined' :todo if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╔══[COMPILATION ERROR] Unrecognized pattern (spread). -//│ ║ l.250: if FooSpd(1, 2, 3) is FooSpd(...args) then args +//│ ║ l.252: if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: args -//│ ║ l.250: if FooSpd(1, 2, 3) is FooSpd(...args) then args +//│ ║ l.252: if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 3 -//│ ║ l.250: if FooSpd(1, 2, 3) is FooSpd(...args) then args +//│ ║ l.252: if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] The constructor does not take any arguments but found one argument. -//│ ║ l.250: if FooSpd(1, 2, 3) is FooSpd(...args) then args +//│ ║ l.252: if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] //│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 3 -//│ ║ l.265: if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] +//│ ║ l.267: if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] The constructor does not take any arguments but found three arguments. -//│ ║ l.265: if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] +//│ ║ l.267: if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error if FooSpd(1, 2, 3) is FooSpd(arg) then arg //│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 3 -//│ ║ l.274: if FooSpd(1, 2, 3) is FooSpd(arg) then arg +//│ ║ l.276: if FooSpd(1, 2, 3) is FooSpd(arg) then arg //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] The constructor does not take any arguments but found one argument. -//│ ║ l.274: if FooSpd(1, 2, 3) is FooSpd(arg) then arg +//│ ║ l.276: if FooSpd(1, 2, 3) is FooSpd(arg) then arg //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -287,10 +289,10 @@ fun foo() = let bar(x: Str): Str = x + x bar("test") //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.287: let bar(x: Str): Str = x + x +//│ ║ l.289: let bar(x: Str): Str = x + x //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: bar -//│ ║ l.288: bar("test") +//│ ║ l.290: bar("test") //│ ╙── ^^^ // ——— ——— ——— @@ -304,7 +306,7 @@ module Foo(x) with print(x) //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.303: module Foo(x) with +//│ ║ l.305: module Foo(x) with //│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo5; @@ -322,7 +324,7 @@ module Foo(x) with //│ }); //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.303: module Foo(x) with +//│ ║ l.305: module Foo(x) with //│ ╙── ^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > 1 @@ -332,7 +334,7 @@ module Foo(x) with module Foo(val x) //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.332: module Foo(val x) +//│ ║ l.334: module Foo(val x) //│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo7; @@ -348,7 +350,7 @@ module Foo(val x) //│ }); //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.332: module Foo(val x) +//│ ║ l.334: module Foo(val x) //│ ╙── ^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -357,7 +359,7 @@ module Foo(val x) // TODO support syntax? data class Foo(mut x) //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found 'mut'-modified identifier -//│ ║ l.358: data class Foo(mut x) +//│ ║ l.360: data class Foo(mut x) //│ ╙── ^ // ——— ——— ——— @@ -377,16 +379,16 @@ class Foo :e class Id(name: UnboundName) //│ ╔══[COMPILATION ERROR] Name not found: UnboundName -//│ ║ l.378: class Id(name: UnboundName) +//│ ║ l.380: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.378: class Id(name: UnboundName) +//│ ║ l.380: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got ‹error› -//│ ║ l.378: class Id(name: UnboundName) +//│ ║ l.380: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.378: class Id(name: UnboundName) +//│ ║ l.380: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ // ——— ——— ——— diff --git a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls index 2008e7c484..ff0c9b5d64 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls @@ -48,6 +48,8 @@ object Bar extends! Foo!class //│ ║ l.46: object Bar extends! Foo!class //│ ║ ^ //│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[COMPILATION ERROR] Too many parameter lists for parent class +//│ ╔══[COMPILATION ERROR] Too many parameter lists for parent class +//│ ║ l.46: object Bar extends! Foo!class +//│ ╙── ^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls index 577e09f113..34c9c92216 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls @@ -116,7 +116,9 @@ class B extends A(1)(2)(3) :e class B extends A(0) -//│ ═══[COMPILATION ERROR] Extending a partially applied class is not supported +//│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported +//│ ║ l.118: class B extends A(0) +//│ ╙── ^^^^ new B //│ = B { x: 0 } @@ -125,17 +127,27 @@ class C :e class D extends C(0) -//│ ═══[COMPILATION ERROR] Too many parameter lists for parent class +//│ ╔══[COMPILATION ERROR] Too many parameter lists for parent class +//│ ║ l.129: class D extends C(0) +//│ ╙── ^^^^ :e new A(1)(2) with fun a = 0 -//│ ═══[COMPILATION ERROR] Extending a partially applied class is not supported +//│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported +//│ ║ l.135: new A(1)(2) with +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ║ l.136: fun a = 0 +//│ ╙── ^^^^^^^^^^^ //│ = $anon { x: 1 } :e new A(1)(2)(3)(4) with fun a = 0 //? = $anon { x: 1 } -//│ ═══[COMPILATION ERROR] Too many parameter lists for parent class +//│ ╔══[COMPILATION ERROR] Too many parameter lists for parent class +//│ ║ l.145: new A(1)(2)(3)(4) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.146: fun a = 0 +//│ ╙── ^^^^^^^^^^^ //│ = $anon { x: 1 } diff --git a/hkmc2/shared/src/test/mlscript/codegen/ConfigDirective.mls b/hkmc2/shared/src/test/mlscript/codegen/ConfigDirective.mls index 2d1537fc1b..6f2f3ad819 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ConfigDirective.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ConfigDirective.mls @@ -195,12 +195,12 @@ fun applyTwice(f, x) = f(x) + f(x) applyTwice(fst, Pair(1, 2)) //│ deforest > >>> non-affine syms >>> //│ deforest > cap_ub_(term:fst,0)_for_fst@35 -//│ deforest > f_for_applyTwice@24 -//│ deforest > f_for_applyTwice@55 +//│ deforest > f_for_applyTwice@23 +//│ deforest > f_for_applyTwice@54 //│ deforest > fst_for_fst@32 //│ deforest > tmp@1 -//│ deforest > x_for_applyTwice@23 -//│ deforest > x_for_applyTwice@54 +//│ deforest > x_for_applyTwice@24 +//│ deforest > x_for_applyTwice@55 //│ deforest > <<< non-affine syms <<< //│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls b/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls index 9eecf5e6ae..5a1f2f1ff0 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls @@ -6,7 +6,9 @@ class Foo(val x)(val y) :e :sjs class Bar(val a)(val d) extends Foo(a) -//│ ═══[COMPILATION ERROR] Extending a partially applied class is not supported +//│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported +//│ ║ l.8: class Bar(val a)(val d) extends Foo(a) +//│ ╙── ^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Bar1; //│ Bar1 = function Bar(a) { diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/ups.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/ups.mls index 8318bb5f70..95da5575bf 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/ups.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/ups.mls @@ -4,7 +4,7 @@ :ucs ups pattern Middle = ([x] => x) | ([x, ...(Middle as m), y] => m) -//│ elaborated pattern body: [x] => x⁰ ∨ [x, ...Middle as m, y] => m⁰ +//│ elaborated pattern body: [x‹x⁰›] => x¹ ∨ [x‹x²›, ...Middle as m‹m⁰›, y‹y⁰›] => m¹ //│ dead-param-elim > >>> dead-param-elim results >>> //│ dead-param-elim > <<< dead-param-elim results <<< diff --git a/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls b/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls index 5966f72e06..c1defe9ed9 100644 --- a/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls +++ b/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls @@ -50,7 +50,7 @@ fun test(x) = x.m //│ ║ l.48: fun test(x) = x.m //│ ╙── ^^^ //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ fun test⁰::test¹(x¹) ‹flow:test⁰› = x¹.m‹?› +//│ fun test⁰::test¹(x¹) ‹flow:test⁰› = x¹.mˀˀˀ //│ where //│ x¹ ~> {m: ⋅m⁰} //│ flow:test⁰ <~ ((x¹) -> ⋅m⁰) @@ -60,7 +60,7 @@ fun test(x) = x.m // :df fun test(x: Foo) = x.m //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ fun test²::test³(x²: Foo⁰{ ~> Foo⁰ }) ‹flow:test¹› = x².m‹?›{ ~> x².m⁰ } +//│ fun test²::test³(x²: Foo⁰{ ~> Foo⁰ }) ‹flow:test¹› = x².mˀˀˀ{ ~> x².m⁰ } //│ where //│ x² <~ type Foo //│ flow:test¹ <~ ((type Foo) -> flow:m⁰) @@ -69,7 +69,7 @@ fun test(x: Foo) = x.m let f = (x: Foo) => x.m //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— //│ let f⁰, -//│ f⁰ = (x³: Foo⁰{ ~> Foo⁰ }) => x³.m‹?›{ ~> x³.m⁰ } +//│ f⁰ = (x³: Foo⁰{ ~> Foo⁰ }) => x³.mˀˀˀ{ ~> x³.m⁰ } //│ where //│ f⁰ <~ ((type Foo) -> flow:m⁰) //│ x³ <~ type Foo diff --git a/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls index 6b4f7ec7c4..d50c681131 100644 --- a/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls +++ b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls @@ -15,7 +15,7 @@ module Foo with //│ val a⁰::a¹ ‹flow:a⁰› = 123 //│ }, //│ module Foo⁰::Foo² { -//│ fun foo⁰::foo¹(x⁰: Foo⁰{ ~> Foo⁰ }) ‹flow:foo⁰› = x⁰.a‹?›{ ~> x⁰.a⁰ } +//│ fun foo⁰::foo¹(x⁰: Foo⁰{ ~> Foo⁰ }) ‹flow:foo⁰› = x⁰.aˀˀˀ{ ~> x⁰.a⁰ } //│ } //│ where //│ x⁰ <~ type Foo @@ -50,16 +50,16 @@ f.a.b //│ ║ l.10: val a = 123 //│ ╙── ^^^ //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ f⁰.a⁰.b‹?› +//│ f⁰.a⁰.bˀˀˀ //│ where //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] Error: Access to required field 'b' yielded 'undefined' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :sjs f.foo //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ f⁰.foo‹?›{ ~> Foo⁰.foo⁰(f⁰)‹app⁰› } +//│ f⁰.Foo⁰.foo⁰{ ~> Foo⁰.foo⁰(f⁰)‹app⁰› } //│ where //│ f⁰ <~ Foo() //│ f⁰ ~> {a: ⋅a⁰} {a: ⋅a¹} {foo: ⋅foo⁰} @@ -78,7 +78,7 @@ fun id(x) = x id(f).foo //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ id⁰{ ~> id⁰ }(f⁰)‹app¹›.foo‹?›{ ~> Foo⁰.foo⁰(id⁰(f⁰)‹app¹›)‹app²› } +//│ id⁰{ ~> id⁰ }(f⁰)‹app¹›.Foo⁰.foo⁰{ ~> Foo⁰.foo⁰(id⁰(f⁰)‹app¹›)‹app²› } //│ where //│ f⁰ <~ Foo() //│ f⁰ ~> {a: ⋅a⁰} {a: ⋅a¹} {foo: ⋅foo⁰} @@ -100,7 +100,7 @@ let id(x) = x id(f).foo //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ id²(f⁰)‹app³›.foo‹?›{ ~> Foo⁰.foo⁰(id²(f⁰)‹app³›)‹app⁴› } +//│ id²(f⁰)‹app³›.Foo⁰.foo⁰{ ~> Foo⁰.foo⁰(id²(f⁰)‹app³›)‹app⁴› } //│ where //│ f⁰ <~ Foo() //│ f⁰ ~> {a: ⋅a⁰} {a: ⋅a¹} {foo: ⋅foo⁰} @@ -138,7 +138,7 @@ id(f).foo //│ ║ l.123: id(0) //│ ╙── ^ //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ id³{ ~> id³ }(f⁰)‹app⁶›.foo‹?›{ ~> Foo⁰.foo⁰(id³(f⁰)‹app⁶›)‹app⁷› } +//│ id³{ ~> id³ }(f⁰)‹app⁶›.Foo⁰.foo⁰{ ~> Foo⁰.foo⁰(id³(f⁰)‹app⁶›)‹app⁷› } //│ where //│ f⁰ <~ Foo() //│ f⁰ ~> {a: ⋅a⁰} {a: ⋅a¹} {foo: ⋅foo⁰} @@ -155,7 +155,7 @@ fun test(g) = g.foo //│ ║ l.153: fun test(g) = g.foo //│ ╙── ^^^^^ //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ fun test⁰::test¹(g⁰) ‹flow:test⁰› = g⁰.foo‹?› +//│ fun test⁰::test¹(g⁰) ‹flow:test⁰› = g⁰.fooˀˀˀ //│ where //│ g⁰ ~> {foo: ⋅foo⁴} //│ flow:test⁰ <~ ((g⁰) -> ⋅foo⁴) @@ -176,7 +176,7 @@ test(f) fun test(g) = g.foo test(f) //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ fun test²::test³(g¹) ‹flow:test¹› = g¹.foo‹?›{ ~> Foo⁰.foo⁰(g¹)‹app⁹› }, +//│ fun test²::test³(g¹) ‹flow:test¹› = g¹.Foo⁰.foo⁰{ ~> Foo⁰.foo⁰(g¹)‹app⁹› }, //│ test²{ ~> test² }(f⁰)‹app¹⁰› //│ where //│ f⁰ <~ Foo() @@ -202,7 +202,7 @@ module AA with //│ val x⁴::x⁵: Int⁰{ ~> Int⁰ } ‹flow:x⁰› = 1 //│ }, //│ module CC⁰::CC² { -//│ fun getX⁰::getX¹(self⁰: ⟨BB¹.⟩CC⁰) ‹flow:getX⁰› = self⁰.x‹?›{ ~> self⁰.x⁴ } +//│ fun getX⁰::getX¹(self⁰: ⟨BB¹.⟩CC⁰) ‹flow:getX⁰› = self⁰.xˀˀˀ{ ~> self⁰.x⁴ } //│ } //│ } //│ } @@ -215,7 +215,7 @@ module AA with :sjs new AA.BB.CC().x //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ new AA⁰.BB⁰.CC⁰().x‹?›{ ~> new AA⁰.BB⁰.CC⁰().x⁴ } +//│ new AA⁰.BB⁰.CC⁰().xˀˀˀ{ ~> new AA⁰.BB⁰.CC⁰().x⁴ } //│ where //│ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— @@ -226,7 +226,7 @@ new AA.BB.CC().x :sjs new AA.BB.CC().getX //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ new AA⁰.BB⁰.CC⁰().getX‹?›{ ~> AA⁰.BB⁰.CC⁰.getX⁰(new AA⁰.BB⁰.CC⁰())‹app¹¹› } +//│ new AA⁰.BB⁰.CC⁰().AA⁰.BB⁰.CC⁰.getX⁰{ ~> AA⁰.BB⁰.CC⁰.getX⁰(new AA⁰.BB⁰.CC⁰())‹app¹¹› } //│ where //│ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— @@ -266,12 +266,12 @@ foo.test //│ ║ l.264: foo.test //│ ╙── ^^^^^^^^ //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ foo².test‹?› +//│ foo².testˀˀˀ //│ where //│ foo² <~ A //│ foo² ~> {test: ⋅test⁰} //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] Error: Access to required field 'test' yielded 'undefined' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. let foo = @@ -288,7 +288,7 @@ let foo = //│ module A³::A⁵ { //│ fun test⁶::test⁷(a³) ‹flow:test³› = new A³ //│ }, -//│ new A³.test‹?›{ ~> A³.test⁶(new A³)‹app¹²› } +//│ new A³.A³.test⁶{ ~> A³.test⁶(new A³)‹app¹²› } //│ } //│ where //│ foo³ <~ A @@ -305,12 +305,12 @@ foo.test //│ ║ l.303: foo.test //│ ╙── ^^^^^^^^ //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ foo³.test‹?› +//│ foo³.testˀˀˀ //│ where //│ foo³ <~ A //│ foo³ ~> {test: ⋅test²} //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] Error: Access to required field 'test' yielded 'undefined' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. class CC(val x: Int) @@ -322,7 +322,7 @@ module CC with //│ val x⁷::x⁸ ‹flow:x¹› = x⁶ //│ }, //│ module CC⁴::CC⁶ { -//│ fun getX²::getX³(self¹: CC⁴{ ~> CC⁴ }) ‹flow:getX¹› = self¹.x‹?›{ ~> self¹.x⁷ } +//│ fun getX²::getX³(self¹: CC⁴{ ~> CC⁴ }) ‹flow:getX¹› = self¹.xˀˀˀ{ ~> self¹.x⁷ } //│ } //│ where //│ x⁶ <~ type Int @@ -335,7 +335,7 @@ module CC with :fixme // TODO: handle lifted class defs: the JS is missing the `.class` access to get the static member `CC2.class.getX` CC(123).getX //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ CC⁴(123)‹app¹³›.getX‹?›{ ~> CC⁴.getX²(CC⁴(123)‹app¹³›)‹app¹⁴› } +//│ CC⁴(123)‹app¹³›.CC⁴.getX²{ ~> CC⁴.getX²(CC⁴(123)‹app¹³›)‹app¹⁴› } //│ where //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -351,7 +351,7 @@ module CC with //│ //│ }, //│ module CC⁷::CC⁹ { -//│ fun oops⁰::oops¹(x⁹: CC⁷{ ~> CC⁷ }) ‹flow:oops⁰› = x⁹.oops‹?›{ ~> CC⁷.oops⁰(x⁹)‹app¹⁵› } +//│ fun oops⁰::oops¹(x⁹: CC⁷{ ~> CC⁷ }) ‹flow:oops⁰› = x⁹.CC⁷.oops⁰{ ~> CC⁷.oops⁰(x⁹)‹app¹⁵› } //│ } //│ where //│ x⁹ <~ type CC @@ -363,7 +363,7 @@ module CC with :re new CC().oops //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ new CC⁷().oops‹?›{ ~> CC⁷.oops⁰(new CC⁷())‹app¹⁶› } +//│ new CC⁷().CC⁷.oops⁰{ ~> CC⁷.oops⁰(new CC⁷())‹app¹⁶› } //│ where //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -379,7 +379,7 @@ module CC with //│ val okay⁰::okay¹ ‹flow:okay⁰› = 123 //│ }, //│ module CC¹⁰::CC¹² { -//│ fun okay²::okay³(x¹⁰: CC¹⁰{ ~> CC¹⁰ }) ‹flow:okay¹› = x¹⁰.okay‹?›{ ~> x¹⁰.okay⁰ } +//│ fun okay²::okay³(x¹⁰: CC¹⁰{ ~> CC¹⁰ }) ‹flow:okay¹› = x¹⁰.okayˀˀˀ{ ~> x¹⁰.okay⁰ } //│ } //│ where //│ x¹⁰ <~ type CC @@ -389,7 +389,7 @@ module CC with new CC().okay //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ new CC¹⁰().okay‹?›{ ~> new CC¹⁰().okay⁰ } +//│ new CC¹⁰().okayˀˀˀ{ ~> new CC¹⁰().okay⁰ } //│ where //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -430,7 +430,7 @@ let e = new Example.Test(123) :fixme // Same as test case above (missing `.class` access) e.next //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ e⁰.next‹?›{ ~> Example⁰.Test¹.next⁰(e⁰)‹app¹⁹› } +//│ e⁰.Example⁰.Test¹.next⁰{ ~> Example⁰.Test¹.next⁰(e⁰)‹app¹⁹› } //│ where //│ e⁰ <~ Test(123) //│ e⁰ ~> {next: ⋅next⁰} diff --git a/hkmc2/shared/src/test/mlscript/flows/SelExpansionImport.mls b/hkmc2/shared/src/test/mlscript/flows/SelExpansionImport.mls index 473d127f7a..0c425ef222 100644 --- a/hkmc2/shared/src/test/mlscript/flows/SelExpansionImport.mls +++ b/hkmc2/shared/src/test/mlscript/flows/SelExpansionImport.mls @@ -39,12 +39,12 @@ e.next //│ ║ l.37: e.next //│ ╙── ^^^^^^ //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ e⁰.next‹?› +//│ e⁰.nextˀˀˀ //│ where //│ e⁰ <~ Test(123) //│ e⁰ ~> {field: ⋅field⁰} {next: ⋅next⁰} //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] Error: Access to required field 'next' yielded 'undefined' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. open Example @@ -71,12 +71,12 @@ e.next //│ ║ l.69: e.next //│ ╙── ^^^^^^ //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ e¹.next‹?› +//│ e¹.nextˀˀˀ //│ where //│ e¹ <~ Test(123) //│ e¹ ~> {next: ⋅next¹} //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] Error: Access to required field 'next' yielded 'undefined' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -132,11 +132,11 @@ Example.t.field //│ ║ ^^^^^^^^^^^^^^^ //│ ╙── Type `type ⊤` does not contain member 'field' //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ Example⁰{ ~> Example⁰ }.t⁰{ ~> Example⁰.t⁰ }.field‹?› +//│ Example⁰{ ~> Example⁰ }.t⁰{ ~> Example⁰.t⁰ }.fieldˀˀˀ //│ where //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = 123 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme // Same Example.t.next @@ -148,10 +148,10 @@ Example.t.next //│ ║ ^^^^^^^^^^^^^^ //│ ╙── Type `type ⊤` does not contain member 'next' //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ Example⁰{ ~> Example⁰ }.t⁰{ ~> Example⁰.t⁰ }.next‹?› +//│ Example⁰{ ~> Example⁰ }.t⁰{ ~> Example⁰.t⁰ }.nextˀˀˀ //│ where //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] Error: Access to required field 'next' yielded 'undefined' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls index 6f34bf970e..1004891777 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls @@ -7,7 +7,7 @@ if id(0) is t then t //│ Split with nested patterns: //│ > if //│ > let tmp:scrut = member:id⁰(0) -//│ > tmp:scrut is t then t⁰ +//│ > tmp:scrut is t‹t⁰› then t⁰ //│ Expanded split with flattened patterns: //│ > ‹if|while› //│ > let tmp:scrut = member:id⁰(0) diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls index de83df9939..c9381f3ba2 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls @@ -10,7 +10,7 @@ pattern DropZ = ((:x, :y, :z)) => (x: x, y: y) :ucs ups pattern SumList = (Nil => 0) | ((Int as hd) :: (SumList as tl)) => hd + tl -//│ elaborated pattern body: Nil => 0 ∨ Cons(Int as hd, SumList as tl) => builtin:+⁰(hd⁰, tl⁰) +//│ elaborated pattern body: Nil => 0 ∨ Cons(Int as hd‹hd⁰›, SumList as tl‹tl⁰›) => builtin:+⁰(hd¹, tl¹) // Flatten the nested tuples. Note that it makes use of biased union. pattern Flatten = ([Flatten as hd, ...Flatten as tl] => Array.concat(hd, tl)) | _ @@ -28,12 +28,12 @@ pattern CountTree(pattern P) = // Match a 2x2 matrix and return its transpose. :ucs ups pattern Transpose = (null => null) | (([[a, b], [c, d]]) => [[a, c], [b, d]]) -//│ elaborated pattern body: null => null ∨ [[a, b], [c, d]] => [[a⁰, c⁰], [b⁰, d⁰]] +//│ elaborated pattern body: null => null ∨ [[a‹a⁰›, b‹b⁰›], [c‹c⁰›, d‹d⁰›]] => [[a¹, c¹], [b¹, d¹]] // Return the middle element of a list. It won't match if the length is even. :ucs ups pattern Middle = ([x] => x) | ([x, ...(Middle as m), y] => m) -//│ elaborated pattern body: [x] => x⁰ ∨ [x, ...Middle as m, y] => m⁰ +//│ elaborated pattern body: [x‹x⁰›] => x¹ ∨ [x‹x²›, ...Middle as m‹m⁰›, y‹y⁰›] => m¹ // Return the leaves of a tree. pattern Leaves = (null => Nil) | ([x] => x :: Nil) | ([Leaves as left, Leaves as right] => left ::: right) diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls index ce2c404682..c411e53c2b 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls @@ -19,31 +19,31 @@ class Pair[A, B](val fst: A, val snd: B) :ucs ups pattern PlainList(pattern T) = (null => Nil) | (Pair(T as hd, PlainList(T) as tl) => hd :: tl) -//│ elaborated pattern body: null => (member:Stack⁰.)Nil‹member:Nil› ∨ Pair(T as hd, PlainList(T) as tl) => (member:Stack⁰.)Cons‹member:Cons›(hd⁰, tl⁰) +//│ elaborated pattern body: null => (member:Stack⁰.)Nil‹member:Nil› ∨ Pair(T as hd‹hd⁰›, PlainList(T) as tl‹tl⁰›) => (member:Stack⁰.)Cons‹member:Cons›(hd¹, tl¹) :ucs ups // TODO: Decide on the relative operator precedence between `as` and `=>`. // This is elaborated into `T as (x => x)`, which might not be what we want. pattern Identity(pattern T) = T as x => x -//│ elaborated pattern body: T as (x => x⁰) +//│ elaborated pattern body: T >> (x‹x⁰› => x¹) :ucs ups pattern Identity(pattern T) = (T as x) => x -//│ elaborated pattern body: (T as x) => x¹ +//│ elaborated pattern body: (T as x‹x²›) => x³ pattern Identity = x => x :ucs ups pattern Sick = (0 as a as b as c as d as e as f) => a + b + c + d + e + f -//│ elaborated pattern body: ((((((0 as a) as b) as c) as d) as e) as f) => builtin:+⁰(builtin:+⁰(builtin:+⁰(builtin:+⁰(builtin:+⁰(a⁰, b⁰), c⁰), d⁰), e⁰), f⁰) +//│ elaborated pattern body: ((((((0 as a‹a⁰›) as b‹b⁰›) as c‹c⁰›) as d‹d⁰›) as e‹e⁰›) as f‹f⁰›) => builtin:+⁰(builtin:+⁰(builtin:+⁰(builtin:+⁰(builtin:+⁰(a¹, b¹), c¹), d¹), e¹), f¹) :ucs ups pattern PairLike = [fst, snd] => Pair(fst, snd) -//│ elaborated pattern body: [fst, snd] => member:Pair⁰(fst⁰, snd⁰) +//│ elaborated pattern body: [fst‹fst⁰›, snd‹snd⁰›] => member:Pair⁰(fst¹, snd¹) :ucs ups pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] -//│ elaborated pattern body: Pair(fst, snd) ∨ [fst, snd] +//│ elaborated pattern body: Pair(fst‹fst²›, snd‹snd²›) ∨ [fst‹fst²›, snd‹snd²›] :w pattern UselessParameter = x @@ -53,11 +53,11 @@ pattern UselessParameter = x :ucs ups pattern PlainList(pattern T) = (null => Nil) | ([T as hd, PlainList(T) as tl] => hd :: tl) -//│ elaborated pattern body: null => (member:Stack⁰.)Nil‹member:Nil› ∨ [T as hd, PlainList(T) as tl] => (member:Stack⁰.)Cons‹member:Cons›(hd¹, tl¹) +//│ elaborated pattern body: null => (member:Stack⁰.)Nil‹member:Nil› ∨ [T as hd‹hd²›, PlainList(T) as tl‹tl²›] => (member:Stack⁰.)Cons‹member:Cons›(hd³, tl³) :ucs ups pattern Consistent = ((Int as x) | (Str as x)) => x.toString() -//│ elaborated pattern body: (Int as x ∨ Str as x) => x².toString() +//│ elaborated pattern body: (Int as x‹x⁴› ∨ Str as x‹x⁴›) => x⁵.toString() :e pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() @@ -113,7 +113,7 @@ pattern BadNegated = (~(Int as x)) => x //│ ╔══[COMPILATION ERROR] Name not found: x //│ ║ l.106: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬(Int as x) => +//│ elaborated pattern body: ¬(Int as x‹›) => :ucs ups :e @@ -127,7 +127,7 @@ pattern BadNegated = (~Pair(Int, x)) => x //│ ╔══[COMPILATION ERROR] Name not found: x //│ ║ l.120: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬Pair(Int, x) => +//│ elaborated pattern body: ¬Pair(Int, x‹?›) => :ucs ups pattern DoubleNegated = ~(~Int) @@ -147,11 +147,11 @@ pattern Origin = (x: 0, y: 0, z: 0) :ucs ups pattern PointLike = ((x: x, y: y)) => x + y -//│ elaborated pattern body: {x: x, y: y} => builtin:+⁰(x³, y⁰) +//│ elaborated pattern body: {x: x‹x⁶›, y: y‹y⁰›} => builtin:+⁰(x⁷, y¹) :ucs ups pattern PointLike = ((:x, :y, :z)) => x + y -//│ elaborated pattern body: {x: x, y: y, z: z} => builtin:+⁰(x⁴, y¹) +//│ elaborated pattern body: {x: x‹x⁸›, y: y‹y²›, z: z‹z⁰›} => builtin:+⁰(x⁹, y³) :ucs ups pattern NestedTuple = [[[]]] @@ -166,7 +166,7 @@ class SecretPair(a: Int, val b: Int) :ucs ups pattern KeepSecret(b) = SecretPair(_, b) -//│ elaborated pattern body: SecretPair(_, b) +//│ elaborated pattern body: SecretPair(_, b‹b²›) :e pattern RebuildSecret = SecretPair(_, b => b + 1) diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls index f5e5b923e1..fd3e8e8143 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls @@ -26,19 +26,19 @@ pattern Test = (1 => 2) | 4 => 8 // The precedence of `as` is lower than `=>`. pattern Test = 0 as x => 2 -//│ elaborated pattern body: 0 as (x => 2) +//│ elaborated pattern body: 0 >> (x‹x⁰› => 2) pattern Test = (0 as x) => 2 -//│ elaborated pattern body: (0 as x) => 2 +//│ elaborated pattern body: (0 as x‹x¹›) => 2 pattern Test(x) = 0 as x | 1 as x -//│ elaborated pattern body: 0 as x ∨ 1 as x +//│ elaborated pattern body: 0 as x‹x²› ∨ 1 as x‹x²› pattern Test(x) = 0 as x | (1 as x) -//│ elaborated pattern body: 0 as x ∨ 1 as x +//│ elaborated pattern body: 0 as x‹x³› ∨ 1 as x‹x³› pattern Test(x) = 0 as x | (1 as x) -//│ elaborated pattern body: 0 as x ∨ 1 as x +//│ elaborated pattern body: 0 as x‹x⁴› ∨ 1 as x‹x⁴› pattern Test(x) = (0 as x) | (1 as x) -//│ elaborated pattern body: 0 as x ∨ 1 as x +//│ elaborated pattern body: 0 as x‹x⁵› ∨ 1 as x‹x⁵› diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 0953d05f3e..96fcec3857 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -47,6 +47,7 @@ abstract class MLsDiffMaker extends DiffMaker: val showResolve = NullaryCommand("r") val showResolvedTree = NullaryCommand("rt") val showFlows = FlagCommand(false, "sf") + val showResl = FlagCommand(false, "sr") val showLoweredTree = NullaryCommand("lot") val ppLoweredTreeOld = NullaryCommand("slot", () => output("Option ':slot' is deprecated, use ':sir' instead.")) val showIR = NullaryCommand("sir") @@ -242,7 +243,7 @@ abstract class MLsDiffMaker extends DiffMaker: given Elaborator.State = new Elaborator.State: override def dbg: Bool = dbgParsing.isSet - || dbgElab.isSet + // || dbgElab.isSet || dbgResolving.isSet || debug.isSet @@ -476,8 +477,20 @@ abstract class MLsDiffMaker extends DiffMaker: outputSeparator(s"Elaborated tree") output(e.showAsTree) + if showResl.isSet then + import semantics.ShowCfg + given ShowCfg = ShowCfg( + showErasedTypes = showIRErasedTypes.isSet, + showExpansionMappings = true, + showFlowSymbols = true, + debug = debug.isSet, + ) + outputSeparator(s"Resolved") + output: + import document.* + doc" #{ ${e.showTopLevel(using flowScp)} #} ".mkString(output.ColWidth) + processTerm(e, inImport = false) - def processTerm(trm: semantics.Term.Blk, inImport: Bool)(using Config, Raise): Unit = @@ -486,8 +499,9 @@ abstract class MLsDiffMaker extends DiffMaker: if file.toString =/= runtimeSourceFile.toString && file.toString =/= preludeFile.toString then summon[Elaborator.State].initRuntimeSymbolsFromFile(runtimeSourceFile, prelude)( using summon[TL], summon[Raise], cctx) - val resolver = Resolver(rtl) - curICtx = resolver.traverseBlock(trm)(using curICtx) + if !config.language.useNewResolution then + val resolver = Resolver(rtl) + curICtx = resolver.traverseBlock(trm)(using curICtx) if showResolve.isSet then output(s"Resolved: ${trm.showDbg}") From 23eb13d5605eb1fa17cd7f5d64e2f5a5f405cf06 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 18 Sep 2026 16:23:55 +0800 Subject: [PATCH 002/228] W --- .../main/scala/hkmc2/codegen/Lowering.scala | 183 +++++- .../scala/hkmc2/codegen/js/JSBuilder.scala | 7 + .../scala/hkmc2/semantics/Elaborator.scala | 312 +++++++--- .../scala/hkmc2/semantics/NewResolver.scala | 581 ++++++++++++++++++ .../main/scala/hkmc2/semantics/Pattern.scala | 53 +- .../scala/hkmc2/semantics/PatternShape.scala | 45 ++ .../main/scala/hkmc2/semantics/Shape.scala | 356 +++++++++++ .../scala/hkmc2/semantics/SimpleSplit.scala | 24 +- .../main/scala/hkmc2/semantics/Symbol.scala | 44 +- .../src/main/scala/hkmc2/semantics/Term.scala | 177 +++++- .../main/scala/hkmc2/semantics/TermImpl.scala | 39 ++ .../hkmc2/semantics/flow/FlowAnalysis.scala | 25 + .../hkmc2/semantics/ups/SplitCompiler.scala | 15 +- hkmc2/shared/src/test/mlscript/HkScratch.mls | 177 ++++++ hkmc2/shared/src/test/mlscript/HkScratch2.mls | 58 ++ hkmc2/shared/src/test/mlscript/newres/.mls | 2 + .../src/test/mlscript/newres/BadApps.mls | 30 + .../src/test/mlscript/newres/BareClasses.mls | 159 +++++ .../src/test/mlscript/newres/Basics.mls | 156 +++++ .../src/test/mlscript/newres/Captures.mls | 118 ++++ .../src/test/mlscript/newres/CtxSens.mls | 106 ++++ .../test/mlscript/newres/CurriedFunctions.mls | 57 ++ .../src/test/mlscript/newres/Functions.mls | 226 +++++++ .../src/test/mlscript/newres/Inheritance.mls | 151 +++++ .../mlscript/newres/MultiParamClasses.mls | 83 +++ .../test/mlscript/newres/NestedClasses.mls | 46 ++ .../test/mlscript/newres/NestedModules.mls | 118 ++++ .../src/test/mlscript/newres/ParamClasses.mls | 339 ++++++++++ .../src/test/mlscript/newres/PatMat.mls | 183 ++++++ .../src/test/mlscript/newres/ReslScratch.mls | 183 ++++++ .../src/test/mlscript/newres/ValCtxSens.mls | 50 ++ .../src/test/mlscript/newres/WIPTest.mls | 153 +++++ .../src/test/mlscript/newres/loose/.mls | 2 + .../src/test/mlscript/newres/loose/Basics.mls | 39 ++ 34 files changed, 4171 insertions(+), 126 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala create mode 100644 hkmc2/shared/src/test/mlscript/HkScratch2.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/BadApps.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/BareClasses.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/Basics.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/Captures.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/CtxSens.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/CurriedFunctions.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/Functions.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/Inheritance.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/MultiParamClasses.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/NestedClasses.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/NestedModules.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/PatMat.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/WIPTest.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/loose/.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/loose/Basics.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index ef600ef2e7..52b5e2cf9f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -21,6 +21,7 @@ import semantics.Elaborator.{State, Ctx, ctx} import syntax.{Literal, Tree, SpreadKind} import hkmc2.syntax.{Fun, Keyword, LetBind, MutVal} +import sem.flow.SelectionTarget abstract class TailOp(val transfersControl: Bool) extends (Result => Block) @@ -86,6 +87,9 @@ import Lowering.* class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): + val newResolution: Bool = config.language.useNewResolution + val strictResolution: Bool = config.language.strictResolution + extension (t: Term) def instantiated = t match case r: Resolvable => @@ -184,13 +188,13 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): new TailOp(transfersControl = true): override def apply(k: Result): Block = Ret(castTo(k, returnType, N)) - def parentConstructor(parentClsPath: Path, cls: Term, args: Ls[Term])(using LoweringCtx) = + def parentConstructor(parentClsPath: Path, cls: Term, args: Ls[Term], loc: Opt[Loc])(using LoweringCtx) = lowerSuperCtorCall( parentClsPath, State.builtinOpsMap("super").asSimpleRef, isMlsFun = true, args, - N, // TODO: location? + loc, )(c => Assign(NoSymbol, c, End())) // * Used to work around Scala's @tailrec annotation for those few calls that are not in tail position. @@ -404,8 +408,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): assert(k isnt syntax.Mod) // modules can't extend things and can't have super calls val cfgOverride = defn.extraAnnotations.collectFirst: case Annot.Config(modify) => modify(config) - subTerm(ext.cls): clsp => - val pctor = inScopedBlock(parentConstructor(clsp, ext.cls, ext.args)) + classOf(ext.cls, ext): clsp => + val pctor = inScopedBlock(parentConstructor(clsp, ext.cls, ext.args, ext.toLoc)) Define( ClsLikeDefn( defn.owner, defn.sym, defn.bsym, defn.ctorSym, defn.kind, defn.paramsOpt, defn.auxParams, S(clsp), @@ -418,6 +422,39 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): blockImpl(imps ::: funs ::: rest, res) + def classOf(trm: Term, nw: Resolvable)(k: Path => Block)(using LoweringCtx): Block = + if newResolution then + trm.withoutCaptures match + case resl: NewResolvable => + resl.resolvedTargets.distinct match + case cls :: Nil => + cls.defn match + case S(clsDef: ClassLikeDef) => + resl match + case MemberRef(bms: BlockMemberSymbol) => + k(Value.MemberRef(bms, clsDef.sym)) + case MemberRef(tr: TermSymbol) => + ??? + case MemberRef(_) => die // FIXME: should make this unreachable + case sel: NewSel => + subTerm(sel.prefix): pre => + k(Select(pre, memberIdent(sel.id, S(clsDef.bsym)))(S(clsDef.sym))(false)) + case _ => + softAssert(resl.isErroneous, s"Unexpected `new` target: ${cls.showDbg}") + compError + case Nil => + if !resl.isErroneous then raise: + ErrorReport( + msg"Cannot resolve the class instantiated here" -> trm.toLoc :: Nil, + source = Diagnostic.Source.Compilation) + compError + case _ => + ??? + case _ => + softAssert(nw.isErroneous, "Unexpected class term shape") + compError + else subTerm(trm)(k) + def getClassParamLists(cls: Path): Ls[ParamList] = cls.targetSymbol match case S(clsSym: ClassSymbol) => @@ -611,7 +648,39 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case td: TermDefinition => (td.k is syntax.Fun) && td.params.isEmpty case _ => false - def ref(ref: st.Ref, annots: List[Annot], disamb: Opt[DefinitionSymbol[?]], inStmtPos: Bool)(k: Result => Block)(using LoweringCtx): Block = + def selSymbol(sel: AnySelTerm): Opt[DefinitionSymbol[?]] = + sel.validResolvedTargets match + // sel.resolvedTargets match + case Nil => + if newResolution && !sel.isErroneous then raise: + ErrorReport( + msg"This selection of member '${sel.nme.name}' has no resolved target" -> sel.toLoc :: + Nil, S(sel), source = Diagnostic.Source.Compilation) + N + case SelectionTarget.ObjectMember(sym: DefinitionSymbol[?]) :: Nil => + S(sym) + case SelectionTarget.ObjectMember(sym: BlockMemberSymbol) :: Nil => + // TODO: instead, make SelectionTarget a listener that resolves to a more precise target + sym.asTrm orElse sym.asModOrObj match + case s @ S(mod) => s + case N => + if newResolution && !sel.isErroneous then raise: + ErrorReport( + msg"Selection target ${sym.describe} '${sym.nme}' cannot be used as a term" -> sel.toLoc :: + Nil, S(sel), source = Diagnostic.Source.Compilation) + N + case ts => + if newResolution && strictResolution && !sel.isErroneous then raise: + // println(ts) + // ??? + ErrorReport( + msg"Selection of member '${sel.nme.name}' is ambiguous, as it has multiple resolved targets" -> sel.toLoc :: + ts.map: t => + msg"target: ${t.describe}" -> t.loc + , S(sel), source = Diagnostic.Source.Compilation) + N + + def ref(ref: AnyRef_, annots: List[Annot], disamb: Opt[DefinitionSymbol[?]], inStmtPos: Bool)(k: Result => Block)(using LoweringCtx): Block = def warnStmt = if inStmtPos then warnPureExprInStmtPos(ref.toLoc, S(ref)) val sym = ref.sym @@ -823,6 +892,38 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case st.CtxTup(fs) => // * This case is currently triggered for code such as `f(using 42)` args(fs, Nil)(args => k(Tuple(mut = false, args))) + case t @ st.SimpleRef(sym) => + ref(t, annots, N, inStmtPos = inStmtPos)(k) + case t @ st.SelfRef(sym) => + ref(t, annots, N, inStmtPos = inStmtPos)(k) + case t @ st.MemberRef(bms) => + t.resolvedTargets.distinct match + case Nil => + // fail: + // ErrorReport( + // msg"Member reference '${sym.nme}' has no resolved target" -> t.toLoc :: Nil, S(t), + // source = Diagnostic.Source.Compilation) + bms.asTrm orElse bms.asModOrObj orElse bms.asCls match + case s @ S(_) => + ref(t, annots, s, inStmtPos = inStmtPos)(k) + case N => + if t.isErroneous then raise: + ErrorReport( + msg"Member reference '${bms.nme}' cannot be used as a term" -> t.toLoc :: Nil, S(t), + source = Diagnostic.Source.Compilation) + compError + case trgt :: Nil => + ref(t, annots, S(trgt), inStmtPos = inStmtPos)(k) + case ts => + if strictResolution && !t.isErroneous then raise: + ErrorReport( + msg"Member reference '${bms.nme}' is ambiguous, as it has multiple resolved targets" -> t.toLoc :: + ts.map: t => + msg"target: ${t.describeKind}" -> t.toLoc + , S(t), source = Diagnostic.Source.Compilation) + compError + case Capture(base, thru) => + term(base, inStmtPos = inStmtPos)(k) case t @ st.Ref(sym) => ref(t, annots, N, inStmtPos = inStmtPos)(k) case st.Resolved(t @ st.Ref(bsym), sym) => @@ -973,7 +1074,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): // * are preserved in the call and not moved to a temporary variable. case sel @ Sel(prefix, nme) => subTerm(prefix): p => - conclude(Select(p, nme)(N)(false).withLocOf(sel)) + conclude(Select(p, nme)(selSymbol(sel))(false).withLocOf(sel)) case Resolved(sel @ Sel(prefix, nme), sym) => subTerm(prefix): p => conclude(Select(p, definitionIdent(nme, sym))(S(sym))(false).withLocOf(sel)) @@ -1081,12 +1182,58 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case whltrm: st.SynthWhile => ucs.Normalization(this)(whltrm)(k) + case sel @ NewSel(prefix, id) => + if sel.isErroneous then compError else + // /* + // TODO dedup with MemberRef + sel.resolvedMembers.distinct match + case Nil => + fail: + // ErrorReport( + // msg"Member reference '${sym.nme}' has no resolved target" -> t.toLoc :: Nil, S(t), + // source = Diagnostic.Source.Compilation) + ErrorReport( + msg"This selection of member '${sel.id.name}' has no resolved target" -> sel.toLoc :: + Nil, S(sel), source = Diagnostic.Source.Compilation) + case bms :: Nil => + sel.resolvedTargets.distinct match + case Nil => + bms.asTrm orElse bms.asModOrObj orElse bms.asCls match + case s @ S(_) => + setupSelection(prefix, id, s)(k) + case N => + if sel.isErroneous then raise: + ErrorReport( + msg"Member reference '${bms.nme}' cannot be used as a term" -> sel.toLoc :: Nil, S(sel), + source = Diagnostic.Source.Compilation) + compError + case trgt :: Nil => setupSelection(prefix, id, S(trgt))(k) + case ts => ??? + case ts => + // if t.isErroneous then + if strictResolution then fail: + ErrorReport( + msg"This selection of member '${sel.id.name}' is ambiguous, as it has multiple resolved targets" -> t.toLoc :: + ts.map: t => + msg"target: ${t.describe} '${t.nme}'${ + t.asTrm.fold("")(_.owner.fold("")(o => s" in ${o.asBlkMember.get.describe} '${o.nme}'")) // TOOD other kinds of owned symbols + }" -> t.toLoc + , S(ts.map(s => s.showDbg + " " + s.tsym.map(_.showDbg))), source = Diagnostic.Source.Compilation) + // compError + else setupSelection(prefix, id, N)(k) + // ??? + // */ + + case sel @ Sel(prefix, nme) => - setupSelection(prefix, nme, N)(k) + if sel.isErroneous then compError else + setupSelection(prefix, nme, selSymbol(sel))(k) case Resolved(sel @ Sel(prefix, nme), sym) => setupSelection(prefix, nme, S(sym))(k) case sel @ SynthSel(prefix, nme) => + // System.out.println(sel) + // System.out.println(sel.sym) // * Not using `setupSelection` as these selections are not meant to be sanity-checked // * Unlike source `Sel`s, compiler-synthesized selections may carry a known // * member symbol directly in `sel.sym` without being wrapped in `Resolved`. @@ -1095,8 +1242,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): // * definition when one exists. Do not use this fallback for ordinary `Sel` // * lowering unless that convention is changed at the source. subTerm(prefix): p => - k(Select(p, memberIdent(nme, sel.sym))(sel.sym.collect: - case s: DefinitionSymbol[?] => s + k(Select(p, memberIdent(nme, sel.sym))(sel.sym.flatMap: + case s: DefinitionSymbol[?] => S(s) + case bms: BlockMemberSymbol => bms.asTrm // TODO: clean up logic + case err: ErrorSymbol => N )(false)) case Resolved(sel @ SynthSel(prefix, nme), sym) => // * Not using `setupSelection` as these selections are not meant to be sanity-checked @@ -1110,23 +1259,23 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case nw @ (_: New | _: DynNew | Mut(_: New | _: DynNew)) => - val (mut, cls, as, rft) = nw match - case New(c, a, r) => (false, c, a, r) - case Mut(New(c, a, r)) => (true, c, a, r) - case DynNew(c, a) => (false, c, a, N) - case Mut(DynNew(c, a)) => (true, c, a, N) + val (mut, cls, as, rft, nwtrm) = nw match + case nwtrm @ New(c, a, r) => (false, c, a, r, nwtrm) + case Mut(nwtrm @ New(c, a, r)) => (true, c, a, r, nwtrm) + case nwtrm @ DynNew(c, a) => (false, c, a, N, nwtrm) + case Mut(nwtrm @ DynNew(c, a)) => (true, c, a, N, nwtrm) case _ => spuriousWarning - subTerm(cls): sr => + classOf(cls, nwtrm): sr => rft match case N => lowerMultiInstantiate(mut, sr, as, annots)(k) case S((isym, rft)) => val sym = new BlockMemberSymbol(isym.name, Nil) loweringCtx.collectScopedSym(sym) val (mtds, publicFlds, privateFlds, ctor) = gatherMembers(rft) - val pctor = parentConstructor(sr, cls, as) + val pctor = parentConstructor(sr, cls, as, nw.toLoc) val clsDef = ClsLikeDefn(N, isym, sym, N, syntax.Cls, N, Nil, S(sr), mtds, privateFlds, publicFlds, pctor, ctor, N, N)(N, Nil) - val inner = new New(sym.ref().resolved(isym), Nil, N)(N) + val inner = new New(sym.ref().resolved(isym), Nil, N)(FlowSymbol.neww(), N) Define(clsDef, term_nonTail(if mut then Mut(inner) else inner)(k)) case Try(sub, finallyDo) => 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 966920309e..d1ac5b13bc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -512,6 +512,13 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case N => doc"${scope.lookup_!(sym, sym.toLoc)} = ${result(p)};${returningTerm(rst, endSemi)}" case S(owner) => + + + + + + + val thisDoc = mkThis(owner) val nme = sym.nme owner match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index db86204d42..0815f87dac 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -47,18 +47,18 @@ object Elaborator: // TODO: rename to ScopeKind? enum OuterCtx: - case Function(returnHandlerSymbol: TempSymbol)(val isGenerator: Bool, val isAsync: Bool) + case Function(returnHandlerSymbol: TempSymbol, sym: Opt[DefinitionSymbol[?]])(val isGenerator: Bool, val isAsync: Bool) case InnerScope(innerSymbol: InnerSymbol) case LocalScope(nameHint: Str) case LambdaOrHandlerBlock - case NonReturnContext + case NonReturnContext(sym: Opt[DefinitionSymbol[?]]) def showDbg: Str = this match - case Function(sym) => s"fun:${sym.nme}" + case Function(retSym, sym) => s"fun:${retSym.nme}${sym.fold("")(s => s"‹${s}›")}" case InnerScope(inner) => inner.toString case LocalScope(hint) => hint case LambdaOrHandlerBlock => "LambdaOrHandlerBlock" - case NonReturnContext => "NonReturnContext" + case NonReturnContext(sym) => s"NonReturnContext(${sym})" def inner: Opt[InnerSymbol] = this match case InnerScope(inner) => S(inner) @@ -141,8 +141,49 @@ object Elaborator: def nestLocal(nameHint: Str): Ctx = nest(OuterCtx.LocalScope(nameHint)) def nestInner(inner: InnerSymbol): Ctx = nest(OuterCtx.InnerScope(inner)) - def get(name: Str): Opt[Ctx.Elem] = - env.get(name).orElse(parent.flatMap(_.get(name))) + /* + def get(name: Str)(using config: Config): Opt[Ctx.Elem] = + env.get(name).orElse: + val nr = config.language.useNewResolution + if nr then parent match + case S(p @ Ctx(outer = OuterCtx.InnerScope(inner))) if nr => + p.get(name).map(Ctx.CaptElem(_, inner.asDefnSym)) + case S(p @ Ctx(outer = OuterCtx.Function(_, _, symo))) if nr => + symo match + case S(inner) => + p.get(name).map(Ctx.CaptElem(_, inner)) + case N => + // TODO: also capture across lambdas. + ??? + case _ => parent.flatMap(_.get(name)) + else parent.flatMap(_.get(name)) + */ + /* + def get(name: Str)(using config: Config): Opt[Ctx.Elem] = + env.get(name).orElse: + val nr = config.language.useNewResolution + if nr then outer match + case OuterCtx.InnerScope(inner) => + parent.flatMap(_.get(name)).map(Ctx.CaptElem(_, inner.asDefnSym)) + case OuterCtx.Function(_, _, symo) => + symo match + case S(inner) => + parent.flatMap(_.get(name)).map(Ctx.CaptElem(_, inner)) + case N => + // TODO: also capture across lambdas. + ??? + case _ => parent.flatMap(_.get(name)) + else parent.flatMap(_.get(name)) + */ + def get(name: Str)(using config: Config): Opt[Ctx.Elem] = + env.get(name).orElse: + val nr = config.language.useNewResolution + if nr then outer match + case OuterCtx.NonReturnContext(S(sym)) => + parent.flatMap(_.get(name)).map(Ctx.CaptElem(_, sym)) + case _ => parent.flatMap(_.get(name)) + else parent.flatMap(_.get(name)) + def lookupLabel(name: Str): LabelLookup = @tailrec def go( @@ -177,15 +218,16 @@ object Elaborator: case _ => false) go(ctx.parent, nextCrossedFunction, nextCrossedLambdaOrHandler) go(S(this), false, false) + def getOuter: Opt[InnerSymbol] = outer.inner.orElse(parent.flatMap(_.getOuter)) def getNonLocalRetHandler: Opt[TempSymbol] = outer match - case OuterCtx.Function(sym) => S(sym) + case OuterCtx.Function(rsym, _) => S(rsym) case _ => parent.flatMap(_.getNonLocalRetHandler) def getRetHandler: ReturnHandler = outer match - case OuterCtx.Function(sym) => ReturnHandler.Direct + case OuterCtx.Function(rsym, _) => ReturnHandler.Direct case _: (OuterCtx.LambdaOrHandlerBlock.type | OuterCtx.InnerScope) => getNonLocalRetHandler.fold(ReturnHandler.NotInFunction)(ReturnHandler.Required(_)) - case OuterCtx.NonReturnContext => ReturnHandler.Forbidden + case OuterCtx.NonReturnContext(_) => ReturnHandler.Forbidden case _: OuterCtx.LocalScope => parent.fold(ReturnHandler.NotInFunction)(_.getRetHandler) def inGenerator: Bool = outer match @@ -381,23 +423,41 @@ object Elaborator: object Ctx: abstract class Elem: def nme: Str - def ref(id: Ident)(using Elaborator.State, Ctx): Resolvable + def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term def symbol: Opt[Symbol] def isImport: Bool final case class RefElem(sym: Symbol) extends Elem: val nme = sym.nme - def ref(id: Ident)(using Elaborator.State, Ctx): Resolvable = - // * Note: due to symbolic ops, we may have `id.name =/= nme`; - // * e.g., we can have `id.name = "|>"` and `nme = "pipe"`. - Term.Ref(sym)(id, N) // FIXME: 666 is a temporary placeholder + def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term = + if config.language.useNewResolution then + sym match + case sym: codegen.SimpleSymbol => + Term.SimpleRef(sym)(id) + case sym: InnerSymbol => + Term.SelfRef(sym)(id) + case sym: MemberSymbol => + // Term.MemberRef(sym)(id, FlowSymbol.memSym(sym, id.name)) + Term.MemberRef(sym)(id, FlowSymbol.memSym(sym)) + // case sym: TermSymbol => // FIXME: should never happen... (currently happens for ref to ctor let) + // Term.MemberRef(sym)(id) + else + // * Note: due to symbolic ops, we may have `id.name =/= nme`; + // * e.g., we can have `id.name = "|>"` and `nme = "pipe"`. + sym.ref(id) def symbol = S(sym) def isImport: Bool = false final case class SelElem(base: Elem, nme: Str, symOpt: Opt[MemberSymbol], isImport: Bool) extends Elem: - def ref(id: Ident)(using Elaborator.State, Ctx): Resolvable = + def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term = // * Same remark as in RefElem#ref Term.SynthSel(base.ref(Ident(base.nme)), new Ident(nme).withLocOf(id))(symOpt, FlowSymbol.synthSel(nme), N, S(summon)) def symbol = symOpt + final case class CaptElem(base: Elem, thru: DefinitionSymbol[?]) extends Elem: + def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term = + Term.Capture(base.ref(Ident(base.nme)), thru) + def symbol = base.symbol + def isImport: Bool = false + def nme: Str = base.nme given Conversion[Symbol, Elem] = RefElem(_) val empty: Ctx = Ctx(OuterCtx.LocalScope("top-level"), N, Map.empty, Mode.Full, Map.empty) @@ -634,13 +694,16 @@ extends Importer: import tl.* given TraceLogger = tl + val newResolution: Bool = config.language.useNewResolution + lazy val illegalMemberNameTail = msg"Member names must start with a letter or underscore, followed by letters, digits, or underscores." -> N :: Nil def mkLetBinding(kw: Tree.Keywrd[?], sym: LocalVarSymbol | TermSymbol, rhs: Term, annotations: Ls[Annot]): Ls[Statement] = - LetDecl(sym, annotations).mkLocWith(kw, sym) :: DefineVar(sym, rhs) :: Nil + LetDecl(sym, annotations).mkLocWith(kw, sym) :: defineVar(sym, rhs) :: Nil + // TODO: remove in favor of new resolution logic def resolveField(srcTree: Tree, base: Opt[Symbol], nme: Ident): Opt[MemberSymbol] = base match case S(psym: BlockMemberSymbol) => @@ -741,8 +804,8 @@ extends Importer: Nil, N, ) - tsym.defn = S(td) mtdSym.tsym = S(tsym) + tsym.defn = S(td) HandlerTermDefinition(resumeSym, td) Term.Handle(handlerSymbol, state.nonLocalRetHandlerTrm, Nil, clsSym, htds, body) @@ -757,7 +820,7 @@ extends Importer: val rs = FlowSymbol.app() val mtdTree = new Ident(methodName) val argTree = new Tup(argTrees) - Term.App( + app( Term.Sel(handlerSymbol.ref(callSiteId), mtdTree)( S(state.nonLocalRet), FlowSymbol.sel(callSiteId.name), N, S(summon)), Term.Tup(argTerms.map(term => PlainFld(term)))(argTree), @@ -838,13 +901,26 @@ extends Importer: t.split match case block: Block => termSplit(block.desugStmts, identity) case other: Tree => termSplit(Ls(other), identity) - new Term.IfLike(t.kw.kw, form, split).withLocOf(t) + ifLike(t.kw.kw, form, split, t.toLoc) /** Elaborate `case` expressions */ protected def caseSplit(scrut: VarSymbol, tree: Case): Ctxl[Term.IfLike] = val (form, split) = withScopedConnectives(tree.kw): patternBranch(() => scrut.ref(), tree.branches, identity) - new Term.IfLike(tree.kw.kw, form, split).withLocOf(tree) + ifLike(tree.kw.kw, form, split, tree.toLoc) + + protected def mkSplitLet(binding: LocalVarSymbol, term: Term): Head.Let = + if newResolution then + binding match + case binding: ShapeHost => + if newResolution then pipeTerm(term, binding) + // case _ => ??? + Head.Let(binding, term) + + protected def mkMatch(scrutinee: Term.Ref, pattern: Pattern, consequent: SimpleSplit) = + log(s"mkMatch: scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, consequent = ${consequent.showDbg}") + matchScrutPat(scrutinee, pattern) + Head.Match(scrutinee, pattern, consequent) /** Elaborate shorthand expressions. */ protected def shorthandSplit(tree: Tree)(using UnderCtx): Ctxl[SimpleSplit] = @@ -856,15 +932,15 @@ extends Importer: pattern match case Block(Nil) => val recordPattern = Pattern.Record(Nil).withLocOf(pattern) - Head.Match(scrutinee(), recordPattern, innerSplit) ~: negative + mkMatch(scrutinee(), recordPattern, innerSplit) ~: negative case Block(trees) => trees.foldRight(negative): case (pattern, alternative) => - Head.Match(scrutinee(), this.pattern(pattern), innerSplit) ~: alternative + mkMatch(scrutinee(), this.pattern(pattern), innerSplit) ~: alternative case _ => val firstPattern = this.pattern(pattern) firstPattern.variables.report (ctx ++ firstPattern.variables.allocate).givenIn: - Head.Match(scrutinee(), firstPattern, innerSplit) ~: negative + mkMatch(scrutinee(), firstPattern, innerSplit) ~: negative /** Desugar a list of trees as a term split. The returned function takes a * function, which takes a `Ctx` and returns a `SimpleSplit` representing @@ -914,11 +990,11 @@ extends Importer: // Interleaved-`let` bindings like `{ x is A then 0; let x = 1; ... }`. case LetLike(Keywrd(`let`), ident: Ident, S(rhsTree), N) => val symbol = VarSymbol(ident, erasedType = N) - val head = Head.Let(symbol, term(rhsTree)) + val head = mkSplitLet(symbol, term(rhsTree)) ((ctx + (ident.name -> symbol)), head ~: End) // Interleaved-`do` statements like `{ x is A then 0; do log(1); ... }`. case PrefixApp(Keywrd(`do`), rhsTree) => - (ctx, Head.Let(TempSymbol(N, erasedType = N, "unused"), term(rhsTree)) ~: End) + (ctx, mkSplitLet(TempSymbol(N, erasedType = N, "unused"), term(rhsTree)) ~: End) // Although the `else`-clause marks the end of the split, we cannot // stop and still have to elaborate the remaining trees. case PrefixApp(kwTree @ Keywrd(`else`), elseTree) => @@ -938,7 +1014,7 @@ extends Importer: val split = matches.foldLeft(consequent(using innerCtx)): case (innerSplit, (scrutinee, pattern)) => scrutinee.reference: scrutineeRef => - Head.Match(scrutineeRef(), pattern, innerSplit) ~: End + mkMatch(scrutineeRef(), pattern, innerSplit) ~: End split private def termBranch(t: Tree, mk: Term => Term): Ctxl[(Ctx, SimpleSplit)] = branch.appOrElse(t): @@ -956,7 +1032,7 @@ extends Importer: case coda => mk(term(coda)).reference: scrutinee => val pattern = this.pattern(patternTree) val innerCtx = ctx ++ pattern.variables.allocate - Head.Match(scrutinee(), pattern, innerSplit(using innerCtx)) ~: End + mkMatch(scrutinee(), pattern, innerSplit(using innerCtx)) ~: End (ctx, split) // Handle splits on binary operators. case OpApp(lhs, ident: Ident, rhss) => @@ -964,7 +1040,7 @@ extends Importer: val split = term(lhs).reference: lhs => val mk2 = (rhs: Term) => val args = Term.Tup(PlainFld(lhs()) :: PlainFld(rhs) :: Nil)(DummyTup) - Term.App(op, args)(Tree.DummyApp, N, FlowSymbol("‹operator-split›")) + app(op, args)(Tree.DummyApp, N, FlowSymbol("‹operator-split›")) termSplit(rhss, mk2 andThen mk) (ctx, split) case OpSplit(lhs, rhss) => @@ -1006,7 +1082,7 @@ extends Importer: case L(tree) => termSplit(Ls(tree), identity) case R((kw, tree)) => Else(term(tree))(S(kw)) - Head.Match(scrutinee(), firstPattern, split) ~: End + mkMatch(scrutinee(), firstPattern, split) ~: End case _ => error(msg"Unrecognized pattern split (${t.describe})." -> t.toLoc) Else(Term.Error().withLocOf(t))(N).withLocOf(t) // To inspect the source of errors. @@ -1019,7 +1095,7 @@ extends Importer: // Otherwise, we need to create a temporary symbol holding the term. case term: Term => val symbol = TempSymbol(N, erasedType = N, "scrut") - Head.Let(symbol, term) ~: continuation(() => symbol.ref()) + mkSplitLet(symbol, term) ~: continuation(() => symbol.ref()) private type TT = (Tree, Tree) @@ -1057,7 +1133,7 @@ extends Importer: go(tree, ::(_, Nil))() def term(tree: Tree): Ctxl[Term] = - trace[Term](s"Elab term ${tree.showDbg}", r => s"~> $r"): + trace[Term](s"Elab term ${tree.showDbg}", r => s"~> ${r.showDbg}"): val unders = mutable.ArrayBuffer.empty[VarSymbol] given UnderCtx = new UnderCtx(S(unders)) val st = subterm(tree) @@ -1067,32 +1143,79 @@ extends Importer: if params.isEmpty then st else Term.Lam(PlainParamList(params), st) + /* + private def termDefinition( + k: TermDefKind, // * The only reason we store it here in addition to tsym.k is for refining patmats + sym: BlockMemberSymbol, + tsym: TermSymbol, + params: Ls[ParamList], + tparams: Opt[Ls[Param]], + sign: Opt[Term], + body: Opt[Term], + flags: TermDefFlags, + modulefulness: Modulefulness, + annotations: Ls[Annot], + companion: Opt[CompanionSymbol], + ): TermDefinition = + val res = new TermDefinition( + k, sym, tsym, params, tparams, sign, body, flags, modulefulness, annotations, companion) + body.foreach: body => + listenTerm(body, shape => termDefShape(shape, res)) + res + */ + + private def app(lt: Term, rt: Term)(tree: Tree.App, typ: Opt[typing.Type], resSym: FlowSymbol): Term = + val res = new Term.App(lt, rt)(tree, typ, resSym) + if newResolution then listenTerm(lt)(shape => appShape(shape, rt, res)) + res + + def ifLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit, loc: Opt[Loc]): Term.IfLike = + val res = new Term.IfLike(kw, form, split).withLoc(loc) + if newResolution then + // log(s"Listening to if-like split ${res.showDbg}") + split.results.foreach: trm => + pipeTerm(trm, res) + res + + def subterm(tree: Tree): Ctxl[UnderCtx ?=> Term] = - trace[Term](s"Elab subterm ${tree.showDbg}", r => s"~> $r"): + trace[Term](s"Elab subterm ${tree.showDbg}", r => s"~> ${r.showDbg}"): def error = Term.Error().withLocOf(tree) + def mkNew(cls: Term, args: Ls[Term], rft: Opt[ClassSymbol -> ObjBody])(typ: Opt[typing.Type]): Term.New = + val res = new Term.New(cls, args, rft)(FlowSymbol.neww(), typ).withLocOf(tree) + if newResolution then resolveNew(res) + res + /** Fallback to a normal selection + application when label-specific handling does not apply. */ def mkNonLabelSelectionApp(tree: App, sel: Sel, args: Ls[Tree]): Term = val sym = FlowSymbol.app() val lt = subterm(sel) val rt = subterm(Tup(args)) - Term.App(lt, rt)(tree, N, sym) + app(lt, rt)(tree, N, sym) def elaborateSelection(tree: Sel): Term = val preTrm = subterm(tree.prefix) - val sym = resolveField(tree.name, preTrm.symbol, tree.name) - if sym.contains(ctx.builtins.source.line) then - val loc = tree.toLoc.getOrElse(???) - val (line, _, _) = loc.origin.fph.getLineColAt(loc.spanStart) - Term.Lit(IntLit(loc.origin.startLineNum + line)) - else if sym.contains(ctx.builtins.source.name) then - Term.Lit(StrLit(ctx.getOuter.map(_.nme).getOrElse(""))) - else if sym.contains(ctx.builtins.source.file) then - val loc = tree.toLoc.getOrElse(???) - Term.Lit(StrLit(loc.origin.fileName.toString)) + if newResolution then + val res = new Term.NewSel(preTrm, tree.name)(FlowSymbol.sel(tree.name.name)).withLocOf(tree) + // listenTerm(preTrm, shape => selShape2(shape, tree.name, res)) + newSel(res) + res else - Term.Sel(preTrm, tree.name)(sym, FlowSymbol.sel(tree.name.name), N, S(summon)) + val sym = if newResolution then N + else resolveField(tree.name, preTrm.symbol, tree.name) + if sym.contains(ctx.builtins.source.line) then + val loc = tree.toLoc.getOrElse(???) + val (line, _, _) = loc.origin.fph.getLineColAt(loc.spanStart) + Term.Lit(IntLit(loc.origin.startLineNum + line)) + else if sym.contains(ctx.builtins.source.name) then + Term.Lit(StrLit(ctx.getOuter.map(_.nme).getOrElse(""))) + else if sym.contains(ctx.builtins.source.file) then + val loc = tree.toLoc.getOrElse(???) + Term.Lit(StrLit(loc.origin.fileName.toString)) + else + Term.Sel(preTrm, tree.name)(sym, FlowSymbol.sel(tree.name.name), N, S(summon)) tree.desugared match case Trm(term) => term @@ -1138,7 +1261,7 @@ extends Importer: val lt = subterm(lhs) val sym = TempSymbol(S(lt), erasedType = N, "old") Blk( - LetDecl(sym, Nil) :: DefineVar(sym, lt) :: Nil, Term.Try(Blk( + LetDecl(sym, Nil) :: defineVar(sym, lt) :: Nil, Term.Try(Blk( Term.Assgn(lt, subterm(rhs)) :: Nil, subterm(bod), ), Term.Assgn(lt, sym.ref()))) @@ -1267,10 +1390,10 @@ extends Importer: case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => block(tree :: Nil, hasResult = false)._1 case PrefixApp(kw @ Keywrd(Keyword.`not`), rhs) => - Term.App(State.builtinOpsMap("!").ref(new Ident("not").withLocOf(kw)), Term.Tup( + app(State.builtinOpsMap("!").ref(new Ident("not").withLocOf(kw)), Term.Tup( PlainFld(subterm(rhs)) :: Nil)(DummyTup))(DummyApp, N, FlowSymbol("not-app")) case tree @ InfixApp(lhs, Keywrd(Keyword.`is` | Keyword.`and` | Keyword.`or`), rhs) => - Term.IfLike(Keyword.`if`, IfLikeForm.ReturningIf, shorthandSplit(tree)) + ifLike(Keyword.`if`, IfLikeForm.ReturningIf, shorthandSplit(tree), tree.toLoc) case InfixApp(Sel(pre, idn: Ident), Keywrd(Keyword.`#`), idp: Ident) => val c = subterm(idn) val f = c.symbol.flatMap(_.asCls) match @@ -1351,13 +1474,13 @@ extends Importer: val sym = FlowSymbol.app() val lt = subterm(lhs) val rt = subterm(rhs) - Term.App(lt, rt)(tree, N, sym) + app(lt, rt)(tree, N, sym) case tree @ OpApp(lhs, op, rhss) => val sym = FlowSymbol.app() val lt = subterm(lhs) val ot = subterm(op) val rts = rhss.map(r => PlainFld(subterm(r))) - Term.App(ot, Term.Tup(PlainFld(lt) :: rts)(DummyTup))( + app(ot, Term.Tup(PlainFld(lt) :: rts)(DummyTup))( DummyApp, N, sym) case SynthSel(pre, nme) => val preTrm = subterm(pre) @@ -1422,7 +1545,7 @@ extends Importer: ) val rs = FlowSymbol.app() Term.Lam(ps, - Term.App(Term.SelProj(self.ref(), c, nme)(f, FlowSymbol.selProj(nme.name), N, S(summon)), args.ref())( + app(Term.SelProj(self.ref(), c, nme)(f, FlowSymbol.selProj(nme.name), N, S(summon)), args.ref())( App(nme, Tup(Nil)) // FIXME , N, rs) ) @@ -1455,15 +1578,15 @@ extends Importer: val (mut, c2) = c match case Modified(Keywrd(Keyword.`mut`), c) => (true, c) case c => (false, c) - val inner = new Term.New( + val inner = mkNew( subterm(c2), // * Note: we'll catch bad `new` targets during type checking args.map(subterm(_)), bodo - )(N).withLocOf(tree) + )(N) if mut then Term.Mut(inner) else inner case N => val objectRef = ctx.builtins.Object.bms.get.ref(Ident("Object")) - Term.New(objectRef, Nil, bodo)(N).withLocOf(tree) + mkNew(objectRef, Nil, bodo)(N) // case _ => // raise(ErrorReport(msg"Illegal new expression." -> tree.toLoc :: Nil)) @@ -1517,7 +1640,7 @@ extends Importer: case PrefixApp(kw @ Keywrd(Keyword.`yield` | Keyword.`yield*`), body) => if ctx.inGenerator then val synthIdent = new Tree.Ident(kw.kw.name).withLocOf(kw) - Term.App(ident(synthIdent).get, Term.Tup(PlainFld(subterm(body)) :: Nil)(DummyTup))(DummyApp, N, FlowSymbol("yield")) + app(ident(synthIdent).get, Term.Tup(PlainFld(subterm(body)) :: Nil)(DummyTup))(DummyApp, N, FlowSymbol("yield")) else raise: ErrorReport(msg"Yield expressions are not allowed in this context." -> tree.toLoc :: Nil) @@ -1587,12 +1710,12 @@ extends Importer: val res = go(acc, lhs :: Nil) val sym = FlowSymbol.app() val fl = Fld(FldFlags.empty, res, N) - val app = Term.App(subterm(f), Term.Tup( + val ap_ = app(subterm(f), Term.Tup( fl :: args.map(fld))(tup))(ap, N, sym) - go(app, trees) + go(ap_, trees) case (ap @ App(f, tup @ Tup(args))) :: trees => val sym = FlowSymbol.app() - go(Term.App(subterm(f), + go(app(subterm(f), Term.Tup(Fld(FldFlags.empty, acc, N) :: args.map(fld))(tup) )(ap, N, sym), trees) case Block(sts) :: trees => @@ -1696,7 +1819,7 @@ extends Importer: def blockOrRcd(blk: Block, hasResult: Bool)(using UnderCtx) : Ctxl[(Blk | Rcd, Ctx)] = trace[(Blk | Rcd, Ctx)]( - pre = s"Elab block ${blk.desugStmts.toString.truncate(100, "[...]")} ${ctx.outer}", r => s"~> ${r._1}" + pre = s"Elab block ${blk.desugStmts.toString.truncate(100, "[...]")} ${ctx.outer}", r => s"~> ${r._1.showDbg}" ): val members = blk.definedSymbols.toMap @@ -1880,7 +2003,7 @@ extends Importer: val sym = new VarSymbol(id, erasedType = N) newCtx += id.name -> sym RcdField(Term.Lit(StrLit(id.name)).withLocOf(id), sym.ref(id)) - :: DefineVar(sym, rhs_t) + :: defineVar(sym, rhs_t) :: LetDecl(sym, annotations) :: acc case lit: Literal => @@ -1924,7 +2047,7 @@ extends Importer: ctx.get(id.name) match case S(elem) => elem.symbol match - case S(sym: (LocalSymbol | TermSymbol)) => go(sts, Nil, DefineVar(sym, r) :: acc) + case S(sym: (LocalSymbol | TermSymbol)) => go(sts, Nil, defineVar(sym, r) :: acc) case S(sym) => raise(ErrorReport(msg"Symbol '${id.name}' is not a variable and cannot be reassigned" -> id.toLoc :: Nil)) go(sts, Nil, Term.Error().withLocOf(id) :: acc) @@ -1966,7 +2089,10 @@ extends Importer: :: illegalMemberNameTail return go(sts, Nil, acc) val isMethod = owner.exists(_.isInstanceOf[ClassSymbol]) - val tdf = ctx.nest(OuterCtx.NonReturnContext).givenIn: newCtx ?=> + + val tsym = TermSymbol(k, owner, id, erasedType = N) // TODO? + + val tdf = ctx.nest(OuterCtx.NonReturnContext(S(tsym))).givenIn: newCtx ?=> // * Add type parameters to context val (tps, newCtx1) = td.typeParams match case S(t) => @@ -1979,6 +2105,7 @@ extends Importer: val (res, newCtx2) = funParams(ps)(using newCtx) newCtx = newCtx2 res + // * Elaborate signature val st = td.annotatedResultType.orElse(newSignatureTrees.get(id.name)) // FIXME: may elaborate external sig twice!! val s = st.map: @@ -1987,6 +2114,7 @@ extends Importer: // ^^^^^^ case TypeDef(Mod, st, N) => term(st)(using newCtx) case st => term(st)(using newCtx) + val body: Opt[Term] = rhs match case N => N case _ if ctx.mode is Mode.Light => S(Term.Missing) @@ -1996,7 +2124,7 @@ extends Importer: val hasAsyncAnnotation = annotations.contains(Annot.Async) if pss.isEmpty && hasGeneratorAnnotation then raise(ErrorReport(msg"Generators are not supported on functions without a parameter list" -> td.toLoc :: Nil)) - newCtx.nest(OuterCtx.Function(nonLocalRetHandler)(pss.nonEmpty && hasGeneratorAnnotation, hasAsyncAnnotation)).givenIn: newCtx ?=> + newCtx.nest(OuterCtx.Function(nonLocalRetHandler, S(tsym))(pss.nonEmpty && hasGeneratorAnnotation, hasAsyncAnnotation)).givenIn: newCtx ?=> val b = term(rhs)(using newCtx) if nonLocalRetHandler.directRefs.isEmpty then b else mkEffectHandleAbortive( @@ -2086,11 +2214,12 @@ extends Importer: else S(ErasedType.FuncRef(rsc = S(false), physicalParamLists, retTpe)) case _: syntax.Val => retTpe case _ => N - val tsym = TermSymbol(k, owner, id, erasedType = erasedTpe) // TODO? + // val tsym = TermSymbol(k, owner, id, erasedType = erasedTpe) // TODO? + tsym.erasedType = erasedTpe val tdf = TermDefinition(k, sym, tsym, pss, tps, s, body, TermDefFlags.empty.copy(isMethod = isMethod), mfn, annotations, N).withLocOf(td) - tsym.defn = S(tdf) sym.tsym = S(tsym) + tsym.defn = S(tdf) tdf go(sts, Nil, tdf :: acc) @@ -2131,7 +2260,7 @@ extends Importer: var newCtx = S(td.symbol).collectFirst: case s: InnerSymbol => s - .fold(ctx.nest(OuterCtx.NonReturnContext))(ctx.nestInner(_)) + .fold(ctx.nest(OuterCtx.NonReturnContext(S(td.symbol))))(ctx.nestInner(_)) val tps = td.typeParams match case S(ts) => @@ -2186,7 +2315,10 @@ extends Importer: case s: InnerSymbol => S(s) case _: TypeAliasSymbol => die - if p.flags.isVal || isDataClass + val isPublicField = p.flags.isVal || isDataClass + if isPublicField + // TODO: + || newResolution // TODO: rm `|| newResolution` then val k = if p.flags.mut then MutVal else ImmutVal val fsym = BlockMemberSymbol(p.sym.nme, Nil) @@ -2199,10 +2331,13 @@ extends Importer: fsym, tsym, Nil, N, N, - S(p.sym.ref()), - TermDefFlags.empty.copy(isMethod = (k is Cls)), + if newResolution then + S(Term.Capture(Term.SimpleRef(p.sym)(Ident(p.sym.name)), tsym)) + else S(p.sym.ref()) + , + TermDefFlags.empty.copy(isMethod = (k is Cls)), // FIXME?! p.modulefulness, - Nil, + if isPublicField then Nil else Annot.Private :: Nil, N, ).withLocOf(p) assert(p.fldSym.isEmpty) @@ -2210,12 +2345,13 @@ extends Importer: fsym.tsym = S(tsym) tsym.defn = S(fdef) p.sym.erasedType.foreach(tsym.populateErasedType) + fsym.complete() fdef :: Nil else val psym = TermSymbol(LetBind, owner, p.sym.id, erasedType = p.sym.erasedType) psym.sourceAliases = p.sym.sourceAliases - val decl = LetDecl(psym, Nil) - val defn = DefineVar(psym, p.sym.ref()) + val decl = LetDecl(psym, Nil) // TODO: never use term symbols on LetDecl LHS + val defn = defineVar(psym, p.sym.ref()) p.fldSym = S(psym) decl :: defn :: Nil @@ -2228,7 +2364,7 @@ extends Importer: val psym = TermSymbol(LetBind, owner, p.sym.id, erasedType = p.sym.erasedType) psym.sourceAliases = p.sym.sourceAliases val decl = LetDecl(psym, Nil) - val defn = DefineVar(psym, p.sym.ref()) + val defn = defineVar(psym, p.sym.ref()) p.fldSym = S(psym) decl :: defn :: Nil @@ -2439,8 +2575,29 @@ extends Importer: case _ => go(sts, Nil, res :: acc) end go - ctx.withMembers(members).givenIn: + val res = ctx.withMembers(members).givenIn: go(blk.desugStmts, Nil, Nil) + + // if newResolution then + members.valuesIterator.foreach(_.complete()) + members.valuesIterator.foreach: bms => + bms.symbols.foreach: + case sym: (ClassLikeSymbol & InnerSymbol) => + sym.defn.foreach: d => // erroneous code may not have a defn even after the BMS is completed + d.ext match + case S(ext) => + listenTerm(ext): esh => + val sh = BaseShape(d, S(esh)) // TODO actually use applied shape providing generics? + sym.shapeListeners.foreach(_(sh)) + case N => + val sh = BaseShape(d, N) // TODO actually use applied shape providing generics? + sym.shapeListeners.foreach(_(sh)) + case sym: InnerSymbol => + // println(">>> "+sym) + // TODO: patterns + case _ => () + + res def mkBlk(acc: Ls[Statement], res: Opt[Term], hasResult: Bool): Blk | Rcd = @@ -2692,8 +2849,13 @@ extends Importer: case InfixApp(lhs, Keywrd(op @ (Keyword.`|` | Keyword.`&`)), rhs) => Composition(op is Keyword.`|`, go(lhs), go(rhs)) // Constructor patterns with pattern arguments and arguments. - case App(ctor: Ctor, Tup(argTrees)) => - Constructor(term(ctor), S(argTrees.map(go(_)))) + case App(ctor: Ctor, Tup(argTrees)) => // TODO: rm this weird `: Ctor` guard + val lt = term(ctor) + val rt = argTrees.map(go(_)) + val res = new Constructor(lt, S(rt)) + // if newResolution then listenTerm(lt, shape => patAppShape(shape, rt, res)) + patApp(lt, rt, res) + res // `[p1, p2, ...ps, pn] => term`: All patterns are in the `TyTup`. case (lhs: TyTup) `=>` rhs => arrow(lhs, rhs) // `pattern => term`: Note that `pattern` is wrapped in a `Tup`. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala new file mode 100644 index 0000000000..6d7010c6ce --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -0,0 +1,581 @@ +package hkmc2 +package semantics + +import scala.collection.mutable +import scala.annotation.tailrec + +import hkmc2.utils.*, shorthands.* +import hkmc2.utils.* +import hkmc2.Message.MessageContext +import hkmc2.io +import utils.TraceLogger +import syntax.* +// import Tree.* +import Term.* + +import Elaborator.* +import hkmc2.syntax.LetBind + + +class NewResolver: + self: Elaborator => + import tl.* + + /* + class Constraint(val lhs: Term, val rhs: Term): + override def equals(obj: Any): Bool = obj match + case that: Constraint => (this.lhs is that.lhs) && (this.rhs is that.rhs) + case _ => false + private var _hash: Int = 0 + override def hashCode(): Int = + if _hash =/= 0 then _hash + else + var h = lhs.hashCode() * 31 + rhs.hashCode() + if h === 0 then h += 1 + _hash = h + h + def showDbg(using DebugPrinter): Str = + s"${lhs.showDbg} <: ${rhs.showDbg}" + + val processedConstraints: mutable.Set[Constraint] = mutable.Set.empty + val collectedConstraints: mutable.Buffer[Constraint] = mutable.Buffer.empty + */ + + // * The `FlowSymbol`s are currently used to uniquely identify terms + val appShapes: mutable.Map[(TermShape, FlowSymbol), AppShape] = mutable.Map.empty + val newShapes: mutable.Map[(ClassLikeSymbol, FlowSymbol), NewShape] = mutable.Map.empty + val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? + val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty + val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty + + def isOwnedSym(sym: Symbol): Bool = + sym.getState is state + + def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])]): Unit = raise: + ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) + + def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Ls[Elem], src: Term, funSh: TermShape): Unit = + (ps, args) match + case (Nil, Nil) => () + // case (Nil, Spd(k, t) :: args) => + // zip(Nil, r, args) + case (Nil, args) => + r match + case N => + resolError(src, + msg"${funSh.describe.capitalize} expected ${ps.length} ${ + "argument".pluralized(ps.length)}, but got ${args.length}" -> funSh.toLoc :: Nil) + case S(p) => + val packaged = new Tup(args)(Tree.DummyTup).withLoc(Loc.mk(args.iterator.flatMap(_.toLoc))) + val sh = IntroShape(packaged) + p.sym.shapeListeners.foreach(_(sh)) + case (p :: ps, Fld(fls, trm, asc) :: args) => + // case ((p, mss) :: ps, Fld(fls, trm, asc) :: args) => + listenTerm(trm): sh0 => + // val sh = sh0.enter(mss) + // log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg}") + // if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then + // p.sym.shapeListeners.foreach(listener => listener(sh)) + sh0.enter(mss) match + case NoShape => + case sh: TermShape => + log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg} ${isOwnedSym(p.sym)}, ${p.sym.shapes.contains(sh)}") + if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then + // log(s"!!!! ${p.sym.shapeListeners}") + p.sym.shapeListeners.foreach(listener => listener(sh)) + zipArgs(mss, ps, r, args, src, funSh) + case _ => + resolError(src, + msg"${funSh.describe.capitalize} expected ${ps.length} ${ + "argument".pluralized(ps.length)}, but got ${args.length}" -> funSh.toLoc :: Nil) + + // def patAppShape(lhs: Shape, args: Ls[Pattern], res: Pattern.Constructor): Unit = + // // listen(lhs, sh => + // // sh match + // // case sh: TermShape => + // // zipArgs(sh.unappliedParams.map(_._2), args, N, res.args, res, lhs) + // // case _ => + // // softAssert(res.isErroneous) + // // ) + // ??? + def patApp(lhs: Term, args: Ls[Pattern], res: Pattern.Constructor): Unit = if newResolution then + listen(lhs, discardMarks = true): sh => + log(s"patApp: lhs = ${lhs.showDbg}, args = ${args.map(_.showDbg)}, res = ${res.showDbg}, sh = ${sh.shwDbg}") + def checkEmpty = () // TODO + sh match + case sh: TermShape => + // zipArgs(sh.unappliedParams.map(_._2), args, N, res.args, res, lhs) + ??? + case sh: SymShape => + // softAssert(res.isErroneous) + val bms = sh.sym + bms.onComplete: () => + bms.asPat match + case S(pat) => + checkEmpty + res.resolvedSym = S(pat) + case N => + val flow = FlowSymbol.app() + fromBMS(bms, flow, sh.markss, sh => + sh match + case sh: TermShape => + // zipArgs(sh.unappliedParams.map(_._2), args, N, res.args, res, lhs) + // sh.unappliedParams + // ???FlowSymbol.app + sh.applicationHead match + case (ds: DefnShape, mss) => + val cls = ds.defn match + case cd: ClassDef => cd + case td: TermDefinition => + td.tsym match + case ccs: ClassCtorSymbol => ccs.associatedCls.defn.get + case _ => ??? + log(s"Pattern's class: $cls") + lhs.withoutCaptures match + case trm: NewResolvable => + trm.resolvedTargets ::= cls.sym + res.resolvedSym = S(cls.sym) + cls.paramsOpt match + case N => + res.isErroneous = true + resolError(res, + msg"${sh.describe.capitalize} does not take pattern arguments." -> sh.toLoc :: Nil) + case S(ps) => + if ps.restParam.nonEmpty then TODO(ps.restParam) + if args.sizeCompare(ps.params) =/= 0 then + res.isErroneous = true + resolError(res, + msg"${sh.describe.capitalize} expected ${ps.params.length} ${ + "pattern argument".pluralized(ps.params.length)}, but got ${args.length}" -> sh.toLoc :: Nil) + val assoc = ps.params.lazyZip(args).map: (p, a) => + log(s"Pattern's param: ${p.showDbg} (${p.fldSym}), arg: ${a.showDbg}") + p.fldSym match + case S(fldSym: BlockMemberSymbol) => + (fldSym, a) + case S(fldSym) => die + case N => ??? + val psh = CtorPatternShape(cls, assoc, res, FlowSymbol.pat()) + if res.shapes.add(psh) then + res.shapeListeners.foreach(listener => listener(psh)) + case _ => + res.isErroneous = true + resolError(res, + msg"${sh.describe.capitalize} cannot used like an applied pattern." -> sh.toLoc :: Nil) + , lhs) + + def listenPattern(pat: Pattern)(listener: PatternShape => Unit): Unit = + // ??? + log(s"listenPattern: pat = ${pat.showDbg} ${pat.getClass.getSimpleName}") + pat.shapeListeners += listener // FIXME: sus + pat match + case pat: PatternShapeHost => + pat.shapes.foreach(listener) + // pat.shapeListeners += listener + case pat: Pattern.Alias => + // TODO: also handle the alias symbol's shape listeners? + // listenTerm(pat.) + /* + def listen(sh: Shape): Unit = + matchShapePat(sh, pat.pattern) + pat.symbol.shapes.foreach(listen) + pat.symbol.shapeListeners += listen + listenPattern(pat.pattern)(listener) + */ + listener(AliasPatternShape(pat.symbol, pat)) + case Pattern.Literal(_) | Pattern.Tuple(_, _) => // TODO + case Pattern.Wildcard() => + () + // case _ => ??? + + def matchShapePat(shape: Shape, pattern: Pattern): Unit = + pattern match + case al @ Pattern.Alias(pat, id) => + matchShapePat(shape, pat) + log(s"TODO: $id ${al.symbol}") + if al.symbol.shapes.add(shape) then + al.symbol.shapeListeners.foreach(listener => listener(shape)) + // pipeTerm(id, shape) + case Pattern.Wildcard() => + // case _ => ??? + + def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern): Unit = + log(s"matchScrutPat? scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}") + if newResolution then + listenPattern(pattern): psh => + trace(s"matchScrutPat: scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, psh = ${psh.showDbg}"): + listenTerm(scrutinee): sh => + trace(s"matchScrutPat scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, sh = ${sh.shwDbg}"): + if !sh.isSaturated then + ??? + psh match + case AliasPatternShape(sym, pat) => + log(s"matchScrutPat: scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, psh = ${psh.showDbg}") + matchShapePat(sh, pat) + if sym.shapes.add(sh) then + sym.shapeListeners.foreach(listener => listener(sh)) + case CtorPatternShape(cls, fs, src, resSym) => + sh.applicationHead match + case (ds: DefnShape, mss) => + if ds.extendsCls(cls) then + log(s"Subclass: ${ds.defn.sym.showDbg} of ${cls.sym.showDbg}") + fs.foreach: (bms, pat) => + // bms.onComplete: () => + val nme = bms.nme + sh.members.get(nme) match + case S((sym, mss)) => + val sh = symShapes.getOrElseUpdate((bms, resSym, mss), SymShape(bms, resSym, mss)) + // if src.trmHost.shapes.add(sh) then + // src.trmHost.shapeListeners.foreach(listener => listener(sh)) + matchShapePat(sh, pat) + case N => + ??? + else + log(s"Not a subclass: ${ds.defn.sym.showDbg} of ${cls.sym.showDbg}") + + def appShape(lhs: TermShape, args: Term, res: App): Unit = + // log(s"appShape? lhs = $lhs, args = $args, res = $res") + val sh = appShapes.getOrElseUpdate((lhs, res.resSym), { + log(s"appShape: lhs = ${lhs.shwDbg}, args = ${args.showDbg}, res = ${res.showDbg}") + new AppShape(lhs, args, res) + }) + lhs.unappliedParams match + case Nil => () + case (ps, mss) :: pss => + args match + case args: Tup => + log(s"Zipping ${ps} (${mss.map(_.showDbg)}) with ${args.fields}") + zipArgs(mss, ps.params, ps.restParam, args.fields, res, lhs) + case _ => ??? + log(s"appShape isSaturated? ${sh.isSaturated}; head? ${sh.applicationHead}") + def register = if res.shapes.add(sh) then + res.shapeListeners.foreach(listener => listener(sh)) + log(s"lhs ${lhs.isSaturated} ${lhs.unappliedParams.map(_.mapFirst(_.showDbg).mapSecond(_.map(_.showDbg)))}") + if lhs.isSaturated && !res.isErroneous then + res.isErroneous = true + if lhs.applicationHead._1 is lhs + then resolError(res, + msg"${lhs.describe.capitalize} cannot be called like a function." -> lhs.toLoc :: Nil) + else resolError(res, + msg"${lhs.describe.capitalize} cannot receive more argument lists." -> lhs.toLoc :: Nil) + if sh.isSaturated then + def go(body: Term, mss: Ls[Marks]) = + listenTerm(body): sh => + sh.exit(mss) match + case NoShape => + case sh: TermShape => + if res.shapes.add(sh) then + res.shapeListeners.foreach(listener => listener(sh)) + sh.applicationHead match + case (ds: DefnShape, mss) => + ds.defn match + case cd: ClassDef => + // TODO: resolve ctor? + // TODO: handle `mss` + register + case td: TermDefinition => + // listenTerm(td.body, sh => newShape(sh, args, res)) + td.tsym match + case ccs: ClassCtorSymbol => // TOOD: to avoid the special case, give this the actual body? + softAssert(td.body.isEmpty) + // ccs.associatedCls + // TODO: handle `mss` + register + case _ => + log(s"appShape: td.body = ${td.body}") + td.body.foreach: body => + go(body, mss) + case _ => + softAssert(res.isErroneous) + case (sh: IntroShape, _) => + sh.trm match + case Lam(params, body) => + go(body, Nil) + case _ => + softAssert(res.isErroneous) + case _ => + softAssert(res.isErroneous) + else register + + def newSel(sel: NewSel): Unit = + log(s"newSel? sel = ${sel.showDbg}") + listenTerm(sel.prefix): shape => + log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") + shape.members.get(sel.id.name) match + case S((bms, mss)) => + log(s"newSel member: bms = ${bms.showDbg}, mss = ${mss.map(_.showDbg)}") + sel.resolvedMembers ::= bms + val sh = symShapes.getOrElseUpdate((bms, sel.resSym, mss), SymShape(bms, sel.resSym, mss)) + if sel.shapes.add(sh) then + sel.shapeListeners.foreach(listener => listener(sh)) + // symShapes.getOrElseUpdate((bms, sel.resSym), SymShape(bms, sel.resSym)) + // .exit(mss) match + // case NoShape => + // case sh: TermShape => + // if sel.shapes.add(sh) then + // sel.shapeListeners.foreach(listener => listener(sh)) + case N => + sel.isErroneous = true + resolError(sel, msg"${shape.describe.capitalize} does not contain member '${sel.id.name}'" -> shape.toLoc :: Nil) + + def resolveNew(nw: Term.New): Unit = + log(s"resolveNew? res = ${nw.showDbg}") + // listenTerm(nw.cls, shape => newShape(shape, nw.args, nw)) + nw.cls.withoutCaptures match + case trm: NewResolvable => + listen(trm): shape => + def reject = + nw.isErroneous = true + resolError(nw, + msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> trm.toLoc :: Nil) + shape match + case ss: SymShape => + // TODO handle ss.resSym + val bms = ss.sym + bms.onComplete: () => + bms.asCls match + case S(cls) => + trm.resolvedTargets ::= cls + val cd = cls.defn.get + listenExt(cd.ext, extsh => { + val dsh = DefnShape(cd, extsh) + val sh = newShapes.getOrElseUpdate((cls, nw.resSym), + new NewShape(dsh, cls, nw.args, nw): + receiver.unappliedParams.lazyZip(argss).foreach: + case ((ps, mss), args) => + args match + case args: Tup => + zipArgs(mss, ps.params, ps.restParam, args.fields, src, dsh) + case _ => ??? + // TODO: mv to NewShape def + lazy val members: Map[Str, MemberInfo] = + receiver match + case ds: DefnShape => + ds.defn match + case cd: ClassDef => + cd.body.members.mapValues(_ -> ss.markss).toMap + case td: TermDefinition => + td.tsym match + case ccs: ClassCtorSymbol => + ccs.associatedCls.defn.getOrElse(die // TODO + ).body.members.mapValues(_ -> ss.markss).toMap + case _ => + Map.empty + case _ => + Map.empty + case _ => + Map.empty + ) + if nw.shapes.add(sh) then + nw.shapeListeners.foreach(listener => listener(sh)) + }) + case N => + reject + case _ => + reject + case _ => + nw.isErroneous = true + resolError(nw, + msg"Invalid class expression: ${nw.cls.describe}" -> nw.cls.toLoc :: Nil) + + // def symShape(sym: BlockMemberSymbol, res: Ref): Unit = + // val sh = symShapes.getOrElseUpdate(sym, { + // log(s"symShape: sym = $sym, res = $res") + // SymShape(sym) + // }) + // if res.shapes.add(sh) then + // res.shapeListeners.foreach(listener => listener(sh)) + + def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term): DefineVar = + if newResolution then sym match + case sym: TermSymbol => + // symShape(sym, rhs) + // ??? + // sym.defn.get + println(s"TODO: defineVar for TermSymbol ${sym.showDbg}") + case sym: LocalSymbol => + listen(rhs): sh => + assert(isOwnedSym(sym), s"defineVar: sym = ${sym.showDbg}, rhs = ${rhs.showDbg}") + if sym.shapes.add(sh) then + sym.shapeListeners.foreach(listener => listener(sh)) + DefineVar(sym, rhs) + + def listenDefn(sym: TermSymbol, listener: TermShape => Unit): Unit = + sym.defn match + case S(td: TermDefinition) if td.params.isEmpty => + td.body match + case S(body) => + listenTerm(body)(listener) + case N => + ??? // TODO error + case S(d) => + listener(defnShapes.getOrElseUpdate(sym, DefnShape(d, N))) + case N => + sym.defnListeners += (d => listener(defnShapes.getOrElseUpdate(sym, DefnShape(d, N)))) + + def pipeTerm(from: Term, to: ShapeHost): Unit = + log(s"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.shapes}") + listenTerm(from): sh => + if to.shapes.add(sh) then + to.shapeListeners.foreach(listener => listener(sh)) + + def listenExt(ext: Opt[Term], listener: Opt[TermShape] => Unit): Unit = + ext match + case S(trm) => + listenTerm(trm): sh => + listener(S(sh)) + case N => + listener(N) + + def fromBMS(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: TermShape => Unit, trm: Term) = + log(s"listenBMS: bms = ${bms.describe}") + bms.onComplete: () => + log(s"listenedBMS: bms = ${bms.describe}") + bms.asModOrObj orElse bms.asTrm orElse bms.asCls match + case S(sym: (ModuleOrObjectSymbol | TermSymbol | ClassSymbol)) => + val wrappedListener: TermShape => Unit = sh => + log(s"fromBMS: bms = ${bms.showDbg}, sh = ${sh.shwDbg}, flow = ${resSym.showDbg}, markss = ${markss.map(_.showDbg)}") + val sh0 = sh + MarkedShape.exit(sh, sym, S(resSym)).exit(markss) match + // sh.exit(markss) match + // case NoShape => + // case sh: TermShape => + // MarkedShape.exit(sh, sym, S(resSym)) match + case NoShape => + log(s"FILTER OUT ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") + case sh: TermShape => + // if sh is sh0 + if sh0.isInstanceOf[MarkedShape] + then log(s"MATCH ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") + else log(s"PUSH ${sh.shwDbg}") + listener(sh) + sym.defn match + case S(td: TermDefinition) if td.params.isEmpty => + log(s"listenTerm: td.body = ${td.body.fold("N")(_.showDbg)}") + td.body match + case S(body) => + listenTerm(body)(wrappedListener) + case N => + ??? // TODO error + case S(d: TermDefinition) => + d.tsym match + case ccs: ClassCtorSymbol => + val cls = ccs.associatedCls.defn.get + listenExt(cls.ext, extsh => + defnShapes.get(sym).foreach: existing => + ??? // TODO error? + wrappedListener(defnShapes.getOrElseUpdate(sym, DefnShape(d, S(BaseShape(cls, extsh))))) + ) + case _ => + wrappedListener(defnShapes.getOrElseUpdate(sym, DefnShape(d, N))) + case S(d: ClassLikeDef) => + listenExt(d.ext, extsh => + // defnShapes.get(sym).foreach: existing => + // ??? // TODO error? + wrappedListener(defnShapes.getOrElseUpdate(sym, DefnShape(d, extsh))) + ) + case N => + // sym.defnListeners += (d => listener(defnShapes.getOrElseUpdate(sym, DefnShape(d)))) + softAssert(false, s"Symbol definition of ${sym} is not set upon completion of ${bms}") + case _ => + resolError(trm, + msg"Expected a term; got ${bms.describe} '${bms.nme}'" -> N :: Nil, + // msg"expected a term shape, but got ${bms.describe} (${bms.toString})" -> trm.toLoc :: Nil, + ) + + def listenTerm(trm: Term)(listener: TermShape => Unit): Unit = + log(s"listenTerm: trm = ${trm.showDbg}") + trm match + /* + // * Synthetic selections are not really selections from the POV of the resolver. + // * Eg: a plain member reference or plain reference to imported symbol + // * Later, we should make Terms more closely aligned wiht the source and remove SynthSel + case ss: SynthSel => + ss.sym match + case S(ts: TermSymbol) => + ??? + ts.defn.get.body match + case S(body) => + listenTerm(body, listener) + case N => + ??? // TODO error? use sig + case S(bms: BlockMemberSymbol) => + fromBMS(bms, listener, trm) + case N => ??? + */ + case _ => + listen(trm): + case sh: TermShape => + listener(sh) + case ss: SymShape => + fromBMS(ss.sym, ss.resSym, ss.markss, listener, trm) + + def listen(trm: Term, discardMarks: Bool = false)(listener: Shape => Unit): Unit = + log(s"listen: trm = ${trm.showDbg}") + trm.shapeListeners += listener + trm match + // * Synthetic selections are not really selections from the POV of the resolver. + // * Eg: a plain member reference or plain reference to imported symbol + // * Later, we should make Terms more closely aligned wiht the source and remove SynthSel + case ss: SynthSel => + ss.sym match + case S(ts: TermSymbol) => + ??? + ts.defn.get.body match + case S(body) => + listenTerm(body)(listener) + case N => + ??? // TODO error? use sig + case S(bms: BlockMemberSymbol) => + // TODO: add mark + fromBMS(bms, ss.resSym, Nil//TODO? + , listener, trm) + case N => ??? + case intro: IntroTerm => + val sh = introShapes.getOrElseUpdate(intro, { + log(s"introShape: intro = $intro") + IntroShape(intro) + }) + listener(sh) + case ref @ Ref(loc: LocalSymbol) => + loc.shapes.foreach(listener) + loc.shapeListeners += listener + case ref @ SimpleRef(sym) => + sym match + case loc: LocalSymbol => + loc.shapes.foreach(listener) + loc.shapeListeners += listener + case ref @ MemberRef(sym: TermSymbol) => + ??? + // listenDefn(sym, sh => + // listener(MarkedShape.enter(sh, sym, S(ref.resSym)))) + case ref @ MemberRef(sym: BlockMemberSymbol) => + val fs = ref.resSym + val sh = symShapes.getOrElseUpdate((sym, fs, Nil), SymShape(sym, fs, Nil)) + listener(sh) + case Capture(base, thru) => + if discardMarks then + listen(base)(listener) + else listenTerm(base): sh => + listener(MarkedShape.enter(sh, thru, N)) + case ref @ Ref(sym: InnerSymbol) => // TODO: remove remaining occurrences of such refs + sym.shapeListeners += listener + case ref @ Ref(bsym: BlockMemberSymbol) => + ??? + case res: ResolvableImpl => + res.shapes.foreach(listener) + // ??? + case sh: ShapeHost => + sh.shapes.foreach(listener) + sh.shapeListeners += listener + case Blk(sts, rs) => + listen(rs)(listener) + // case u: UnitVal => + case Missing => + () // FIXME: Currently get this from light-elaborated Predef import + case _ => + println(s"TODO: listen for ${trm.describe} (${trm.getClass})") + () + +end NewResolver + + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index 4fc5e5b055..df8951a47c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -7,6 +7,8 @@ import syntax.{Keyword, SpreadKind, Tree}, Tree.{Ident, StrLit} import Elaborator.State, Message.MessageContext, ucs.error import scala.annotation.tailrec, util.chaining.* import utils.{TraceLogger, tl} +import hkmc2.document.* +import hkmc2.document.Document.* object Pattern: /** The reason why a variable obtained from `Pattern.variables` is invalid. */ @@ -145,9 +147,13 @@ object Pattern: /** A shorthand for creating a variable pattern. */ def Variable = Pattern.Wildcard() binds (_: Ident) - trait ConstructorImpl: + trait ConstructorImpl extends PatternShapeHost: self: Pattern.Constructor => + var isErroneous: Bool = false + // val resSym: FlowSymbol = FlowSymbol.app + var resolvedSym: Opt[DefinitionSymbol[?]] = N + /** Get the resolved symbol of the target term. */ def symbol: Opt[Symbol] = self.target.resolvedSym @@ -171,7 +177,7 @@ object Pattern: import Pattern.*, InvalidReason.* /** An inductive data type for patterns. */ -enum Pattern extends AutoLocated: +enum Pattern extends AutoLocated, Describable, PatternShapePublisher: /** A pattern that matches a constructor and its arguments. * @param target The term representing the constructor. * @param arguments `None` if the pattern does not have a parameter list. The @@ -250,6 +256,12 @@ enum Pattern extends AutoLocated: * `and` in split. */ case Guarded(pattern: Pattern, guard: Term) + + // val trmHost: ShapeHost = new ShapeHost{} + object trmHost extends ShapeHost: + def showDbg(using DebugPrinter): Str = s"trmHost(${Pattern.this.showDbg})" + end trmHost + infix def binds(id: Ident): Pattern.Alias = Pattern.Alias(this, id) /** Annotate the pattern using the given term. If the term is `Error`, then @@ -302,6 +314,37 @@ enum Pattern extends AutoLocated: case p: Pattern => p.varNamesUsedInGuards .foldLeft(Set.empty[Str])(_ ++ _) + def show(using Scope, ShowCfg, Raise): Document = this match + // case Constructor(target, arguments) => + // target.show + arguments.fold(""): + // args => s"(${args.map(_.show).mkString(", ")})" + case Composition(true, left, right) => doc"${left.show} | ${right.show}" + case Composition(false, left, right) => doc"${left.show} & ${right.show}" + case Negation(pattern) => doc"not ${pattern.show}" + case Wildcard() => "_" + case Literal(literal) => literal.idStr + case Range(lower, upper, rightInclusive) => + doc"${lower.idStr} ${if rightInclusive then "to" else "until"} ${upper.idStr}" + case Concatenation(left, right) => doc"${left.show} ~ ${right.show}" + case Tuple(leading, spread) => + (leading.iterator.map(_.show) ++ spread.fold(Iterator.empty): + case (_, middle, trailing) => + Iterator.single(middle.show) ++ trailing.iterator.map(_.show) + ).toList.mkDocument("[", ", ", "]") + case Record(fields) => doc"{${fields.map((k, v) => doc"${k.name}: ${v.show}").mkString(", ")}}" + case Chain(first, second) => doc"${first.show} >> ${second.show}" + // case al @ Alias(pattern, alias) => doc"${pattern.show} as ${alias.name}‹${al.symbolOption.fold("")(_.showName)}›" + case al @ Alias(pattern, alias) => doc"${pattern.show} as ${al.symbolOption.fold(alias.name+"ˀˀˀ")(_.showName)}" + case Transform(pattern, _, transform) => doc"${pattern.show} => ${transform.show}" + case Annotated(pattern, annotations) => annotations.iterator.map: + case L(errorLoc) => "error" + case R(term) => term.show + .mkString("@", " @", " ") + pattern.show + case Guarded(pattern, guard) => pattern.show + " where " + guard.show + case Constructor(target, arguments) => + target.show :: arguments.fold(doc""): + args => doc"(${args.map(_.show).mkDocument(", ")})" + def children: Vector[Located] = this match case Constructor(target, arguments) => target +: arguments.fold(Vector.empty)(_.toVector) case Composition(polarity, left, right) => Vector.double(left, right) @@ -382,9 +425,9 @@ enum Pattern extends AutoLocated: Iterator.single(spreadKind.str + middle.showDbg) ++ trailing.iterator.map(_.showDbg)).mkString("[", ", ", "]") case Record(fields) => s"{${fields.map((k, v) => s"${k.name}: ${v.showDbg}").mkString(", ")}}" - case Chain(first, second) => s"${first.showDbgWithPar} as ${second.showDbgWithPar}" - case Alias(Wildcard(), alias) => alias.name - case Alias(pattern, alias) => s"${pattern.showDbgWithPar} as ${alias.name}" + case Chain(first, second) => s"${first.showDbgWithPar} >> ${second.showDbgWithPar}" + case al @ Alias(Wildcard(), alias) => s"${alias.name}‹${al.symbolOption.fold("?")(_.showDbg)}›" + case al @ Alias(pattern, alias) => s"${pattern.showDbgWithPar} as ${alias.name}‹${al.symbolOption.fold("")(_.showDbg)}›" case Transform(pattern, _, transform) => s"${pattern.showDbgWithPar} => ${transform.showDbg}" case Annotated(pattern, annotations) => annotations.iterator.map: case L(errorLoc) => "error" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala new file mode 100644 index 0000000000..a6319c3df9 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala @@ -0,0 +1,45 @@ +package hkmc2 +package semantics + +import scala.collection.mutable.{Buffer, Set as MutSet, LinkedHashSet} + +import hkmc2.utils.*, shorthands.* +import syntax.* +import hkmc2.utils.Scope +import hkmc2.utils.Scope.scope +import hkmc2.document.* +import hkmc2.document.Document.* + +import Elaborator.State +import hkmc2.typing.Type +import hkmc2.semantics.Elaborator.{Ctx, ctx} +import hkmc2.Message.MessageContext +import hkmc2.semantics.flow.{SelectionTarget, AppTarget} + + + +type PatternShapePublisher = Publisher[PatternShape] +type PatternShapeHost = Host[PatternShape] + + +sealed abstract class PatternShape: + def showDbg(using DebugPrinter): Str + + +// TODO: not a case class...? +case class CtorPatternShape( + cls: ClassDef, + fs: Ls[BlockMemberSymbol -> Pattern], + src: Pattern.Constructor, + resSym: FlowSymbol, +) extends PatternShape: + def showDbg(using DebugPrinter): Str = src.showDbg + + +case class AliasPatternShape( + sym: VarSymbol, + src: Pattern.Alias, +) extends PatternShape: + def showDbg(using DebugPrinter): Str = src.showDbg + + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala new file mode 100644 index 0000000000..5d1b332223 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -0,0 +1,356 @@ +package hkmc2 +package semantics + +import hkmc2.utils.*, shorthands.* +import syntax.* +import hkmc2.document.* +import hkmc2.document.Document.* + + +sealed trait Shape extends ShapeLike: + def describe: Str + def shwDbg(using DebugPrinter): Str = this match + // case ds: DefnShape => s"DefnShape(${ds.defn.describe} ${ds.defn.sym.showDbg})" + case ds: DefnShape => ds.defn.sym.showDbg + case as: AppShape => s"AppShape(${as.receiver.shwDbg}, ${as.args.showDbg})" + case ms: MarkedShape => s"MarkedShape(${ms.sh.shwDbg}, ${ms.mark.showDbg})" + case ns: SymShape => s"SymShape(${ns.sym.showDbg})" + case ns: NewShape => s"NewShape(${ns.cls.showDbg}, ${ns.argss.map(_.showDbg).mkString(", ")})" + case is: IntroShape => s"IntroShape(${is.trm.showDbg})" + +sealed trait NonMarkedShape extends TermShape +sealed trait NonAppTermShape extends NonMarkedShape + +// type Mark = Opt[AnyDefinitionSymbol] -> Bool +// case class Mark(sym: Opt[AnyDefinitionSymbol], entry: Bool) +// sealed abstract class Marks +// case class MoreMarks(sym: Opt[AnyDefinitionSymbol], entry: Bool, rest: Marks) extends Marks +// case object NoMark extends Marks +sealed abstract class Marks: + def showDbg(using DebugPrinter): Str = this match + case EntryMark(sym, id, rest) => s"↘⟨${sym.showDbg}⟩${id.fold("")("%⟨"+_.showDbg+"⟩")}${rest.showDbg}" + case ExitMark(sym, id, rest) => s"↗⟨${sym.showDbg}⟩${id.fold("")("%⟨"+_.showDbg+"⟩")}${rest.showDbg}" + case NoMarks => "ϵ" +type SomeMarks = EntryMark | ExitMark +case class EntryMark(sym: AnyDefinitionSymbol, id: Opt[FlowSymbol], rest: Marks) extends Marks +sealed abstract class ExitMarks extends Marks +case class ExitMark(sym: AnyDefinitionSymbol, id: Opt[FlowSymbol], rest: ExitMarks) extends ExitMarks +case object NoMarks extends ExitMarks + +sealed trait ShapeLike: + def exit(revMarkss: Ls[Marks])(using TL): TermShape | NoShape + def exit(marks: Marks)(using TL): TermShape | NoShape + def enter(revMarkss: Ls[Marks])(using TL): TermShape | NoShape + def enter(marks: Marks)(using TL): TermShape | NoShape + def shwDbg(using DebugPrinter): Str + +case object NoShape extends ShapeLike: + def exit(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = NoShape + def exit(marks: Marks)(using TL): TermShape | NoShape = NoShape + def enter(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = NoShape + def enter(marks: Marks)(using TL): TermShape | NoShape = NoShape + def shwDbg(using DebugPrinter): Str = toString + +type NoShape = NoShape.type + +object Marked: + def unapply(sh: TermShape): S[(NonMarkedShape, Marks)] = + sh match + case sh: NonMarkedShape => S((sh, NoMarks)) + case MarkedShape(sh, mark) => S((sh, mark)) +end Marked + +case class MarkedShape(sh: NonMarkedShape, mark: SomeMarks) extends TermShape: + lazy val members: Map[Str, MemberInfo] = + // sh.members.view.mapValues(m => MarkedShape.exit(m, mark)).toMap + // sh.members // FIXME: add marks to tuple result + sh.members.mapValues(_.mapSecond(_ ::: mark :: Nil)).toMap + // sh.members.mapValues(_.mapSecond(mark :: _)).toMap + def describe: Str = sh.describe + def toLoc: Opt[Loc] = sh.toLoc +object MarkedShape: + def enter(sh: TermShape, sym: AnyDefinitionSymbol, id: Opt[FlowSymbol])(using TL): MarkedShape = + sh match + case sh: NonMarkedShape => MarkedShape(sh, EntryMark(sym, id, NoMarks)) + case MarkedShape(sh, marks) => + // marks match + // case marks: EntryMark => MarkedShape(sh, EntryMark(sym, id, marks)) + // case ExitMark(sym2, id2, rest) => + // require(sym2 is sym) + // id2 match + // case S(`id`) => MarkedShape(sh, marks) + // if id2 is id then + // rest match + // case NoMarks => sh + // case rest: ExitMark => MarkedShape(sh, rest) + // else + // MarkedShape(sh, EntryMark(sym, id, marks)) + // // MarkedShape(sh, EntryMark(sym, marks)) + // ??? + MarkedShape(sh, EntryMark(sym, id, marks)) + def exit(sh: TermShape, sym: AnyDefinitionSymbol, id: Opt[FlowSymbol])(using TL): TermShape | NoShape = + tl.trace[TermShape | NoShape](s".exit MarkedShape (${sh.shwDbg}, ${sym.showDbg}, ${id.fold("")(_.showDbg)})", res => s"= ${res.shwDbg}"): + tl.log(s"${sym.getClass} ${sym match + case sym: TermSymbol => sym.owner.map(_.showDbg) + case _ => "?" + }") + sh match + case sh: NonMarkedShape => MarkedShape(sh, ExitMark(sym, id, NoMarks)) + case MarkedShape(sh, marks) => + marks match + case marks: ExitMark => MarkedShape(sh, ExitMark(sym, id, marks)) + case EntryMark(sym2, id2, rest) => + require(sym2 is sym, s"Expected symbol ${sym.showDbg} but got ${sym2.showDbg}") + // tl.log(s"!? ${id} vs ${id2}") + // // id2 match + // (id,id2) match + // // case S(`id`) | N => + // // case `id` | N => + // case (N, _) | (_, N) | (S(_), S(_)) if id === id2 => + // rest match + // case NoMarks => sh + // case rest: SomeMarks => MarkedShape(sh, rest) + // // case S(id2) => + // case _ => + // // MarkedShape(sh, ExitMark(sym, id, marks)) + // NoShape + // // MarkedShape(sh, EntryMark(sym, marks)) + // // ??? + if id.forall(i1 => id2.forall(i2 => i1 is i2)) then + rest match + case NoMarks => sh + case rest: SomeMarks => MarkedShape(sh, rest) + else NoShape +end MarkedShape + +sealed trait TermShape extends Shape: + def members: Map[Str, MemberInfo] + + def extendsCls(cls: ClassLikeDef): Bool = false + + lazy val applicationHead: (NonAppTermShape, Ls[Marks]) = this match + // case ds: DefnShape => ds + // case as: AppShape => as.receiver.applicationHead + case as: AppShape => as.receiver.applicationHead + case ns: NewShape => ns.receiver.applicationHead + case na: NonAppTermShape => (na, Nil) + case MarkedShape(sh, mark) => sh.applicationHead.mapSecond(mark :: _) + lazy val unappliedParams: Ls[(ParamList, Ls[Marks])] = this match + case ds: DefnShape => ds.defn match + case defn: TermDefinition => defn.params.map(_ -> Nil) + case defn: ClassDef => + // println(defn.ctorSym) + // if defn.ctorSym.isDefined then + if defn.paramsOpt.isDefined then // whether the class can receive direct applications + (defn.paramsOpt.toList ::: defn.auxParams).map(_ -> Nil) + else Nil + case _ => Nil + case as: AppShape => as.receiver.unappliedParams.drop(1) + case ns: NewShape => ns.receiver.unappliedParams.drop(ns.argss.length) + case MarkedShape(sh, mark) => sh.unappliedParams.map(p => p._1 -> (mark :: p._2)) + case is: IntroShape => + is.trm match + case Term.Lam(params, body) => (params -> Nil) :: + // body.unappliedParams + Nil + case _ => Nil + case _ => Nil + + def exit(marks: Marks)(using TL): TermShape | NoShape = + tl.trace[TermShape | NoShape](s".exit $shwDbg (${marks.showDbg})", res => s"= ${res.shwDbg}"): + marks match + case NoMarks => this + // case EntryMark(sym, id, rest) => + // MarkedShape.enter(this, sym, id).exit(rest) + // case ExitMark(sym, id, rest) => + // MarkedShape.exit(this, sym, id).exit(rest) + case EntryMark(sym, id, rest) => + exit(rest) match + case NoShape => NoShape + case ex: TermShape => MarkedShape.enter(ex, sym, id) + case ExitMark(sym, id, rest) => + exit(rest) match + case NoShape => NoShape + case ex: TermShape => MarkedShape.exit(ex, sym, id) + + def exit(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = revMarkss match + case Nil => this + case mark :: rest => + exit(mark).exit(rest) + + def enter(marks: Marks)(using tl: TL): TermShape | NoShape = + tl.trace[TermShape | NoShape](s".enter $shwDbg (${marks.showDbg})", res => s"= ${res.shwDbg}"): + marks match + case NoMarks => this + case EntryMark(sym, id, rest) => + // MarkedShape.enter(this, sym, id).enter(rest) + // ??? + // MarkedShape.exit(this, sym, id) match + // case NoShape => this // TODO: should this be an error? + // case sh: TermShape => sh.enter(rest) + MarkedShape.exit(this, sym, id).enter(rest) + case ExitMark(sym, id, rest) => + // MarkedShape.exit(this, sym, id) match + // case NoShape => this // TODO: should this be an error? + // case sh => sh.enter(rest) + MarkedShape.enter(this, sym, id).enter(rest) + // marks match + // case NoMarks => this + // case marks: SomeMarks => + // this match + // case sh: NonMarkedShape => MarkedShape(sh, marks) + // case MarkedShape(sh, mark) => + // ??? + + def enter(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = revMarkss match + case Nil => this + case mark :: rest => + enter(mark).enter(rest) + + def isSaturated: Bool = unappliedParams.isEmpty + def toLoc: Opt[Loc] + +end TermShape + +// sealed trait TermShape: +// def describe: Str = this match +// case app: AppShape => s"application of ${app.lhs.describe} to ${app.args.describe}" +// case sel: SelShape => s"selection of ${sel.nme.name} from ${sel.receiver.describe}" +// case sym: SymShape => s"symbol ${sym.sym.describe}" + +type MemberInfo = (BlockMemberSymbol, Ls[Marks]) + +class ErrShape(val err: ErrorReport) extends NonAppTermShape: + def describe: Str = s"error: ${err.mainMsg}" + def members: Map[Str, MemberInfo] = Map.empty + def toLoc: Opt[Loc] = N + +class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using DebugPrinter) extends NonMarkedShape: + lazy val members: Map[Str, MemberInfo] = + // An unsaturated term definition is just a concrete function shape + if !isSaturated then Map.empty + else + applicationHead match + case (ds: DefnShape, mss) => + // TODO: handle `mss` + + // ds.members + // ds.defn match + // case td: TermDefinition => td.sym match + // case cs: ClassCtorSymbol => + // val cd = cs.associatedCls.defn.get + // cd.ext.fold(Map.empty)(_.members) ++ cd.body.members + // case _ => ??? // TODO: add softRequire on ction – should not be possible + // case cd: ClassDef => cd.ext.fold(Map.empty)(_.members) ++ cd.body.members + ds.defn match + case cd: ClassDef => ds.ext.fold(Map.empty)(_.members).mapValues(_.mapSecond(_ ::: mss)).toMap ++ + cd.body.members.mapValues(_ -> mss).toMap + case td: TermDefinition => + ds.ext.fold(Map.empty)(_.members).mapValues(_.mapSecond(_ ::: mss)).toMap + case _ => Map.empty + def describe: Str = + // s"application of ${receiver.describe}" + s"instance of ${applicationHead._1.describe}" + def toLoc: Opt[Loc] = src.toLoc + override def toString: String = s"AppShape($receiver, ${args.showDbg})" + // def target: Opt[AppTarget] + +abstract class NewShape(val receiver: TermShape, val cls: ClassLikeSymbol, val argss: Ls[Term], val src: Term.New)(using DebugPrinter) extends NonMarkedShape: + def describe: Str = + // s"instantiation of ${receiver.describe}" + s"instance of ${cls.defn.get.describeRef}" + override def toString: String = s"NewNewShape(${cls.showDbg}, $argss)" + def toLoc: Opt[Loc] = src.toLoc + +class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: Ls[Marks]) extends Shape: + def describe: Str = s"${sym.describe} symbol '${sym.nme}'" + override def toString: String = s"SymShape($sym)" + def exit(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = ??? + def exit(marks: Marks)(using TL): TermShape | NoShape = ??? + def enter(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = ??? + def enter(marks: Marks)(using TL): TermShape | NoShape = ??? + +/* +class ThisShape(val defn: Definition) extends NonAppTermShape: + def describe: Str = s"Self-reference to ${defn.bsym.describe} '${defn.bsym.nme}'" + override def toString: String = s"ThisShape(${defn.describe})" + def members: Map[Str, MemberInfo] = ??? +*/ + +// TODO: make it not a TermShape? +class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppTermShape: + def describe: Str = s"${defn.describe}" + lazy val members: Map[Str, MemberInfo] = + ext.fold(Map.empty)(_.members) ++ defn.body.members.mapValues(_ -> Nil).toMap + def toLoc: Opt[Loc] = defn.toLoc + +class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTermShape: + override def extendsCls(cls: ClassLikeDef): Bool = + clsDef.contains(cls) || ext.exists(_.extendsCls(cls)) + lazy val clsDef = defn match + case defn: ClassLikeDef => S(defn) + case defn: TermDefinition => + defn.tsym match + case ts: ClassCtorSymbol => S(ts.associatedCls.defn.get) + case _ => N + case _ => N + // def describe: Str = s"${defn.describe}" + def describe: Str = + // s"${defn.bsym.describe} '${defn.bsym.nme}'" + // s"${defn.describe}" + // s"${defn.bsym.asCls.fold("")(_.defn.get.kind.desc+" ")}'${defn.bsym.nme}'" + s"${defn match + case defn: TypeLikeDef => defn.kind.desc + " " + case defn: TermDefinition => + defn.tsym match + case _: ClassCtorSymbol => "class constructor " + case s => s.k.desc + " " + case _ => "" + }'${defn.bsym.nme}'" + // override def toString: String = s"DefnShape(${defn.describe} ${defn.bsym.nme})" + override def toString: String = s"DefnShape(${defn.describe})" + lazy val members: Map[Str, MemberInfo] = + // println((ext, ext.map(_.members))) + // ext.fold(Map.empty)(_.members) ++ defn.match + defn match + case defn: ModuleOrObjectDef => + // println(defn.ext.map(_.cls.members)) + ext.fold(Map.empty)(_.members) ++ defn.body.members.mapValues(_ -> Nil).toMap + case defn: TermDefinition => ??? + case _ => Map.empty + def toLoc: Opt[Loc] = defn.sym.toLoc + +/* +class RefinedShape(val base: TermShape, val refinements: Ls[Str -> Term]) extends NonAppTermShape: + def describe: Str = base.describe + lazy val members: Map[Str, MemberInfo] = + base.members ++ refinements.iterator.map: (nme, trm) => + // nme -> BlockMemberSymbol(nme, trm) + ??? +*/ +type IntroTerm = Term.Lit | Term.UnitVal | Term.Tup | Term.Lam | Term.Rcd //| Term.New +class IntroShape(val trm: IntroTerm) extends NonAppTermShape: + def describe: Str = trm.describe + lazy val members: Map[Str, MemberInfo] = trm match + case tup: Term.Tup => + // tup.fields.iterator.map: + ??? + case lam: Term.Lam => Map.empty // TODO: methods on lambdas + case rcd: Term.Rcd => + // rcd.stats.iterator.collect: + // TODO: handler RcdField, RcdSpread + ??? + // case newTerm: Term.New => + // Map.empty // TODO + def toLoc: Opt[Loc] = trm.toLoc + override def toString: Str = s"IntroShape(${trm})" + +sealed trait LitShape extends NonAppTermShape: + self: Term.Lit => + def members: Map[Str, MemberInfo] = Map.empty // TODO: methods on literals, e.g. string methods + + +type ShapePublisher = Publisher[Shape] +type ShapeHost = Host[Shape] + + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala index b2ee9e73f2..df4327fc98 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala @@ -3,6 +3,8 @@ package semantics import hkmc2.utils.*, shorthands.*, syntax.*, Tree.{BoolLit, Keywrd} import Keyword.{`do`, `else`, `then`}, utils.TL, Elaborator.{Ctx, State} +import hkmc2.document.* +import hkmc2.document.Document.* /** * Similar to `Split` but contains nested patterns (i.e., class `Pattern`). @@ -29,6 +31,20 @@ enum SimpleSplit extends AutoLocated with ProductWithTail: case els: Else => els case End => this + def showSub(using Scope, ShowCfg, Raise, IfLikeForm): Document = this match + // doc"${scrutinee.show} is ${pattern.show} and { #{ # ${consequent.show} #} # }" + case Cons(head, End) => doc"${summon[IfLikeForm].midStr} ${head.show}" + case Cons(head, tail) => doc" and { #{ # ${head.show}; # ${tail.show} #} # }" + // case Else(default) => doc" else ${default.show}" + case Else(default) => doc" ${summon[IfLikeForm].midStr} ${default.show}" + case End => "‹unhandled›" + def show(using Scope, ShowCfg, Raise, IfLikeForm): Document = this match + case Cons(head, End) => doc"${head.show}" + case Cons(head, tail) => doc"${head.show}; # ${tail.show}" + // case Else(default) => doc"${summon[IfLikeForm].midStr} ${default.show}" + case Else(default) => doc"else ${default.show}" + case End => "" + protected def children: Vector[Located] = this match case Cons(branch, tail) => Vector.double(branch, tail) case els @ Else(default) => els.kw match @@ -75,7 +91,7 @@ enum SimpleSplit extends AutoLocated with ProductWithTail: private var _expandedSplit: Opt[Split] = N /** */ - def getExpandedSplit(using TL, Ctx, State, Raise): Split = _expandedSplit.getOrElse: + def getExpandedSplit(using TL, Ctx, State, Raise, Config): Split = _expandedSplit.getOrElse: val split = Split.from(this) _expandedSplit = S(split) split @@ -110,6 +126,12 @@ object SimpleSplit: case Match(scrutinee: Term.Ref, pattern: Pattern, consequent: SimpleSplit) case Let(binding: LocalVarSymbol, term: Term) + def show(using Scope, ShowCfg, Raise, IfLikeForm): Document = this match + case Match(scrutinee, pattern, consequent) => + // doc"${scrutinee.show} is ${pattern.show} and { #{ # ${consequent.show} #} # }" + doc"${scrutinee.show} is ${pattern.show}${consequent.showSub}" + case Let(binding, term) => doc"let ${binding.showName} = ${term.show}" + def subTerms: Vector[Term] = this match case Match(scrutinee, pattern, consequent) => scrutinee +: (pattern.subTerms ++ consequent.subTerms) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 3c2c3bc4ec..6d95c45df2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -2,7 +2,7 @@ package hkmc2 package semantics import scala.collection.mutable -import scala.collection.mutable.{Set => MutSet} +import scala.collection.mutable.{Buffer, LinkedHashSet} import hkmc2.utils.*, shorthands.* import syntax.* @@ -186,14 +186,25 @@ object FlowSymbol: // FlowSymbol("‹app-res›") // FlowSymbol("@") FlowSymbol("app") - + + def pat()(using State) = + FlowSymbol("pat") + + def neww()(using State) = + FlowSymbol("new") + def sel(nme: Str)(using State) = FlowSymbol(s"⋅$nme") def synthSel(nme: Str)(using State) = FlowSymbol(s"(⋅)$nme") def selProj(nme: Str)(using State) = FlowSymbol(s"#⋅$nme") - + + // def memSym(sym: MemberSymbol, nme: Str)(using State) = + // FlowSymbol(s"$nme(${sym.nme})") + def memSym(sym: MemberSymbol)(using State) = + FlowSymbol(sym.nme) + def lds(nme: Str)(using State) = FlowSymbol(s"Ɛ⋅$nme") @@ -203,7 +214,13 @@ class ConcreteFlowSymbol(label: Str)(using State) extends FlowSymbol(label): def subst(using s: SymbolSubst): FlowSymbol = s.mapFlowSym(this) -sealed trait LocalSymbol extends Symbol +// sealed trait LocalSymbol extends Symbol, ShapePublisher: +// /** Shapes are published in discovery order. Resolution replays this collection to late +// * listeners, so an unordered set would make ambiguous-target diagnostics depend on hash and +// * parallel compilation timing. */ +// private[semantics] val shapes: LinkedHashSet[Shape] = LinkedHashSet.empty +sealed trait LocalSymbol extends Symbol, ShapeHost + sealed trait NamedSymbol extends Symbol: def name: Str def id: Ident @@ -301,6 +318,17 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[TypeOrTermDef], val nameIsMe var tsym: Opt[TermSymbol] = N var sourceAliases: Ls[Str] = Nil + private var defnListeners: Buffer[() => Unit] = Buffer.empty + def complete(): Unit = if defnListeners isnt null then + defnListeners.foreach(_()) + defnListeners = null // free memory and prevent further listening + /** Called when all the symbols covered by this BMS are present, with their definitions set. */ + def onComplete(f: () => Unit): Unit = + if defnListeners is null then + f() + else + defnListeners += f + def toLoc: Option[Loc] = Loc(trees) def symbols = tsym.toList ::: trees.collect: @@ -458,9 +486,15 @@ sealed trait DefinitionSymbol[Defn <: Definition] extends MemberSymbol: def defn_=(d: S[Defn]): Unit = require(_defn.isEmpty, s"Cannot reassign defn of ${this} from ${_defn} to ${d}") _defn = d + defnListeners.foreach(_(d.value)) + defnListeners = null // free memory and prevent further listening + var decl: Opt[Declaration] = N // NOTE: currently only assigned for class params and only used by deforestation; may want to just remove it once deforestation is improved def bms: Opt[BlockMemberSymbol] = defn.map(_.bsym) + // TODO: rm + private[semantics] var defnListeners: Buffer[Defn => Unit] = Buffer.empty + // * Although the IR is immutable, // * we consider that a given symbol is *owned* by the IR Defn node that defines it. var irDefn: Opt[codegen.Defn] = N @@ -498,7 +532,7 @@ end DefinitionSymbol * One overloaded `BlockMemberSymbol` may correspond to multiple `InnerSymbol`s * A `Ref(_: InnerSymbol)` represents a `this`-like reference to the current object. */ // TODO prevent from appearing in Ref -sealed trait InnerSymbol(using State) extends Symbol: +sealed trait InnerSymbol(using State) extends Symbol, ShapePublisher: // Ideally, InnerSymbol should extend DefinitionSymbol, but that requires us to specify the type // parameter to all occurrences of InnerSymbol. So, we use a self-type annotation instead to // ensure that any implementation of InnerSymbol is also a DefinitionSymbol. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index ca261329e7..81923efa15 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -1,7 +1,7 @@ package hkmc2 package semantics -import scala.collection.mutable.{Buffer, Set as MutSet} +import scala.collection.mutable.{Buffer, Set as MutSet, LinkedHashSet} import hkmc2.utils.*, shorthands.* import syntax.* @@ -14,6 +14,7 @@ import Elaborator.State import hkmc2.typing.Type import hkmc2.semantics.Elaborator.{Ctx, ctx} import hkmc2.Message.MessageContext +import hkmc2.semantics.flow.{SelectionTarget, AppTarget} final case class QuantVar(sym: VarSymbol, ub: Opt[Term], lb: Opt[Term]) @@ -114,7 +115,10 @@ sealed trait AnySel extends ResolvableImpl: val originalCtx: Opt[SrcScope] var resolvedTargets: Ls[flow.SelectionTarget] = Nil // * filled during flow analysis - var isErroneous: Bool = false // * to avoid reporting follow-on errors after a flow/resolution error + def validResolvedTargets = resolvedTargets.filterNot: + case flow.SelectionTarget.Err(_) => true + case _ => false + // var isErroneous: Bool = false // * to avoid reporting follow-on errors after a flow/resolution error end AnySel object AnySel: @@ -124,13 +128,25 @@ object AnySel: case Term.SelProj(lhs, cls, proj) => S((lhs, proj, S(cls))) end AnySel + +sealed trait AppImpl extends ResolvableImpl: + self: Term.App => + var resolvedTargets: Ls[flow.AppTarget] = Nil // * filled during flow analysis +end AppImpl + + type Resolvable = Term & ResolvableImpl -sealed trait ResolvableImpl: +sealed trait ResolvableImpl extends ShapeHost: this: Term => import Resolvable.CallableDefinition + var isErroneous: Bool = false // * to avoid reporting follow-on errors after a flow/resolution error + + // private[semantics] val shapes: MutSet[Shape] = MutSet.empty + def getShapes: Ls[Shape] = shapes.toList + /** * The expanded form of the term, if it exists. * @@ -151,7 +167,7 @@ sealed trait ResolvableImpl: case t: Term.SynthSel => t.copy()(t.sym, t.resSym, t.typ, t.originalCtx) case t: Term.LeadingDotSel => t.copy()(t.originalCtx) case t: Term.SelProj => t.copy()(t.sym, t.resSym, t.typ, t.originalCtx) - case t: Term.New => t.copy()(t.typ) + case t: Term.New => t.copy()(t.resSym, t.typ) .withLocOf(this) .asInstanceOf @@ -174,7 +190,7 @@ sealed trait ResolvableImpl: case t: Term.SynthSel => t.copy()(t.sym, t.resSym, S(typ), t.originalCtx) case _: Term.LeadingDotSel => lastWords(s"Cannot attach a type to leading dot selection: ${this.showDbg}") case t: Term.SelProj => t.copy()(t.sym, t.resSym, S(typ), t.originalCtx) - case t: Term.New => t.copy()(S(typ)) + case t: Term.New => t.copy()(t.resSym, S(typ)) .withLocOf(this) .asInstanceOf @@ -325,21 +341,47 @@ case class SrcScope(outer: Elaborator.OuterCtx, parent: Opt[SrcScope]): object SrcScope: given s: Ctx => SrcScope = summon[Ctx].scope -enum Term extends Statement: + +type AnyRef_ = AnyRefImpl & Term + +sealed trait AnyRefImpl: + self: Term.Ref | Term.SimpleRef | Term.MemberRef | Term.SelfRef => + def tree: Tree.Ident + // val refNum: Int + // val typ: Opt[Type] + // val sym: Symbol + // val resSym: FlowSymbol + def sym: Symbol + +sealed trait NewRefImpl extends AnyRefImpl: + self: Term.SimpleRef | Term.MemberRef | Term.SelfRef => + // def tree: Tree.Ident = Tree.Dummy + def refNum: Int = 0 // TODO + +sealed trait NewSelImpl extends NewResolvableImpl: + self: Term.NewSel => + var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution + + +enum Term extends Statement, ShapePublisher: case Error() case UnitVal() case Missing // Placeholder terms that were not elaborated due to the "lightweight" elaboration mode `Mode.Light` - case Lit(lit: Literal) + case Lit(lit: Literal) extends Term + + // --- NEW --- + case SimpleRef(sym: codegen.SimpleSymbol)(val tree: Tree.Ident) extends Term, NewRefImpl + case SelfRef(sym: InnerSymbol)(val tree: Tree.Ident) extends Term, NewRefImpl + case MemberRef(sym: MemberSymbol)(val tree: Tree.Ident, val resSym: FlowSymbol) extends Term, NewResolvableImpl, NewRefImpl + case NewSel(prefix: Term, id: Tree.Ident)(val resSym: FlowSymbol) extends Term, NewSelImpl, ShapeHost + case Capture(base: Term, thru: AnyDefinitionSymbol) extends Term + // --- LEGACY --- /** A term that wraps another term, indicating that the symbol of the inner term is resolved. * This is mainly used to disambiguate overloaded definitions. */ - case Resolved(t: Term, sym: DefinitionSymbol[?]) + case Resolved(t: Term, sym: AnyDefinitionSymbol) (val typ: Opt[Type]) extends Term, ResolvableImpl case Ref(sym: Symbol) - (val tree: Tree.Ident, val typ: Opt[Type]) extends Term, ResolvableImpl - case App(lhs: Term, rhs: Term) - (val tree: Tree.App, val typ: Opt[Type], val resSym: FlowSymbol) extends Term, ResolvableImpl - case TyApp(lhs: Term, targs: Ls[Term]) - (val typ: Opt[Type]) extends Term, ResolvableImpl + (val tree: Tree.Ident, val typ: Opt[Type]) extends Term, ResolvableImpl, AnyRefImpl case Sel(prefix: Term, nme: Tree.Ident) (val sym: Opt[MemberSymbol], val resSym: FlowSymbol, val typ: Opt[Type], val originalCtx: Opt[SrcScope]) extends Term, AnySel @@ -349,11 +391,17 @@ enum Term extends Statement: case SelProj(prefix: Term, cls: Term, nme: Tree.Ident) (val sym: Opt[MemberSymbol], val resSym: FlowSymbol, val typ: Opt[Type], val originalCtx: Opt[SrcScope]) extends Term, AnySel + // --- END --- + + case App(lhs: Term, rhs: Term) + (val tree: Tree.App, val typ: Opt[Type], val resSym: FlowSymbol) extends Term, AppImpl + case TyApp(lhs: Term, targs: Ls[Term]) + (val typ: Opt[Type]) extends Term, ResolvableImpl case DynSel(prefix: Term, fld: Term, arrayIdx: Bool) case Tup(fields: Ls[Elem])(val tree: Tree.Tup) case Mut(underlying: Tup | Rcd | New | DynNew) case CtxTup(fields: Ls[Elem])(val tree: Tree.Tup) - case IfLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit) + case IfLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit) extends Term, ShapeHost /** `If` expressions synthesized by the pattern compiler. It should only be * created and used in `Lowering`. One must make sure that all terms in the * split are correctly resolved. In the future, we might look for a way to @@ -375,8 +423,8 @@ enum Term extends Statement: case Quoted(body: Term) case Unquoted(body: Term) case New(cls: Term, args: Ls[Term], rft: Opt[ClassSymbol -> ObjBody]) - (val typ: Opt[Type]) extends Term, ResolvableImpl - case DynNew(cls: Term, args: Ls[Term]) + (val resSym: FlowSymbol, val typ: Opt[Type]) extends Term, ResolvableImpl + case DynNew(cls: Term, args: Ls[Term]) extends Term, ResolvableImpl case Asc(term: Term, ty: Term) case CompType(lhs: Term, rhs: Term, pol: Bool) case Neg(rhs: Term) @@ -407,6 +455,10 @@ enum Term extends Statement: override def hashCode: Int = System.identityHashCode(this) */ + def withoutCaptures: Term = this match + case Capture(base, _) => base.withoutCaptures + case _ => this + def expanded: Term = this match case t: Resolvable => t.expansion match case S(S(t)) => t.expanded @@ -545,7 +597,7 @@ enum Term extends Statement: case Quoted(body) => Quoted(body.mkClone) case Unquoted(body) => Unquoted(body.mkClone) case term @ New(cls, args, rft) => - New(cls.mkClone, args.map(_.mkClone), rft.map { case (cs, ob) => cs -> ObjBody(ob.blk.mkBlkClone) })(term.typ) + New(cls.mkClone, args.map(_.mkClone), rft.map { case (cs, ob) => cs -> ObjBody(ob.blk.mkBlkClone) })(term.resSym, term.typ) case DynNew(cls, args) => DynNew(cls.mkClone, args.map(_.mkClone)) case term @ SelProj(prefix, cls, proj) => SelProj(prefix.mkClone, cls.mkClone, Tree.Ident(proj.name))(term.sym, term.resSym, term.typ, term.originalCtx) @@ -573,6 +625,11 @@ enum Term extends Statement: case _ => that + // // private[semantics] val reslListeners: Buffer[Resolution] = MutSet.empty + // private[semantics] val shapeListeners: Buffer[Shape => Unit] = Buffer.empty + // // private[semantics] def listen(f: Shape => Unit): Unit = + // // shapeListeners += f + end Term object Term: @@ -646,7 +703,12 @@ object ShowCfg: end ShowCfg -sealed trait Statement extends AutoLocated, ProductWithExtraInfo: +// TODO: mv to other file +trait Describable: + def describe: Str + + +sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: def mkClone(using State): Statement = this match case t: Term => lastWords(s"overridden implementation") @@ -664,8 +726,12 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case UnitVal() => "unit value" case Lit(lit) => lit.describeLit case Ref(sym) => "reference" + case Capture(base, thru) => base.describe + case SimpleRef(sym) => "reference" + case MemberRef(sym) => "member reference" case App(lhs, rhs) => "application" case TyApp(lhs, targs) => "type application" + case NewSel(pre, nme) => "selection" case Sel(pre, nme) => "selection" case SynthSel(pre, nme) => "selection" case DynSel(o, f, _) => "dynamic selection" @@ -684,7 +750,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Blk(stats, res) => "block" case Quoted(term) => "quoted term" case Unquoted(term) => "unquoted term" - case New(cls, args, rft) => "object creation" + case New(cls, args, rft) => "object instantiation" case SelProj(pre, cls, proj) => "field selection" case Asc(term, ty) => "type ascription" case CompType(lhs, rhs, pol) => if pol then "alternation" else "composition" @@ -705,6 +771,10 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Missing => "missing" case LeadingDotSel(name) => "leading dot selection" case Resolved(t, sym) => t.describe + case td: TermDefinition => s"term definition '${td.sym.nme}'" + case cls: ClassDef => s"class definition '${cls.sym.nme}'" + case mod: ModuleOrObjectDef => s"module/object definition '${mod.sym.nme}'" + case td: TypeDef => s"type definition '${td.bsym.nme}'" case s => TODO(s) this match case self: Resolvable => self.resolvedTyp match @@ -713,6 +783,8 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case _ => desc def extraInfo(using DebugPrinter): Str = this match + case s: AnySel if s.resolvedTargets.nonEmpty => + s"targets=${s.resolvedTargets.map(_.showAsPlain).mkString("[", ",", "]")}" case r: Resolvable if r.resolvedSym.isDefined || r.resolvedTyp.isDefined => ( r.resolvedSym.map(s => s"sym=${s.showAsPlain}") :: r.resolvedTyp.map(s => s"typ=${s.showDbg}") :: Nil @@ -724,7 +796,8 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Blk(stats, res) => stats.toVector :+ res case _ => subTerms def subTerms: Vector[Term] = this match - case Error() | Missing | _: Lit | _: Ref | _: UnitVal => Vector.empty + case Error() | Missing | _: Lit | _: AnyRef_ | _: UnitVal => Vector.empty + case Capture(base, thru) => Vector.single(base) case Resolved(t, sym) => Vector.single(t) case App(lhs, rhs) => Vector.double(lhs, rhs) case RcdField(lhs, rhs) => Vector.double(lhs, rhs) @@ -733,6 +806,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case TyApp(pre, tarsg) => pre +: tarsg.toVector case Sel(pre, _) => Vector.single(pre) case SynthSel(pre, _) => Vector.single(pre) + case NewSel(pre, _) => Vector.single(pre) case DynSel(o, f, _) => Vector.double(o, f) case Tup(fields) => fields.flatMap(_.subTerms).toVector case Mut(und) => Vector.single(und) @@ -805,19 +879,52 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: subTerms // TODO more precise (include located things that aren't terms) def show(using Scope, ShowCfg, Raise): Document = + + def showSelTargets(sel: AnySel): Document = + val str = sel.nme.name + val ts = sel.resolvedTargets + ts match + case Nil => + // doc"$str‹?›" + doc"${str}ˀˀˀ" + case t :: Nil => t.show + case ts => doc"$str‹" :: ts.map(_.show).mkDocument(", ") :: doc"›" + def res: Document = this match case lit: Lit => lit.lit.idStr + case UnitVal() => doc"()" + case r: SimpleRef => + r.sym match + case _: BuiltinSymbol => r.sym.nme + case _ => r.sym.showName + case r: MemberRef => r.sym.showName + case sr: SelfRef => s"${sr.sym.showName}.this" + case Capture(base, thru) => + // doc"${base.show}⟨${thru.showName}⟩" + doc"${base.show}^${thru.showName}" case r: Ref => r.sym match case _: BuiltinSymbol => r.sym.nme case _ => r.sym.showName + case sel: NewSel => + val str = sel.id.name + if summon[ShowCfg].showFlowSymbols + then doc"${sel.prefix.show}.${ + sel.resolvedMembers match + case Nil => doc"${str}ˀˀˀ" + case t :: Nil => t.showName + case ts => doc"$str‹" :: ts.map(_.showName).mkDocument(", ") :: doc"›" + }" + else doc"${sel.prefix.show}.$str" case sel: Sel => if summon[ShowCfg].showFlowSymbols - then doc"${sel.prefix.show}.${sel.sym.fold(doc"${sel.nme.name}‹?›")(_.showName)}" + then doc"${sel.prefix.show}.${sel.sym.fold(doc"${ + showSelTargets(sel)}")(_.showName)}" else doc"${sel.prefix.show}.${sel.nme.name}" case sel: SynthSel => if summon[ShowCfg].showFlowSymbols - then doc"⟨${sel.prefix.show}.⟩${sel.sym.fold(doc"${sel.nme.name}‹?›")(_.showName)}" + then doc"⟨${sel.prefix.show}.⟩${sel.sym.fold(doc"${ + showSelTargets(sel)}")(_.showName)}" else doc"${sel.prefix.show}.${sel.nme.name}" case Resolved(trm, sym) => trm.show @@ -866,8 +973,14 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: doc"import ${"\""}.../${imp.file.last}${"\""} as ${imp.sym.showName}" case LeadingDotSel(name) => doc"_?_.${name.name}" case Error() => doc"‹error›" + case IfLike(kw, form, split) => + // doc"${kw.name} ${form.headStr} ${split.show}" + given IfLikeForm = form + doc"${form.headStr} { #{ # ${split.show} #} # }" + // val fs = fomr match case _ => doc"TODO[show:${getClass.getSimpleName}](${toString})" + this match case t: Resolvable => t.expansion match case S(S(exp)) => @@ -881,6 +994,8 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case _ => res case _ => res + end show + def size: Int = this match case Lit(Tree.StrLit(str)) => str.size / 4 + 1 case _ => subTerms.iterator.map(_.size).sum + 1 @@ -908,6 +1023,10 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Lit(lit) => lit.idStr case Resolved(t, sym) => t.showPlain case r @ Ref(symbol) => symbol.toString + case r @ SimpleRef(symbol) => symbol.toString + case r @ MemberRef(symbol) => symbol.toString + case r @ SelfRef(sym) => sym.toString + case Capture(base, thru) => s"${base.showDbg}^${thru.showDbg}" case App(lhs, rhs) => s"${lhs.showDbg}${rhs.showDbgAsParams}" case RcdField(lhs, rhs) => s"${lhs.showDbg}: ${rhs.showDbg}" case RcdSpread(bod) => s"...${bod.showDbg}" @@ -922,6 +1041,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case WildcardTy(in, out) => s"in ${in.map(_.toString).getOrElse("⊥")} out ${out.map(_.toString).getOrElse("⊤")}" case Sel(pre, nme) => s"${pre.showDbg}.${nme.name}" case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" + case NewSel(pre, nme) => s"${pre.showDbg}.${nme.name}" case DynSel(pre, fld, _) => s"${pre.showDbg}[${fld.showDbg}]" case IfLike(kw, _, split) => s"${kw.name} { ${split.showDbg} }" case SynthIf(split) => s"if { ${split.showDbg} }" @@ -1163,6 +1283,14 @@ sealed abstract class Definition extends Declaration, Statement: def hasDeclareModifier: Opt[Annot.Modifier] = Annot.declareModifierOf(annotations) def hasStagedModifier: Opt[Annot.Modifier] = annotations.collectFirst: case mod @ Annot.Modifier(Keyword.`staged`) => mod + def describeRef: Str = this match + case td: TermDefinition => s"term '${td.sym.nme}'" + case cls: ClassDef => s"class '${cls.sym.nme}'" + case mod: ModuleOrObjectDef => + if mod.kind is Mod then s"module '${mod.sym.nme}'" + else s"object '${mod.sym.nme}'" + case td: TypeDef => s"type '${td.bsym.nme}'" + case pat: PatternDef => s"pattern '${pat.sym.nme}'" sealed trait CompanionValue extends Definition @@ -1367,6 +1495,11 @@ enum IfLikeForm: def isImperative: Bool = this match case ReturningIf => false case ImperativeIf | While => true + def headStr: Str = this match + case ReturningIf => "if" + case ImperativeIf => "if" + case While => "while" + def midStr: Str = if isImperative then "do" else "then" sealed abstract class Elem extends AutoLocated: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala new file mode 100644 index 0000000000..4944a86d8d --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala @@ -0,0 +1,39 @@ +package hkmc2 +package semantics + +import scala.collection.mutable.{Buffer, Set as MutSet, LinkedHashSet} + +import hkmc2.utils.*, shorthands.* +import syntax.* +import hkmc2.utils.Scope +import hkmc2.utils.Scope.scope +import hkmc2.document.* +import hkmc2.document.Document.* + +import Elaborator.State +import hkmc2.typing.Type +import hkmc2.semantics.Elaborator.{Ctx, ctx} +import hkmc2.Message.MessageContext +import hkmc2.semantics.flow.{SelectionTarget, AppTarget} + +import Term.* + + +// enum ReslTaregt: +// case + +type AnyResolvable = Resolvable | NewResolvableImpl +// type AnyRef = Ref | SimpleRef | MemberRef + +type NewResolvable = NewResolvableImpl & Term + +trait NewResolvableImpl: + self: MemberRef | NewSel => + var resolvedTargets: Ls[DefinitionSymbol[?]] = Nil // * filled during flow analysis + // val resSym: FlowSymbol = FlowSymbol.simpleRef(self.sym.name) + // var disamb: Opt[Disambiguation] = None // * filled during flow analysis + var isErroneous: Boolean = false // * filled during resolution + + + + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala index 10d4c0db64..ded6faa872 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -15,6 +15,7 @@ import syntax.Tree import Elaborator.{State, Ctx, ctx} import Producer as P import Consumer as C +import hkmc2.semantics.ClassSymbol @@ -27,9 +28,33 @@ type ProdCtor = Producer.Ctor | Producer.Fun | Producer.Typ | Producer.Tup | Pro case class ConcreteProd(path: Path, ctor: ProdCtor) +enum AppTarget: + case ObjectMember(sym: ClassSymbol) + case Err(err: ErrorReport) + enum SelectionTarget: case ObjectMember(sym: MemberSymbol) case CompanionMember(comp: Term, sym: MemberSymbol) + case Err(err: ErrorReport) + + def describe: Str = this match + case ObjectMember(sym) => s"member ${sym.nme}" // TODO: more info (owner) + case CompanionMember(_, sym) => s"companion member ${sym.nme}" + case Err(err) => s"erroneous selection (${err.mainMsg})" + + def loc: Opt[Loc] = this match + case ObjectMember(sym) => sym.toLoc + case CompanionMember(_, sym) => sym.toLoc + case Err(err) => N + + import hkmc2.document.* + import hkmc2.document.Document.* + def show(using Scope, ShowCfg, Raise): Document = + this match + case ObjectMember(sym) => sym.showName + case CompanionMember(comp, sym) => doc"${comp.show}.${sym.showName}" + case Err(err) => err.mainMsg +end SelectionTarget /** This is a very sketchy exploration/proof of concept of flow analysis diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala index 2365c6343c..cdc8dc7736 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala @@ -119,9 +119,11 @@ object SplitCompiler: import SplitCompiler.* /** This class compiles a pattern to a split that matches the pattern. */ -class SplitCompiler(using tl: TL)(using State, Ctx, Raise) extends TermSynthesizer: +class SplitCompiler(using tl: TL)(using State, Ctx, Raise, Config) extends TermSynthesizer: import tl.*, SP.* + val newResolution: Bool = config.language.useNewResolution + private lazy val lteq = State.builtinOpsMap("<=") private lazy val lt = State.builtinOpsMap("<") private lazy val add = State.builtinOpsMap("+") @@ -649,6 +651,17 @@ class SplitCompiler(using tl: TL)(using State, Ctx, Raise) extends TermSynthesiz */ def makeMatchSplit(scrutinee: Scrut, pattern: SP, outputNeeded: Bool): MakeSplit = pattern match + case ctor @ Constructor(target, arguments) if newResolution => + ctor.resolvedSym match + case S(classSymbol: ClassSymbol) => + makeMatchClassSplit(scrutinee, target, classSymbol, arguments, outputNeeded) + case S(objectSymbol: ModuleOrObjectSymbol) => + makeMatchObjectSplit(pattern.toLoc, scrutinee, target, objectSymbol, arguments) + case S(patternSymbol: PatternSymbol) => + makeMatchPatternSplit(scrutinee, target, patternSymbol, arguments) + case _ => + error(msg"Cannot use this ${target.describe} as a pattern." -> target.toLoc) + RejectSplit case Constructor(target, arguments) => target.resolvedSym match case S(symbol: VarSymbol) => makeMatchPatternParameterSplit(scrutinee, target, symbol, arguments, pattern.toLoc) diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index 48d7326643..f710778f1d 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -9,3 +9,180 @@ // :todo + +:rt +:lot +class Foo(a) +//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— +//│ Blk: +//│ stats = Ls of +//│ Parameterized: +//│ kind = Cls +//│ sym = class:Foo⁰ +//│ bsym = member:Foo¹ +//│ ctorSym = S of term:Foo² +//│ params = ParamList: +//│ params = Ls of +//│ Param: +//│ sym = a⁰ +//│ modulefulness = Modulefulness of N +//│ body = ObjBody of Blk: +//│ stats = Ls of +//│ LetDecl: +//│ sym = term:Foo⁰/a¹ +//│ DefineVar: +//│ sym = term:Foo⁰/a¹ +//│ rhs = Ref{sym=a⁰} of a⁰ +//│ res = Lit of UnitLit of false +//│ res = Lit of UnitLit of false +//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— +//│ Program: +//│ main = Define: +//│ defn = ClsLikeDefn: +//│ isym = class:Foo⁰ +//│ sym = member:Foo¹ +//│ ctorSym = S of term:Foo² +//│ k = Cls +//│ auxParams = Ls of +//│ ParamList: +//│ params = Ls of +//│ Param: +//│ sym = a⁰ +//│ modulefulness = Modulefulness of N +//│ privateFields = Ls of +//│ term:Foo⁰/a¹ +//│ ctor = AssignField: +//│ lhs = This of class:Foo⁰ +//│ nme = Ident of "a" +//│ rhs = SimpleRef of a⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:rt +class Foo(val a) +//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— +//│ Blk: +//│ stats = Ls of +//│ Parameterized: +//│ kind = Cls +//│ sym = class:Foo³ +//│ bsym = member:Foo⁴ +//│ ctorSym = S of term:Foo⁵ +//│ params = ParamList: +//│ params = Ls of +//│ Param: +//│ flags = (val) +//│ sym = a² +//│ modulefulness = Modulefulness of N +//│ body = ObjBody of Blk: +//│ stats = Ls of +//│ TermDefinition: +//│ k = ImmutVal +//│ sym = member:a³ +//│ tsym = term:Foo³/a⁴ +//│ body = S of Ref{sym=a²} of a² +//│ modulefulness = Modulefulness of N +//│ res = Lit of UnitLit of false +//│ res = Lit of UnitLit of false +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:rt +:sjs +new Foo(1) +//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— +//│ Blk: +//│ res = New{typ=class:Foo³}: +//│ cls = Resolved{sym=class:Foo³}: +//│ t = Ref{sym=member:Foo⁴} of member:Foo⁴ +//│ sym = class:Foo³ +//│ args = Ls of +//│ Tup of Ls of +//│ Fld: +//│ term = Lit of IntLit of 1 +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ return globalThis.Object.freeze(new Foo3.class(1)) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Foo(1) + + +:rt +class Foo { + val b = 1 +} +//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— +//│ Blk: +//│ stats = Ls of +//│ Plain: +//│ kind = Cls +//│ sym = class:Foo⁶ +//│ bsym = member:Foo⁷ +//│ body = ObjBody of Blk: +//│ stats = Ls of +//│ TermDefinition: +//│ k = ImmutVal +//│ sym = member:b⁰ +//│ tsym = term:Foo⁶/b¹ +//│ body = S of Lit of IntLit of 1 +//│ flags = (method) +//│ modulefulness = Modulefulness of N +//│ res = Lit of UnitLit of false +//│ res = Lit of UnitLit of false +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:rt +new Foo +//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— +//│ Blk: +//│ res = New{typ=class:Foo⁶}: +//│ cls = Resolved{sym=class:Foo⁶}: +//│ t = Ref{sym=member:Foo⁷} of member:Foo⁷ +//│ sym = class:Foo⁶ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Foo { b: 1 } + + + +class Foo(pred) { + val r = pred.id(42) + fun callId(x) = pred.id(x) +} + +class Foo { + // a + fun f = 1 +} + +:sir +:soir +:sjs +class Bar with + private val a = 1 + print(a) +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Bar⁰ as class Bar¹ { +//│ private val a⁵; +//│ constructor() { @private define a⁶ as val a⁵ = 1; do Predef⁰.print⁰(Bar¹.this.a⁵); end } +//│ }; +//│ end +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ define Bar⁰ as class Bar¹ { +//│ private val a⁵; +//│ constructor() { @private define a⁶ as val a⁵ = 1; do Predef⁰.print⁰(Bar¹.this.a⁵); end } +//│ }; +//│ end +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let Bar1; +//│ (class Bar { +//│ static { +//│ Bar1 = this +//│ } +//│ constructor() { +//│ this.#a = 1; +//│ Predef.print(this.#a); +//│ } +//│ #a; +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Bar"]; +//│ }); +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + diff --git a/hkmc2/shared/src/test/mlscript/HkScratch2.mls b/hkmc2/shared/src/test/mlscript/HkScratch2.mls new file mode 100644 index 0000000000..c5a479003d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/HkScratch2.mls @@ -0,0 +1,58 @@ +:js +// :de +// :sjs +// :pt +// :elt + +:global +// :d +// :todo + + + +:sr +class Foo(val a) +object Bar { val b = 123 } +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo⁰ +//│ class Foo¹::Foo²(val a⁰) { +//│ val a¹::a² ‹flow:a⁰› = a⁰ +//│ }, +//│ object Bar⁰::Bar¹ { val b⁰::b¹ ‹flow:b⁰› = 123 } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +let x = Foo(Bar) +//│ x = Foo(Bar { b: 123, class: object Bar }) + +// :dl +// :de +:sr +// :elt +:sir +:sjs +if x is + Foo(y) then y.b +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ if { x⁰ is Foo¹(_ as y⁰) then y⁰.bˀˀˀ } +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let y, arg$Foo$0$; +//│ match x⁰ +//│ Foo⁰ => +//│ set arg$Foo$0$ = x⁰.a⁰; +//│ set y = arg$Foo$0$; +//│ return y.b﹖ +//│ else +//│ throw new globalThis⁰.Error⁰("match error") +//│ end +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let arg$Foo$0$; +//│ if (x instanceof Foo1.class) { +//│ arg$Foo$0$ = x.a; +//│ return arg$Foo$0$.b +//│ } +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 123 + + diff --git a/hkmc2/shared/src/test/mlscript/newres/.mls b/hkmc2/shared/src/test/mlscript/newres/.mls new file mode 100644 index 0000000000..411541472f --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/.mls @@ -0,0 +1,2 @@ +:.. +#lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/newres/BadApps.mls b/hkmc2/shared/src/test/mlscript/newres/BadApps.mls new file mode 100644 index 0000000000..134b84910b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/BadApps.mls @@ -0,0 +1,30 @@ +:. + + +// FIXME: dedup errors + +:e +42() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.7: 42() +//│ ║ ^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.7: 42() +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] TypeError: 42 is not a function + + +let x = 123 +//│ x = 123 + +:e +x(456) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.21: x(456) +//│ ║ ^^^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.17: let x = 123 +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] TypeError: x is not a function + + diff --git a/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls new file mode 100644 index 0000000000..eac7cfac84 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls @@ -0,0 +1,159 @@ +:. + + +:elt +class Foo { + val b = 1 +} +//│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— +//│ Blk: +//│ stats = Ls of +//│ Plain: +//│ kind = Cls +//│ sym = class:Foo⁰ +//│ bsym = member:Foo¹ +//│ body = ObjBody of Blk: +//│ stats = Ls of +//│ TermDefinition: +//│ k = ImmutVal +//│ sym = member:b⁰ +//│ tsym = term:Foo⁰/b¹ +//│ body = S of Lit of IntLit of 1 +//│ flags = (method) +//│ modulefulness = Modulefulness of N +//│ res = Lit of UnitLit of false +//│ res = Lit of UnitLit of false +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:sr +:sir +class Foo { + val b = 1 + this.b +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ class Foo⁰::Foo¹ { val b⁰::b¹ ‹flow:b⁰› = 1, Foo¹.b⁰ } +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Foo² as class Foo³ { val b²; constructor() { define b² as val b³ = 1; end } }; end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:sr +Foo +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ Foo⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = class Foo + +:e +Foo.b +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.49: Foo.b +//│ ║ ^^^^^ +//│ ╟── Class 'Foo' does not contain member 'b' +//│ ║ l.30: class Foo { +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +let f = Foo +//│ f = class Foo + +:e +f.b +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.62: f.b +//│ ║ ^^^ +//│ ╟── Class 'Foo' does not contain member 'b' +//│ ║ l.30: class Foo { +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +:sr +:sir +:sjs +let f = new Foo +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let f⁰, f⁰ = new Foo⁰ +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ set f⁰ = new Foo³(); end +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let f1; f1 = globalThis.Object.freeze(new Foo3()); +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = Foo { b: 1 } + +let f = new Foo() +//│ f = Foo { b: 1 } + +:sr +f.b +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f¹.b⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + + +:e +class Foo { + constructor(val a) { + val b = a + 1 + } +} +//│ ╔══[COMPILATION ERROR] Unsupported constructor in this position. +//│ ║ l.98: constructor(val a) { +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.99: val b = a + 1 +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ║ l.100: } +//│ ╙── ^^^ + + +// :fixme +:breakme // `val` in ctor param list should be compiled or explicitly disallowed +:sr +class Foo { + constructor(val a) + val b = a + 1 +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo² class Foo³::Foo⁴(val a⁰) { let a¹, a¹ = a⁰, val b²::b³ ‹flow:b¹› = +(a¹^b³, 1)‹app⁰› } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:e +let f = Foo(1) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.124: let f = Foo(1) +//│ ║ ^^^^^^ +//│ ╟── Class 'Foo' cannot receive more argument lists. +//│ ║ l.114: class Foo { +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo6 cannot be invoked without 'new' +//│ f = undefined + + +let f = new Foo(1) +//│ f = Foo { b: 2 } + +:fixme +:sr +f.a +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.140: f.a +//│ ║ ^^^ +//│ ╟── Instance of class 'Foo' does not contain member 'a' +//│ ║ l.135: let f = new Foo(1) +//│ ╙── ^^^^^^^^^^ +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f².aˀˀˀ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:sr +f.b +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f².b² +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 2 + + diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls new file mode 100644 index 0000000000..67a590e64a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -0,0 +1,156 @@ +:. + + +:elt +:rt +:sir +id +//│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— +//│ Blk: +//│ res = SynthSel{sym=member:id⁰}: +//│ prefix = MemberRef of member:Predef⁰ +//│ nme = Ident of "id" +//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— +//│ Blk: +//│ res = SynthSel{sym=member:id⁰}: +//│ prefix = MemberRef of member:Predef⁰ +//│ nme = Ident of "id" +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ return Predef¹.id¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +:de +:sr +:sir +Predef.id +//│ Elab block List(Sel(Ident(Predef),Ident(id))) LocalScope(block:3) +//│ | Elab term Sel(Ident(Predef),Ident(id)) +//│ | | Elab subterm Sel(Ident(Predef),Ident(id)) +//│ | | | Elab subterm Ident(Predef) +//│ | | | ~> member:Predef +//│ | | | newSel? sel = member:Predef.id +//│ | | | listenTerm: trm = member:Predef +//│ | | | listen: trm = member:Predef +//│ | | | listenBMS: bms = module +//│ | | | listenedBMS: bms = module +//│ | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() +//│ | | | .exit MarkedShape (module:Predef¹, module:Predef¹, Predef²) +//│ | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? +//│ | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | PUSH MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | newSel: sel = member:Predef.id, shape = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | newSel member: bms = member:id⁰, mss = List(↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | ~> member:Predef.id +//│ | ~> member:Predef.id +//│ ~> { member:Predef.id } +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ Predef⁰.id⁰ +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ return Predef¹.id¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + +:elt +Predef.id(1) +//│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— +//│ Blk: +//│ res = App: +//│ lhs = NewSel: +//│ prefix = MemberRef of member:Predef⁰ +//│ id = Ident of "id" +//│ rhs = Tup of Ls of +//│ Fld: +//│ term = Lit of IntLit of 1 +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +:e +Predef.idd(1) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.70: Predef.idd(1) +//│ ║ ^^^^^^^^^^ +//│ ╟── Module 'Predef' does not contain member 'idd' +//│ ║ in ../../mlscript-compile/Predef.mls +//│ ║ l.15: module Predef with ... +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +// :de +:silent +let p = Predef + +// :de +:rt +:sir +p.id +//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— +//│ Blk: +//│ res = NewSel: +//│ prefix = SimpleRef of p⁰ +//│ id = Ident of "id" +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ return p⁰.id¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + +p.id +//│ = fun id + +:sr +p.id +p.id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ p⁰.id⁰, p⁰.id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +:silent +val p = Predef + +:sr +p.id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ p¹.id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +:e +(1).id +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.123: (1).id +//│ ║ ^^^^^^ +//│ ╟── Integer literal does not contain member 'id' +//│ ║ l.123: (1).id +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:fixme // parse +1.id +//│ ╔══[LEXICAL ERROR] Expected at least one digit after the decimal point +//│ ║ l.133: 1.id +//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). +//│ ║ l.133: 1.id +//│ ╙── ^^ +//│ = 1 + +:fixme +"ha ".repeat(2) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.143: "ha ".repeat(2) +//│ ║ ^^^^^^^^^^^^ +//│ ╟── String literal does not contain member 'repeat' +//│ ║ l.143: "ha ".repeat(2) +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +print(1) +//│ > 1 + + diff --git a/hkmc2/shared/src/test/mlscript/newres/Captures.mls b/hkmc2/shared/src/test/mlscript/newres/Captures.mls new file mode 100644 index 0000000000..1830ec870b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Captures.mls @@ -0,0 +1,118 @@ +:. + + +:sr +fun foo(x) = + print(x) + x +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo⁰::foo¹(x⁰) ‹flow:foo⁰› = { ⟨Predef⁰.⟩print⁰^foo¹(x⁰)‹app⁰›, x⁰ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +// :de +:sr +foo(1) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ foo⁰(1)‹app¹› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ > 1 +//│ = 1 + +// :de +:sr +foo(Predef).id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ foo⁰(Predef⁰)‹app²›.id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ > class Predef { Symbols: Symbols { prettyPrint: Symbol("mlscript.prettyPrint"), definitionMetadata: Symbol("mlscript.definitionMetadata"), class: object Symbols }, Sub: class Sub, Eq: class Eq, Refl: Refl, js_assert: fun assert, foldl: fun fold, meta: class meta } +//│ = fun id + +:sr +object Foo { + val id = 42 +} +foo(Foo).id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ object Foo⁰::Foo¹ { val id¹::id² ‹flow:id⁰› = 42 }, foo⁰(Foo⁰)‹app³›.id¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ > Foo { id: 42, class: object Foo } +//│ = 42 + + +:sr +fun foo(x) = + if x > 0 do + print(x) + foo(x - 1) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo²::foo³(x¹) ‹flow:foo¹› = if { +//│ let scrut⁰ = >(x¹, 0)‹app⁴›; +//│ scrut⁰ is true do { +//│ ⟨Predef⁰.⟩print⁰^foo³(x¹)‹app⁵›, +//│ foo²^foo³(-(x¹, 1)‹app⁶›)‹app⁷› +//│ }; +//│ else () +//│ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +foo(3) +//│ > 3 +//│ > 2 +//│ > 1 + + +:sr +fun foo(x) = + print(x) + fun bar(y) = x + y + bar +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo⁴::foo⁵(x²) ‹flow:foo²› = { +//│ ⟨Predef⁰.⟩print⁰^foo⁵(x²)‹app⁸›, +//│ fun bar⁰::bar¹(y⁰) ‹flow:bar⁰› = +(x²^bar¹, y⁰)‹app⁹›, +//│ bar⁰ +//│ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +foo(3)(4) +//│ > 3 +//│ = 7 + + +:sr +class foo(x) { + print(x) + fun bar(y) = x + y +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo⁶ +//│ class foo⁷::foo⁸(x³) { +//│ @private val x⁴::x⁵ ‹flow:x⁰› = x³^x⁵, +//│ ⟨Predef⁰.⟩print⁰(⟨foo⁸.this.⟩x⁴)‹app¹⁰›, +//│ fun bar²::bar³(y¹) ‹flow:bar¹› = +(⟨foo⁸.this.⟩x⁴^bar³, y¹)‹app¹¹› +//│ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +foo(3).bar(4) +//│ > 3 +//│ = 7 + + +:sr +fun foo(x) = y => + x + y +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo⁹::foo¹⁰(x⁶) ‹flow:foo³› = (y²) => +(x⁶, y²)‹app¹²› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +let f = foo(3) +//│ f = fun + +f(4) +//│ = 7 + +foo(3)(4) +//│ = 7 + + diff --git a/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls b/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls new file mode 100644 index 0000000000..2ba61244cd --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls @@ -0,0 +1,106 @@ +:. + + +:sr +module A { + val u = 1 +} +module B { + val u = 2 +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ module A⁰::A¹ { val u⁰::u¹ ‹flow:u⁰› = 1 }, module B⁰::B¹ { val u²::u³ ‹flow:u¹› = 2 } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +fun foo(x) = x + +// :de +:sr +foo(A).u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ foo⁰(A⁰)‹app⁰›.u⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +foo(A).u +//│ = 1 + +// * We now preserve context sensitivity +:sr +foo(B).u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ foo⁰(B⁰)‹app¹›.u² +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 2 + + +// :de +:sr +fun bar(y) = + foo(y) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun bar⁰::bar¹(y⁰) ‹flow:bar⁰› = foo⁰^bar¹(y⁰)‹app²› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// :de +// :fixme +:sr +bar(A).u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ bar⁰(A⁰)‹app³›.u⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +// * As expected, we currently lose context sensitivity when going through the call of a captured function (foo) +:todo +:sr +bar(B).u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ bar⁰(B⁰)‹app⁴›.u‹u², u⁰› +//│ ╔══[COMPILATION ERROR] This selection of member 'u' is ambiguous, as it has multiple resolved targets +//│ ║ l.58: bar(B).u +//│ ║ ^^^^^^^^ +//│ ╟── target: value 'u' in module 'B' +//│ ║ l.9: val u = 2 +//│ ║ ^^^^^^^^^ +//│ ╟── target: value 'u' in module 'A' +//│ ║ l.6: val u = 1 +//│ ╙── ^^^^^^^^^ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +:sr +class Foo(val a) { + val b = A +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo⁰ class Foo¹::Foo²(val a⁰) { val a¹::a² ‹flow:a⁰› = a⁰^a², val b⁰::b¹ ‹flow:b⁰› = A⁰^b¹ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +let x = Foo(A) +//│ x = Foo(class A { u: 1 }) + +:sr +x.a.u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ x⁰.a¹.u⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +:sr +x.b.u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ x⁰.b⁰.u⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +:sr +Foo(B).a.u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ Foo¹(B⁰)‹app⁵›.a¹.u² +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 2 + + diff --git a/hkmc2/shared/src/test/mlscript/newres/CurriedFunctions.mls b/hkmc2/shared/src/test/mlscript/newres/CurriedFunctions.mls new file mode 100644 index 0000000000..8a81488f3b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/CurriedFunctions.mls @@ -0,0 +1,57 @@ +:. + + +:sr +fun foo(x)(y) = + x + y +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo⁰::foo¹(x⁰)(y⁰) ‹flow:foo⁰› = +(x⁰, y⁰)‹app⁰› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:sr +let f = foo(1)(2) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let f⁰, f⁰ = foo⁰(1)‹app¹›(2)‹app²› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = 3 + +:breakme // builtins like `+` do not yet yield a shape +:e +f(3) +//│ ═══[RUNTIME ERROR] TypeError: f is not a function + +:sr +let f = foo(1) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let f¹, f¹ = foo⁰(1)‹app³› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = fun + +:sr +let g = f(2) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let g⁰, g⁰ = f¹(2)‹app⁴› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ g = 3 + +:breakme // builtins like `+` do not yet yield a shape +:e +g(3) +//│ ═══[RUNTIME ERROR] TypeError: g is not a function + + +let f = x => y => x + y +//│ f = fun f + +f(1) +//│ = fun + +:sr +f(1)(2) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f²(1)‹app⁵›(2)‹app⁶› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 3 + + diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls new file mode 100644 index 0000000000..6f4bac6ffd --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -0,0 +1,226 @@ +:. + + +fun foo(x) = x + 1 + +:e +foo() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.7: foo() +//│ ║ ^^^^^ +//│ ╟── Function 'foo' expected 1 argument, but got 0 +//│ ║ l.4: fun foo(x) = x + 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] Error: Function 'foo' expected 1 argument but got 0 + +:e +foo(1, 2) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.17: foo(1, 2) +//│ ║ ^^^^^^^^^ +//│ ╟── Function 'foo' expected 0 arguments, but got 1 +//│ ║ l.4: fun foo(x) = x + 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] Error: Function 'foo' expected 1 argument but got 2 + + +:sr +id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ ⟨Predef⁰.⟩id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +:e +fun callid0(p) = p.id(1) +//│ ╔══[COMPILATION ERROR] This selection of member 'id' has no resolved target +//│ ║ l.36: fun callid0(p) = p.id(1) +//│ ╙── ^^^^ + + +:de +:sr +fun callid1(p) = p.id(1) +callid1(Predef) +//│ Elab block List(TermDef(Fun,App(Ident(callid1),Tup(List(Ident(p)))),Some(App(Sel(Ident(p),Ident(id)),Tup(List(I[...] LocalScope(block:7) +//│ | Processing term definition App(Ident(callid1),Tup(List(Ident(p)))) +//│ | Elaborating ParamTree: ParamTree(‹›,Ident(p),None,None,Set()) +//│ | Elab term App(Sel(Ident(p),Ident(id)),Tup(List(IntLit(1)))) +//│ | | Elab subterm App(Sel(Ident(p),Ident(id)),Tup(List(IntLit(1)))) +//│ | | | Elab subterm Sel(Ident(p),Ident(id)) +//│ | | | | Elab subterm Ident(p) +//│ | | | | ~> p +//│ | | | | newSel? sel = p.id +//│ | | | | listenTerm: trm = p +//│ | | | | listen: trm = p +//│ | | | ~> p.id +//│ | | | Elab subterm Tup(List(IntLit(1))) +//│ | | | | Elab term IntLit(1) +//│ | | | | | Elab subterm IntLit(1) +//│ | | | | | ~> 1 +//│ | | | | ~> 1 +//│ | | | ~> [1] +//│ | | | listenTerm: trm = p.id +//│ | | | listen: trm = p.id +//│ | | ~> p.id(1) +//│ | ~> p.id(1) +//│ | Elab term App(Ident(callid1),Tup(List(Ident(Predef)))) +//│ | | Elab subterm App(Ident(callid1),Tup(List(Ident(Predef)))) +//│ | | | Elab subterm Ident(callid1) +//│ | | | ~> member:callid1 +//│ | | | Elab subterm Tup(List(Ident(Predef))) +//│ | | | | Elab term Ident(Predef) +//│ | | | | | Elab subterm Ident(Predef) +//│ | | | | | ~> member:Predef +//│ | | | | ~> member:Predef +//│ | | | ~> [member:Predef] +//│ | | | listenTerm: trm = member:callid1 +//│ | | | listen: trm = member:callid1 +//│ | | | listenBMS: bms = function +//│ | | ~> member:callid1(member:Predef) +//│ | ~> member:callid1(member:Predef) +//│ | listenedBMS: bms = function +//│ | fromBMS: bms = member:callid1⁰, sh = member:callid1⁰, flow = callid1¹, markss = List() +//│ | .exit MarkedShape (member:callid1⁰, term:callid1², callid1¹) +//│ | | class hkmc2.semantics.TermSymbol None +//│ | = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | PUSH MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | appShape: lhs = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ), args = [member:Predef], res = member:callid1(member:Predef) +//│ | Zipping ParamList(‹›,List(Param(‹›,p,None,Modulefulness(None))),None) (List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ)) with List(Fld(‹›,MemberRef(member:Predef),None)) +//│ | listenTerm: trm = member:Predef +//│ | listen: trm = member:Predef +//│ | listenBMS: bms = module +//│ | listenedBMS: bms = module +//│ | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() +//│ | .exit MarkedShape (module:Predef¹, module:Predef¹, Predef²) +//│ | | class hkmc2.semantics.ModuleOrObjectSymbol ? +//│ | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | PUSH MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | .enter MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) (↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .enter MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) (ϵ) +//│ | | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | zipArg: p = p⁰, trm = member:Predef, sh = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) true, false +//│ | newSel: sel = p.id, shape = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | newSel member: bms = member:id⁰, mss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | listenBMS: bms = function +//│ | listenedBMS: bms = function +//│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) +//│ | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) +//│ | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) +//│ | | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .exit MarkedShape (MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), module:Predef¹, Predef²) +//│ | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? +//│ | | | = MarkedShape(member:id⁰, ↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | = MarkedShape(member:id⁰, ↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | appShape: lhs = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), args = [1], res = p.id(1) +//│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) +//│ | listenTerm: trm = 1 +//│ | listen: trm = 1 +//│ | .enter 1 (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | .exit MarkedShape (1, term:callid1², callid1¹) +//│ | | | class hkmc2.semantics.TermSymbol None +//│ | | = MarkedShape(1, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .enter MarkedShape(1, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .enter MarkedShape(1, ↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | | .enter MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) +//│ | | | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false +//│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(module:Predef,Some(Predef),ExitMark(term:Predef/id,Some(⋅id),NoMarks))))) +//│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) +//│ | appShape: td.body = Some(Ref(x)) +//│ | listenTerm: trm = x⁰ +//│ | listen: trm = x⁰ +//│ | listenBMS: bms = function +//│ | listenedBMS: bms = function +//│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) +//│ | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) +//│ | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) +//│ | | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .exit MarkedShape (MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), module:Predef¹, Predef²) +//│ | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? +//│ | | | = MarkedShape(member:id⁰, ↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | = MarkedShape(member:id⁰, ↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) +//│ | listenTerm: trm = 1 +//│ | listen: trm = 1 +//│ | .enter 1 (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | .exit MarkedShape (1, term:callid1², callid1¹) +//│ | | | class hkmc2.semantics.TermSymbol None +//│ | | = MarkedShape(1, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .enter MarkedShape(1, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .enter MarkedShape(1, ↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | | .enter MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) +//│ | | | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false +//│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(module:Predef,Some(Predef),ExitMark(term:Predef/id,Some(⋅id),NoMarks))))) +//│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) +//│ | appShape: td.body = Some(Ref(x)) +//│ | listenTerm: trm = x⁰ +//│ | listen: trm = x⁰ +//│ | appShape isSaturated? true; head? (DefnShape(term definition 'callid1'),List(ExitMark(term:callid1,Some(callid1),NoMarks))) +//│ | lhs false List(((p⁰),List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ))) +//│ | appShape: td.body = Some(App(NewSel(SimpleRef(p),Ident(id)),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))) +//│ | listenTerm: trm = p.id(1) +//│ | listen: trm = p.id(1) +//│ ~> { fun member:callid1(p⁰) = p.id(1); member:callid1(member:Predef) } +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun callid1⁰::callid1¹(p⁰) ‹flow:callid1⁰› = p⁰.id⁰(1)‹app⁰›, callid1⁰(Predef⁰)‹app¹› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + + +:sr +callid2(Predef) +fun callid2(p) = p.id(1) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ callid2⁰(Predef⁰)‹app²›, fun callid2⁰::callid2¹(p¹) ‹flow:callid2⁰› = p¹.id⁰(1)‹app³› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +fun getPredef() = Predef + +:sr +getPredef().id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ getPredef⁰()‹app⁴›.id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +// :de +:sr +fun foo(pred) = + let p = pred + val r = p.id(1) +foo(Predef) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo⁰::foo¹(pred⁰) ‹flow:foo⁰› = { +//│ let p², +//│ p² = pred⁰, +//│ val r⁰::r¹ ‹flow:r⁰› = p²^r¹.id⁰(1)‹app⁵›, +//│ () +//│ }, +//│ foo⁰(Predef⁰)‹app⁶› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + diff --git a/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls b/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls new file mode 100644 index 0000000000..adf49bce46 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls @@ -0,0 +1,151 @@ +:. + + +:sr +class Foo(val x) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo⁰ class Foo¹::Foo²(val x⁰) { val x¹::x² ‹flow:x⁰› = x⁰^x² } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +// :elt +:sr +:sjs +class Bar(y) extends Foo(y + 1) { + val z = this.x + 1 +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Bar⁰ +//│ class Bar¹::Bar²(y⁰) extends new Foo¹(+(y⁰, 1)‹app⁰›) { +//│ @private val y¹::y² ‹flow:y⁰› = y⁰^y², +//│ val z⁰::z¹ ‹flow:z⁰› = +(Bar².x¹, 1)‹app¹› +//│ } +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let Bar1; +//│ Bar1 = function Bar(y) { +//│ return globalThis.Object.freeze(new Bar.class(y)); +//│ }; +//│ (class Bar extends Foo1.class { +//│ static { +//│ Bar1.class = this +//│ } +//│ constructor(y) { +//│ let tmp; +//│ tmp = y + 1; +//│ super(tmp); +//│ let tmp1; +//│ tmp1 = this.x + 1; +//│ this.z = tmp1; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Bar", [null]]; +//│ }); +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +let b = Bar(123) +//│ b = Bar(_) + +// :de +:sr +b.x +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ b⁰.x¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 124 + +:breakme +:e +b.y + +b.z +//│ = 125 + + +let b = new Bar(123) +//│ b = Bar(_) + +:breakme +:e +b.y + +:fixme +b.x +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.72: b.x +//│ ║ ^^^ +//│ ╟── Instance of class 'Bar' does not contain member 'x' +//│ ║ l.64: let b = new Bar(123) +//│ ╙── ^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +b.z +//│ = 125 + + +:fixme +class Baz extends Bar(42) { + val a = [this.z, this.x] +} +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.87: val a = [this.z, this.x] +//│ ║ ^^^^^^ +//│ ╟── Class definition 'Baz' does not contain member 'x' +//│ ║ l.87: val a = [this.z, this.x] +//│ ╙── ^^^^^^^^^^^^^^^^ + + + + +module Outer { + class Inner1 { + constructor(val x) + } + class Inner2(val x) +} + +:e +object Derived extends Outer.Inner1 +//│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported +//│ ║ l.107: object Derived extends Outer.Inner1 +//│ ╙── ^^^^^^^^^^^^ + +:sr +:sir +object Derived extends Outer.Inner1(11) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ object Derived⁰::Derived¹ extends new Outer⁰.Inner1⁰(11) { } +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Derived⁰ as object Derived¹ extends Outer⁰.Inner1⁰ {}; end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:fixme +Derived.x +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.122: Derived.x +//│ ║ ^^^^^^^^^ +//│ ╟── Object 'Derived' does not contain member 'x' +//│ ║ l.114: object Derived extends Outer.Inner1(11) +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +Derived."x" + +:e +object Derived extends Outer.Inner2 +//│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported +//│ ║ l.134: object Derived extends Outer.Inner2 +//│ ╙── ^^^^^^^^^^^^ + +:sr +:sir +object Derived extends Outer.Inner2(12) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ object Derived²::Derived³ extends new Outer⁰.Inner2⁰(12) { } +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Derived² as object Derived³ extends Outer⁰.Inner2⁰ {}; end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +Derived.x +//│ = 12 + + diff --git a/hkmc2/shared/src/test/mlscript/newres/MultiParamClasses.mls b/hkmc2/shared/src/test/mlscript/newres/MultiParamClasses.mls new file mode 100644 index 0000000000..2826f2baca --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/MultiParamClasses.mls @@ -0,0 +1,83 @@ +:. + + +:sr +class Foo(val a)(val b) { + val c = a + b +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo⁰ +//│ class Foo¹::Foo²(val a⁰)(val b⁰) { +//│ val a¹::a² ‹flow:a⁰› = a⁰^a², +//│ val b¹::b² ‹flow:b⁰› = b⁰^b², +//│ val c⁰::c¹ ‹flow:c⁰› = +(⟨Foo².this.⟩a¹^c¹, ⟨Foo².this.⟩b¹^c¹)‹app⁰› +//│ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:sr +let f = new Foo +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let f⁰, f⁰ = new Foo¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = fun f + +let mk = f(1) +//│ mk = fun + +:sr +mk(2) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ mk⁰(2)‹app¹› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Foo(1) + +:sr +f(1)(2) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f⁰(1)‹app²›(2)‹app³› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Foo(1) + +:sr +let f = new Foo(1) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let f¹, f¹ = new Foo¹(1) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = fun f + +:sr +f(2) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f¹(2)‹app⁴› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Foo(1) + +:sr +let f = new Foo(1)(2) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let f², f² = new Foo¹(1)(2) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = Foo(1) + +:sr +f.a +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f².a¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +:sr +f.b +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f².b¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 2 + +:sr +f.c +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f².c⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 3 + + diff --git a/hkmc2/shared/src/test/mlscript/newres/NestedClasses.mls b/hkmc2/shared/src/test/mlscript/newres/NestedClasses.mls new file mode 100644 index 0000000000..67c18dd5a6 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/NestedClasses.mls @@ -0,0 +1,46 @@ +:. + + +:sr +:sir +module Foo { + class Bar { + fun id(x) = x + } +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ module Foo⁰::Foo¹ { class Bar⁰::Bar¹ { fun id⁰::id¹(x⁰) ‹flow:id⁰› = x⁰ } } +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Foo⁰ as class Foo¹ +//│ module Foo² { +//│ constructor { define Bar⁰ as class Bar¹ { method id⁰ = fun id¹(x) { return x } }; end } +//│ }; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:sr +:sir +:lot +let x = new Foo.Bar +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let x¹, x¹ = new Foo⁰.Bar⁰ +//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— +//│ Program: +//│ main = Assign: +//│ lhs = x⁰ +//│ rhs = Instantiate: +//│ mut = false +//│ cls = Select{sym=class:Bar¹}: +//│ qual = MemberRef{disamb=module:Foo²}: +//│ bms = member:Foo⁰ +//│ disamb = module:Foo² +//│ name = Ident of "Bar" +//│ argss = Ls of +//│ Nil +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ set x⁰ = new Foo².Bar¹(); end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ x = Bar + + + diff --git a/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls new file mode 100644 index 0000000000..0484016ee1 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls @@ -0,0 +1,118 @@ +:. + + +:sr +:sir +module Foo { + module Bar { + fun id(x) = x + } +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ module Foo⁰::Foo¹ { module Bar⁰::Bar¹ { fun id⁰::id¹(x⁰) ‹flow:id⁰› = x⁰ } } +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Foo⁰ as class Foo¹ +//│ module Foo² { +//│ constructor { +//│ define Bar⁰ as class Bar¹ +//│ module Bar² { method id⁰ = fun id¹(x) { return x } }; +//│ end +//│ } +//│ }; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// :de +:sr +:sir +:lot +Foo.Bar +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ Foo⁰.Bar⁰ +//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— +//│ Program: +//│ main = Return of Select{sym=module:Bar²}: +//│ qual = MemberRef{disamb=module:Foo²}: +//│ bms = member:Foo⁰ +//│ disamb = module:Foo² +//│ name = Ident of "Bar" +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ return Foo².Bar² +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = class Bar + +:sr +Foo.Bar.id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ Foo⁰.Bar⁰.id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +let + f = Foo + b = f.Bar +//│ b = class Bar +//│ f = class Foo { Bar: class Bar } + +:elt +b.id +//│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— +//│ Blk: +//│ res = NewSel: +//│ prefix = SimpleRef of b⁰ +//│ id = Ident of "id" +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +// :de +:sr +let m = if maybe then Foo.Bar else Predef +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let m⁰, m⁰ = if { let scrut⁰ = ⟨Predef⁰.⟩maybe⁰; scrut⁰ is true then Foo⁰.Bar⁰; else Predef⁰ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ m = class Bar + +:sr +:e +m.id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ m⁰.id‹id², id⁰› +//│ ╔══[COMPILATION ERROR] This selection of member 'id' is ambiguous, as it has multiple resolved targets +//│ ║ l.79: m.id +//│ ║ ^^^^ +//│ ╟── target: function 'id' in module 'Predef' +//│ ║ in ../../mlscript-compile/Predef.mls +//│ ║ l.18: fun id(x) = x +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── target: function 'id' in module 'Bar' +//│ ║ in NestedModules.mls:6 +//│ ║ l.8: fun id(x) = x +//│ ╙── ^^^^^^^^^^^^^ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +let x = id(Foo.Bar) +//│ x = class Bar + +// * We don't currently resolve *through* external definitions +:todo +x.id +//│ ╔══[COMPILATION ERROR] This selection of member 'id' has no resolved target +//│ ║ l.102: x.id +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +fun localid(x) = x + +localid(Foo.Bar).id +//│ = fun id + +// * We preserve context sensitivity +localid(Predef).id +//│ = fun id + + diff --git a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls new file mode 100644 index 0000000000..961352fd48 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls @@ -0,0 +1,339 @@ +:. + + +// :elt +:sr +class Foo(val a) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo⁰ class Foo¹::Foo²(val a⁰) { val a¹::a² ‹flow:a⁰› = a⁰^a² } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +// let f = Foo(123) + + +:sr +:silent +val f = Foo(Predef) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ val f⁰::f¹ ‹flow:f⁰› = Foo¹^f¹(Predef⁰^f¹)‹app⁰› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// :de +:sr +let tmp = f.a +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let tmp⁰, tmp⁰ = f⁰.a¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ tmp = class Predef { +//│ Symbols: Symbols { +//│ prettyPrint: Symbol("mlscript.prettyPrint"), +//│ definitionMetadata: Symbol("mlscript.definitionMetadata"), +//│ class: object Symbols +//│ }, +//│ Sub: class Sub, +//│ Eq: class Eq, +//│ Refl: Refl, +//│ js_assert: fun assert, +//│ foldl: fun fold, +//│ meta: class meta +//│ } + +// :de +tmp.id +//│ = fun id + +// :de +:sr +f.a.id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f⁰.a¹.id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +// :de +:silent +:sr +let p = Foo(Predef).a +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let p⁰, p⁰ = Foo¹(Predef⁰)‹app¹›.a¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// :de +:sr +p.id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ p⁰.id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + +:sr +Foo(Predef).a.id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ Foo¹(Predef⁰)‹app²›.a¹.id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +:sr +// TODO +// class Foo(val a) { +// val b = a + 1 +// } +class Foo(val a) { + val b = a + 1 +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo³ +//│ class Foo⁴::Foo⁵(val a³) { +//│ val a⁴::a⁵ ‹flow:a¹› = a³^a⁵, +//│ val b⁰::b¹ ‹flow:b⁰› = +(⟨Foo⁵.this.⟩a⁴^b¹, 1)‹app³› +//│ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// :de +:sr +let f = Foo(1) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let f², f² = Foo⁴(1)‹app⁴› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = Foo(1) + +// :fixme +// :de +:sr +f.a +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f².a⁴ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +// :fixme +// :de +:sr +f.b +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ f².b⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 2 + + +val fv = Foo(1) +//│ fv = Foo(1) + +:sr +fv.a +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fv⁰.a⁴ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +// :de +:sr +:sir +fun get = fv.a +val fv = Foo(1) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun get⁰::get¹ ‹flow:get⁰› = fv¹^get¹.a⁴, val fv¹::fv² ‹flow:fv⁰› = Foo⁴^fv²(1)‹app⁵› +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let tmp; +//│ define get⁰ as fun get¹() { return fv⁰.a⁰ }; +//│ set tmp = Foo⁰(1); +//│ define fv¹ as val fv⁰ = tmp; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ fv = Foo(1) + +// :fixme +:sr +:sir +:expect 1 +get +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ get⁰ +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ return get¹() +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + + +:sr +fun foo(x) = + x.a + x.b +foo(f) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo⁰::foo¹(x⁰) ‹flow:foo⁰› = +(x⁰.a⁴, x⁰.b⁰)‹app⁶›, foo⁰(f²)‹app⁷› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 3 + + +// :elt +:sr +class Bar(b) with + val foo = Foo(b) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Bar⁰ +//│ class Bar¹::Bar²(b²) { +//│ @private val b³::b⁴ ‹flow:b¹› = b²^b⁴, +//│ val foo²::foo³ ‹flow:foo¹› = Foo⁴^foo³(⟨Bar².this.⟩b³^foo³)‹app⁸› +//│ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// :de +let y = Bar(123) +//│ y = Bar(_) + +:sr +y.foo +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ y⁰.foo² +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Foo(123) + +// :de +:sr +y.foo.a +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ y⁰.foo².a⁴ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 123 + + +:sjs +:sr +:rt +// :de +let f = new Foo(1) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let f³, f³ = new Foo⁴(1) +//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— +//│ Blk: +//│ stats = Ls of +//│ LetDecl: +//│ sym = f⁰ +//│ DefineVar: +//│ sym = f⁰ +//│ rhs = New: +//│ cls = MemberRef of member:Foo¹ +//│ args = Ls of +//│ Tup of Ls of +//│ Fld: +//│ term = Lit of IntLit of 1 +//│ res = Lit of UnitLit of false +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let f2; f2 = globalThis.Object.freeze(new Foo3.class(1)); +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = Foo(1) + + +let f = new Foo +//│ f = fun f + +let g = f(1) +//│ g = Foo(1) + +:e +g(2) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.237: g(2) +//│ ║ ^^^^ +//│ ╟── Instance of class 'Foo' cannot receive more argument lists. +//│ ║ l.233: let g = f(1) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] TypeError: g is not a function + +:e +let f = new Foo() +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.247: let f = new Foo() +//│ ║ ^^^^^^^^^ +//│ ╟── Class 'Foo' expected 1 argument, but got 0 +//│ ║ l.84: class Foo(val a) { +//│ ╙── ^^^ +//│ f = Foo(undefined) + + +:e +new 42 +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.258: new 42 +//│ ║ ^^^^^^ +//│ ╟── Invalid class expression: integer literal +//│ ║ l.258: new 42 +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +// :de +:sr +:sjs +// class Foo(pred) { // TODO +class Foo(pred) with { + // val r = pred.id(42) + pred.id(42) +} +let f = new Foo(Predef) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo⁶ +//│ class Foo⁷::Foo⁸(pred⁰) { +//│ @private val pred¹::pred² ‹flow:pred⁰› = pred⁰^pred², +//│ ⟨Foo⁸.this.⟩pred¹.id⁰(42)‹app⁹› +//│ }, +//│ let f⁴, +//│ f⁴ = new Foo⁷(Predef⁰) +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let Foo5, f6; +//│ Foo5 = function Foo(pred) { +//│ return globalThis.Object.freeze(new Foo.class(pred)); +//│ }; +//│ (class Foo4 { +//│ static { +//│ Foo5.class = this +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Foo", [null]]; +//│ }); +//│ f6 = globalThis.Object.freeze(new Foo5.class(Predef)); +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = Foo(_) + + +:sr +class Foo(pred) { + pred.id(1) + pred.id(2) +} +let f = Foo(Predef) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo⁹ +//│ class Foo¹⁰::Foo¹¹(pred³) { +//│ @private val pred⁴::pred⁵ ‹flow:pred¹› = pred³^pred⁵, +//│ ⟨Foo¹¹.this.⟩pred⁴.id⁰(1)‹app¹⁰›, +//│ ⟨Foo¹¹.this.⟩pred⁴.id⁰(2)‹app¹¹› +//│ }, +//│ let f⁵, +//│ f⁵ = Foo¹⁰(Predef⁰)‹app¹²› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = Foo(_) + +:sr +class Foo(pred) { + val r = pred.id(42) + fun callId(x) = pred.id(x) +} +let f = new Foo(Predef) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo¹² +//│ class Foo¹³::Foo¹⁴(pred⁶) { +//│ @private val pred⁷::pred⁸ ‹flow:pred²› = pred⁶^pred⁸, +//│ val r⁰::r¹ ‹flow:r⁰› = ⟨Foo¹⁴.this.⟩pred⁷^r¹.id⁰(42)‹app¹³›, +//│ fun callId⁰::callId¹(x¹) ‹flow:callId⁰› = ⟨Foo¹⁴.this.⟩pred⁷^callId¹.id⁰(x¹)‹app¹⁴› +//│ }, +//│ let f⁶, +//│ f⁶ = new Foo¹³(Predef⁰) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ f = Foo(_) + + + diff --git a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls new file mode 100644 index 0000000000..37e9869710 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls @@ -0,0 +1,183 @@ +:. + + +:sr +class Foo(val a) +object Bar { val b = 123 } +class Baz(c) { fun b = c } +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Foo⁰ +//│ class Foo¹::Foo²(val a⁰) { +//│ val a¹::a² ‹flow:a⁰› = a⁰^a² +//│ }, +//│ object Bar⁰::Bar¹ { +//│ val b⁰::b¹ ‹flow:b⁰› = 123 +//│ }, +//│ fun Baz⁰ +//│ class Baz¹::Baz²(c⁰) { +//│ @private val c¹::c² ‹flow:c⁰› = c⁰^c², +//│ fun b²::b³ ‹flow:b¹› = ⟨Baz².this.⟩c¹^b³ +//│ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +let x = Foo(Bar) +//│ x = Foo(Bar { b: 123, class: object Bar }) + +// TODO: avoid the `let scrut⁰` like in old resl. + +// :dl +// :de +:sr +// :elt +// :sir +// :sjs +if x is + Foo(y) then y.b +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ if { let scrut⁰ = x⁰; scrut⁰ is Foo¹(_ as y⁰) then y⁰.b⁰ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 123 + +:e +if x is + Foo(y) then y.id +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.44: Foo(y) then y.id +//│ ║ ^^^^ +//│ ╟── Object 'Bar' does not contain member 'id' +//│ ║ l.6: object Bar { val b = 123 } +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +let bz = Baz(Bar) +//│ bz = Baz(_) + +let x2 = Foo(bz) +//│ x2 = Foo(Baz(_)) + +:sr +if x2 is + Foo(y) then y.b +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ if { let scrut¹ = x2⁰; scrut¹ is Foo¹(_ as y¹) then y¹.b² } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Bar { b: 123, class: object Bar } + + +// :de +:sr +fun test = + case + Foo(y) then y +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun test⁰::test¹ ‹flow:test⁰› = (caseScrut⁰) => if { caseScrut⁰ is Foo¹^test¹(_ as y²) then y² } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:sr +test(x).b +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ test⁰(x⁰)‹app⁰›.b⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 123 + +// :de +:fixme +:sr +test(x2).b +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ test⁰(x2⁰)‹app¹›.b‹b², b⁰› +//│ ╔══[COMPILATION ERROR] This selection of member 'b' is ambiguous, as it has multiple resolved targets +//│ ║ l.88: test(x2).b +//│ ║ ^^^^^^^^^^ +//│ ╟── target: function 'b' in overloaded function and class 'Baz' +//│ ║ l.7: class Baz(c) { fun b = c } +//│ ║ ^^^^^^^^^ +//│ ╟── target: value 'b' in object 'Bar' +//│ ║ l.6: object Bar { val b = 123 } +//│ ╙── ^^^^^^^^^^^ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +let bz = Baz(Bar) +//│ bz = Baz(_) + +:todo +fun test(x) = + if x is + Foo(y) then y.b +test(bz) +//│ ╔══[COMPILATION ERROR] This selection of member 'b' has no resolved target +//│ ║ l.110: Foo(y) then y.b +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] Error: match error + + +:todo +set x = bz +//│ ═══[COMPILATION ERROR] Unexpected left-hand side in assignment (reference) +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:breakme // Note: if compiled together with it, this would make the previous code ambiguous +x = bz + + +let x = if maybe then Bar else bz +//│ x = Bar { b: 123, class: object Bar } + +:e +x.b +//│ ╔══[COMPILATION ERROR] This selection of member 'b' is ambiguous, as it has multiple resolved targets +//│ ║ l.131: x.b +//│ ║ ^^^ +//│ ╟── target: function 'b' in overloaded function and class 'Baz' +//│ ║ l.7: class Baz(c) { fun b = c } +//│ ║ ^^^^^^^^^ +//│ ╟── target: value 'b' in object 'Bar' +//│ ║ l.6: object Bar { val b = 123 } +//│ ╙── ^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:todo +if x is + Baz as y then y.b +//│ /!!!\ Uncaught error: scala.MatchError: Constructor(MemberRef(member:Baz),None) (of class hkmc2.semantics.Pattern$Constructor) + +:todo +if x is + Baz as y then y.b + Bar as z then z.b +//│ /!!!\ Uncaught error: scala.MatchError: Constructor(MemberRef(member:Baz),None) (of class hkmc2.semantics.Pattern$Constructor) + + + + +:silent +let x2 = Foo(Predef) + +if x2 is + Foo(y2) then y2.id +//│ = fun id + + +:todo +if Foo(Predef) is + Foo(y) then y.id +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + + +:todo +val f = case + Foo(y) then y.b +//│ ╔══[COMPILATION ERROR] This selection of member 'b' has no resolved target +//│ ║ l.173: Foo(y) then y.b +//│ ╙── ^^^ +//│ f = fun + + + + + + diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls new file mode 100644 index 0000000000..20517cc6e8 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -0,0 +1,183 @@ +:. + + +:sir +:soir +:sjs +class Bar(a) +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Bar⁰ as class Bar² { +//│ private val a⁰; +//│ constructor Bar¹(a) { @private define a¹ as val a⁰ = a; end } +//│ }; +//│ end +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ define Bar⁰ as class Bar² { constructor Bar¹ }; end +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let Bar1; +//│ Bar1 = function Bar(a) { +//│ return globalThis.Object.freeze(new Bar.class(a)); +//│ }; +//│ (class Bar { +//│ static { +//│ Bar1.class = this +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Bar", [null]]; +//│ }); +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:sr +:rt +:sir +:soir +:sjs +class Bar(a) with + let x = a + print(x) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun Bar⁰ +//│ class Bar¹::Bar²(a⁰) { +//│ @private val a¹::a² ‹flow:a⁰› = a⁰^a², +//│ let x⁰, +//│ x⁰ = ⟨Bar².this.⟩a¹, +//│ ⟨Predef⁰.⟩print⁰(x⁰)‹app⁰› +//│ } +//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— +//│ Blk: +//│ stats = Ls of +//│ Parameterized: +//│ kind = Cls +//│ sym = class:Bar³ +//│ bsym = member:Bar⁴ +//│ ctorSym = S of term:Bar⁵ +//│ params = ParamList: +//│ params = Ls of +//│ Param: +//│ sym = a² +//│ modulefulness = Modulefulness of N +//│ body = ObjBody of Blk: +//│ stats = Ls of +//│ TermDefinition: +//│ k = ImmutVal +//│ sym = member:a³ +//│ tsym = term:Bar³/a⁴ +//│ body = S of Capture: +//│ base = SimpleRef of a² +//│ thru = term:Bar³/a⁴ +//│ modulefulness = Modulefulness of N +//│ annotations = Ls of +//│ Modifier of keyword 'private' +//│ LetDecl: +//│ sym = term:Bar³/x⁰ +//│ DefineVar: +//│ sym = term:Bar³/x⁰ +//│ rhs = SynthSel{sym=member:a³}: +//│ prefix = SelfRef of class:Bar³ +//│ nme = Ident of "a" +//│ res = App: +//│ lhs = SynthSel{sym=member:print⁰}: +//│ prefix = MemberRef of member:Predef⁰ +//│ nme = Ident of "print" +//│ rhs = Tup of Ls of +//│ Fld: +//│ term = MemberRef of term:Bar³/x⁰ +//│ res = Lit of UnitLit of false +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Bar⁴ as class Bar³ { +//│ private val a⁴; +//│ private val x⁰; +//│ constructor Bar⁵(a) { +//│ @private +//│ define a³ as val a⁴ = a; +//│ set Bar³.this.x⁰ = Bar³.this.a⁴; +//│ do Predef¹.print¹(Bar³.this.x⁰); +//│ end +//│ } +//│ }; +//│ end +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ define Bar⁴ as class Bar³ { +//│ private val a⁴; +//│ private val x⁰; +//│ constructor Bar⁵(a) { +//│ @private +//│ define a³ as val a⁴ = a; +//│ set Bar³.this.x⁰ = Bar³.this.a⁴; +//│ do Predef¹.print¹(Bar³.this.x⁰); +//│ end +//│ } +//│ }; +//│ end +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let Bar3; +//│ Bar3 = function Bar(a) { +//│ return globalThis.Object.freeze(new Bar.class(a)); +//│ }; +//│ (class Bar2 { +//│ static { +//│ Bar3.class = this +//│ } +//│ constructor(a) { +//│ this.#a = a; +//│ this.#x = this.#a; +//│ Predef.print(this.#x); +//│ } +//│ #a; +//│ #x; +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Bar", [null]]; +//│ }); +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +Bar(123) +//│ > 123 +//│ = Bar(_) + + +:sir +:soir +:sjs +class Bar with + private val a = 1 +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Bar⁶ as class Bar⁷ { +//│ private val a⁵; +//│ constructor() { @private define a⁶ as val a⁵ = 1; end } +//│ }; +//│ end +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ define Bar⁶ as class Bar⁷; end +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let Bar5; +//│ (class Bar4 { +//│ static { +//│ Bar5 = this +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Bar"]; +//│ }); +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + + +:sr +fun foo(x) = + if x is + [y] and y > 0 then 1 + 0 then 2 + else 3 +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo⁰::foo¹(x¹) ‹flow:foo⁰› = if { +//│ let scrut⁰ = x¹; +//│ scrut⁰ is [_ as y⁰] and { +//│ let scrut¹ = >(y⁰, 0)‹app¹›; +//│ scrut¹ is true then 1 +//│ }; +//│ scrut⁰ is 0 then 2; +//│ else 3 +//│ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + diff --git a/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls new file mode 100644 index 0000000000..d2b08ed174 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls @@ -0,0 +1,50 @@ +:. + + +:sr +module A { + val u = 1 +} +module B { + val u = 2 +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ module A⁰::A¹ { val u⁰::u¹ ‹flow:u⁰› = 1 }, module B⁰::B¹ { val u²::u³ ‹flow:u¹› = 2 } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// :de +val foo = case x then x +//│ foo = fun + +// :de +:sr +foo(A).u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ foo⁰(A⁰)‹app⁰›.u⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +foo(A).u +//│ = 1 + +:fixme // preserve context sensitivity +// * We now preserve context sensitivity +:sr +foo(B).u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ foo⁰(B⁰)‹app¹›.u‹u², u⁰, u⁰› +//│ ╔══[COMPILATION ERROR] This selection of member 'u' is ambiguous, as it has multiple resolved targets +//│ ║ l.33: foo(B).u +//│ ║ ^^^^^^^^ +//│ ╟── target: value 'u' in module 'B' +//│ ║ l.9: val u = 2 +//│ ║ ^^^^^^^^^ +//│ ╟── target: value 'u' in module 'A' +//│ ║ l.6: val u = 1 +//│ ╙── ^^^^^^^^^ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + + + diff --git a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls new file mode 100644 index 0000000000..bb9d74cd9c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls @@ -0,0 +1,153 @@ +:. + + +:elt +:sr +fun foo = 1 +//│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— +//│ Blk: +//│ stats = Ls of +//│ TermDefinition: +//│ k = Fun +//│ sym = member:foo⁰ +//│ tsym = term:foo¹ +//│ body = S of Lit of IntLit of 1 +//│ modulefulness = Modulefulness of N +//│ res = Lit of UnitLit of false +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ fun foo⁰::foo¹ ‹flow:foo⁰› = 1 +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +let p = Predef +//│ p = class Predef { +//│ Symbols: Symbols { +//│ prettyPrint: Symbol("mlscript.prettyPrint"), +//│ definitionMetadata: Symbol("mlscript.definitionMetadata"), +//│ class: object Symbols +//│ }, +//│ Sub: class Sub, +//│ Eq: class Eq, +//│ Refl: Refl, +//│ js_assert: fun assert, +//│ foldl: fun fold, +//│ meta: class meta +//│ } + +p.id +//│ = fun id + + +:sr +// :de +val p = Predef +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ val p⁰::p¹ ‹flow:p⁰› = Predef⁰^p¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ p = class Predef { +//│ Symbols: Symbols { +//│ prettyPrint: Symbol("mlscript.prettyPrint"), +//│ definitionMetadata: Symbol("mlscript.definitionMetadata"), +//│ class: object Symbols +//│ }, +//│ Sub: class Sub, +//│ Eq: class Eq, +//│ Refl: Refl, +//│ js_assert: fun assert, +//│ foldl: fun fold, +//│ meta: class meta +//│ } + +:sr +// :de +p.id +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ p⁰.id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + +:sr +:de +module M with + val p = Predef +//│ Elab block List(TypeDef(Mod,InfixApp(Ident(M),Keywrd(keyword 'with'),Block(List(TermDef(ImmutVal,Ident(p),Some([...] LocalScope(block:7) +//│ | Processing module/object definition Ident(M) +//│ | | Companion: None +//│ | | Elab block List(TermDef(ImmutVal,Ident(p),Some(Ident(Predef)))) InnerScope(module:M) +//│ | | | Processing term definition Ident(p) +//│ | | | Elab term Ident(Predef) +//│ | | | | Elab subterm Ident(Predef) +//│ | | | | ~> member:Predef^term:M⁰/p⁰ +//│ | | | ~> member:Predef^term:M⁰/p⁰ +//│ | | ~> { val member:p = member:Predef^term:M⁰/p⁰; } +//│ ~> { Mod M ObjBody(Blk(List(TermDefinition(ImmutVal,member:p,term:M/p,List(),None,None,Some(Capture(MemberRef(member:Predef),term:M/p)),‹›,Modulefulness(None),List(),None)),Lit(UnitLit(false)))); } +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ module M⁰::M¹ { val p²::p³ ‹flow:p¹› = Predef⁰^p³ } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:sr +:de +M.p.id +//│ Elab block List(Sel(Sel(Ident(M),Ident(p)),Ident(id))) LocalScope(block:8) +//│ | Elab term Sel(Sel(Ident(M),Ident(p)),Ident(id)) +//│ | | Elab subterm Sel(Sel(Ident(M),Ident(p)),Ident(id)) +//│ | | | Elab subterm Sel(Ident(M),Ident(p)) +//│ | | | | Elab subterm Ident(M) +//│ | | | | ~> member:M +//│ | | | | newSel? sel = member:M.p +//│ | | | | listenTerm: trm = member:M +//│ | | | | listen: trm = member:M +//│ | | | | listenBMS: bms = module +//│ | | | | listenedBMS: bms = module +//│ | | | | fromBMS: bms = member:M¹, sh = module:M⁰, flow = M², markss = List() +//│ | | | | .exit MarkedShape (module:M⁰, module:M⁰, M²) +//│ | | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? +//│ | | | | = MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) +//│ | | | | PUSH MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) +//│ | | | | newSel: sel = member:M.p, shape = MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) +//│ | | | | newSel member: bms = member:p¹, mss = List(↗⟨module:M⁰⟩%⟨M²⟩ϵ) +//│ | | | ~> member:M.p +//│ | | | newSel? sel = member:M.p.id +//│ | | | listenTerm: trm = member:M.p +//│ | | | listen: trm = member:M.p +//│ | | | listenBMS: bms = value +//│ | | | listenedBMS: bms = value +//│ | | | listenTerm: td.body = member:Predef^term:M⁰/p⁰ +//│ | | | listenTerm: trm = member:Predef^term:M⁰/p⁰ +//│ | | | listen: trm = member:Predef^term:M⁰/p⁰ +//│ | | | listenTerm: trm = member:Predef +//│ | | | listen: trm = member:Predef +//│ | | | listenBMS: bms = module +//│ | | | listenedBMS: bms = module +//│ | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() +//│ | | | .exit MarkedShape (module:Predef¹, module:Predef¹, Predef²) +//│ | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? +//│ | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | PUSH MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ), flow = ⋅p⁰, markss = List(↗⟨module:M⁰⟩%⟨M²⟩ϵ) +//│ | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ), term:M⁰/p⁰, ⋅p⁰) +//│ | | | | class hkmc2.semantics.TermSymbol Some(module:M⁰) +//│ | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | .exit MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) (↗⟨module:M⁰⟩%⟨M²⟩ϵ) +//│ | | | | .exit MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) (ϵ) +//│ | | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ), module:M⁰, M²) +//│ | | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? +//│ | | | | = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | MATCH MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) for term:M⁰/p⁰ % ⋅p⁰ +//│ | | | newSel: sel = member:M.p.id, shape = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | newSel member: bms = member:id⁰, mss = List(↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | ~> member:M.p.id +//│ | ~> member:M.p.id +//│ ~> { member:M.p.id } +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ M⁰.p².id⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = fun id + + + + + diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/.mls b/hkmc2/shared/src/test/mlscript/newres/loose/.mls new file mode 100644 index 0000000000..f84908e26a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/loose/.mls @@ -0,0 +1,2 @@ +:.. +#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Basics.mls new file mode 100644 index 0000000000..71507cc966 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Basics.mls @@ -0,0 +1,39 @@ +:. + + +:sr +module A { + val u = 1 +} +module B { + val u = 2 +} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ module A⁰::A¹ { val u⁰::u¹ ‹flow:u⁰› = 1 }, module B⁰::B¹ { val u²::u³ ‹flow:u¹› = 2 } +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:sr +let m = A +m.u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let m⁰, m⁰ = A⁰, m⁰.u⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 +//│ m = class A { u: 1 } + +:sr +let m = if hide(true) then A else B +m.u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let m¹, +//│ m¹ = if { +//│ let scrut⁰ = ⟨Predef⁰.⟩hide⁰(true)‹app⁰›; +//│ scrut⁰ is true then A⁰; +//│ else B⁰ +//│ }, +//│ m¹.u‹u², u⁰› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 +//│ m = class A { u: 1 } + + From ad3cd749e0957491f48eca5b923851d2c295f883 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 18 Sep 2026 21:05:00 +0800 Subject: [PATCH 003/228] Cache term shape member lookups by name --- .../scala/hkmc2/semantics/NewResolver.scala | 16 +++---- .../main/scala/hkmc2/semantics/Shape.scala | 48 +++++++++---------- .../src/test/mlscript/newres/Basics.mls | 10 ++++ .../src/test/mlscript/newres/Functions.mls | 45 ++++++++--------- 4 files changed, 65 insertions(+), 54 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 6d7010c6ce..9abfe011df 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -221,7 +221,7 @@ class NewResolver: fs.foreach: (bms, pat) => // bms.onComplete: () => val nme = bms.nme - sh.members.get(nme) match + sh.getMember(nme) match case S((sym, mss)) => val sh = symShapes.getOrElseUpdate((bms, resSym, mss), SymShape(bms, resSym, mss)) // if src.trmHost.shapes.add(sh) then @@ -300,7 +300,7 @@ class NewResolver: log(s"newSel? sel = ${sel.showDbg}") listenTerm(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") - shape.members.get(sel.id.name) match + shape.getMember(sel.id.name) match case S((bms, mss)) => log(s"newSel member: bms = ${bms.showDbg}, mss = ${mss.map(_.showDbg)}") sel.resolvedMembers ::= bms @@ -347,23 +347,23 @@ class NewResolver: zipArgs(mss, ps.params, ps.restParam, args.fields, src, dsh) case _ => ??? // TODO: mv to NewShape def - lazy val members: Map[Str, MemberInfo] = + protected def getMemberImpl(name: Str): Opt[MemberInfo] = receiver match case ds: DefnShape => ds.defn match case cd: ClassDef => - cd.body.members.mapValues(_ -> ss.markss).toMap + cd.body.members.get(name).map(_ -> ss.markss) case td: TermDefinition => td.tsym match case ccs: ClassCtorSymbol => ccs.associatedCls.defn.getOrElse(die // TODO - ).body.members.mapValues(_ -> ss.markss).toMap + ).body.members.get(name).map(_ -> ss.markss) case _ => - Map.empty + N case _ => - Map.empty + N case _ => - Map.empty + N ) if nw.shapes.add(sh) then nw.shapeListeners.foreach(listener => listener(sh)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 5d1b332223..ed11b70664 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -5,6 +5,7 @@ import hkmc2.utils.*, shorthands.* import syntax.* import hkmc2.document.* import hkmc2.document.Document.* +import scala.collection.mutable sealed trait Shape extends ShapeLike: @@ -61,11 +62,8 @@ object Marked: end Marked case class MarkedShape(sh: NonMarkedShape, mark: SomeMarks) extends TermShape: - lazy val members: Map[Str, MemberInfo] = - // sh.members.view.mapValues(m => MarkedShape.exit(m, mark)).toMap - // sh.members // FIXME: add marks to tuple result - sh.members.mapValues(_.mapSecond(_ ::: mark :: Nil)).toMap - // sh.members.mapValues(_.mapSecond(mark :: _)).toMap + protected def getMemberImpl(name: Str): Opt[MemberInfo] = + sh.getMember(name).map(_.mapSecond(_ ::: mark :: Nil)) def describe: Str = sh.describe def toLoc: Opt[Loc] = sh.toLoc object MarkedShape: @@ -124,7 +122,11 @@ object MarkedShape: end MarkedShape sealed trait TermShape extends Shape: - def members: Map[Str, MemberInfo] + // Cache both hits and misses per shape, without materializing all inherited members. + private val membersCache = mutable.Map.empty[Str, Opt[MemberInfo]] + final def getMember(name: Str): Opt[MemberInfo] = + membersCache.getOrElseUpdate(name, getMemberImpl(name)) + protected def getMemberImpl(name: Str): Opt[MemberInfo] def extendsCls(cls: ClassLikeDef): Bool = false @@ -222,13 +224,13 @@ type MemberInfo = (BlockMemberSymbol, Ls[Marks]) class ErrShape(val err: ErrorReport) extends NonAppTermShape: def describe: Str = s"error: ${err.mainMsg}" - def members: Map[Str, MemberInfo] = Map.empty + protected def getMemberImpl(name: Str): Opt[MemberInfo] = N def toLoc: Opt[Loc] = N class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using DebugPrinter) extends NonMarkedShape: - lazy val members: Map[Str, MemberInfo] = + protected def getMemberImpl(name: Str): Opt[MemberInfo] = // An unsaturated term definition is just a concrete function shape - if !isSaturated then Map.empty + if !isSaturated then N else applicationHead match case (ds: DefnShape, mss) => @@ -243,11 +245,11 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using // case _ => ??? // TODO: add softRequire on ction – should not be possible // case cd: ClassDef => cd.ext.fold(Map.empty)(_.members) ++ cd.body.members ds.defn match - case cd: ClassDef => ds.ext.fold(Map.empty)(_.members).mapValues(_.mapSecond(_ ::: mss)).toMap ++ - cd.body.members.mapValues(_ -> mss).toMap + case cd: ClassDef => cd.body.members.get(name).map(_ -> mss).orElse: + ds.ext.flatMap(_.getMember(name)).map(_.mapSecond(_ ::: mss)) case td: TermDefinition => - ds.ext.fold(Map.empty)(_.members).mapValues(_.mapSecond(_ ::: mss)).toMap - case _ => Map.empty + ds.ext.flatMap(_.getMember(name)).map(_.mapSecond(_ ::: mss)) + case _ => N def describe: Str = // s"application of ${receiver.describe}" s"instance of ${applicationHead._1.describe}" @@ -280,8 +282,8 @@ class ThisShape(val defn: Definition) extends NonAppTermShape: // TODO: make it not a TermShape? class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppTermShape: def describe: Str = s"${defn.describe}" - lazy val members: Map[Str, MemberInfo] = - ext.fold(Map.empty)(_.members) ++ defn.body.members.mapValues(_ -> Nil).toMap + protected def getMemberImpl(name: Str): Opt[MemberInfo] = + defn.body.members.get(name).map(_ -> Nil).orElse(ext.flatMap(_.getMember(name))) def toLoc: Opt[Loc] = defn.toLoc class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTermShape: @@ -309,15 +311,12 @@ class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTer }'${defn.bsym.nme}'" // override def toString: String = s"DefnShape(${defn.describe} ${defn.bsym.nme})" override def toString: String = s"DefnShape(${defn.describe})" - lazy val members: Map[Str, MemberInfo] = - // println((ext, ext.map(_.members))) - // ext.fold(Map.empty)(_.members) ++ defn.match + protected def getMemberImpl(name: Str): Opt[MemberInfo] = defn match case defn: ModuleOrObjectDef => - // println(defn.ext.map(_.cls.members)) - ext.fold(Map.empty)(_.members) ++ defn.body.members.mapValues(_ -> Nil).toMap + defn.body.members.get(name).map(_ -> Nil).orElse(ext.flatMap(_.getMember(name))) case defn: TermDefinition => ??? - case _ => Map.empty + case _ => N def toLoc: Opt[Loc] = defn.sym.toLoc /* @@ -331,11 +330,12 @@ class RefinedShape(val base: TermShape, val refinements: Ls[Str -> Term]) extend type IntroTerm = Term.Lit | Term.UnitVal | Term.Tup | Term.Lam | Term.Rcd //| Term.New class IntroShape(val trm: IntroTerm) extends NonAppTermShape: def describe: Str = trm.describe - lazy val members: Map[Str, MemberInfo] = trm match + protected def getMemberImpl(name: Str): Opt[MemberInfo] = trm match + case _: Term.Lit | _: Term.UnitVal => N // TODO: methods on literals case tup: Term.Tup => // tup.fields.iterator.map: ??? - case lam: Term.Lam => Map.empty // TODO: methods on lambdas + case lam: Term.Lam => N // TODO: methods on lambdas case rcd: Term.Rcd => // rcd.stats.iterator.collect: // TODO: handler RcdField, RcdSpread @@ -347,7 +347,7 @@ class IntroShape(val trm: IntroTerm) extends NonAppTermShape: sealed trait LitShape extends NonAppTermShape: self: Term.Lit => - def members: Map[Str, MemberInfo] = Map.empty // TODO: methods on literals, e.g. string methods + protected def getMemberImpl(name: Str): Opt[MemberInfo] = N // TODO: methods on literals, e.g. string methods type ShapePublisher = Publisher[Shape] diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls index 67a590e64a..acd5f2b1ad 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -154,3 +154,13 @@ print(1) //│ > 1 + +:e +().id +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.159: ().id +//│ ║ ^^^^^ +//│ ╟── Unit value does not contain member 'id' +//│ ║ l.159: ().id +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 6f4bac6ffd..0e673cf3bf 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -124,18 +124,19 @@ callid1(Predef) //│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) //│ | listenTerm: trm = 1 //│ | listen: trm = 1 -//│ | .enter 1 (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | .exit MarkedShape (1, term:callid1², callid1¹) +//│ | introShape: intro = Lit(IntLit(1)) +//│ | .enter IntroShape(1) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | .exit MarkedShape (IntroShape(1), term:callid1², callid1¹) //│ | | | class hkmc2.semantics.TermSymbol None -//│ | | = MarkedShape(1, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | .enter MarkedShape(1, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | .enter MarkedShape(1, ↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | | .enter MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) -//│ | | | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false +//│ | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .enter MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .enter MarkedShape(IntroShape(1), ↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | | .enter MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) +//│ | | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false //│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(module:Predef,Some(Predef),ExitMark(term:Predef/id,Some(⋅id),NoMarks))))) //│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) //│ | appShape: td.body = Some(Ref(x)) @@ -160,18 +161,18 @@ callid1(Predef) //│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) //│ | listenTerm: trm = 1 //│ | listen: trm = 1 -//│ | .enter 1 (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | .exit MarkedShape (1, term:callid1², callid1¹) +//│ | .enter IntroShape(1) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | .exit MarkedShape (IntroShape(1), term:callid1², callid1¹) //│ | | | class hkmc2.semantics.TermSymbol None -//│ | | = MarkedShape(1, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | .enter MarkedShape(1, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | .enter MarkedShape(1, ↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | | .enter MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) -//│ | | | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(1, ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false +//│ | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .enter MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .enter MarkedShape(IntroShape(1), ↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | | .enter MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) +//│ | | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false //│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(module:Predef,Some(Predef),ExitMark(term:Predef/id,Some(⋅id),NoMarks))))) //│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) //│ | appShape: td.body = Some(Ref(x)) From 911f5229314406cf65038dabc3e16a1ce6a8c8f8 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 19 Sep 2026 15:52:57 +0800 Subject: [PATCH 004/228] W --- .../scala/hkmc2/semantics/NewResolver.scala | 86 ++++--------------- .../main/scala/hkmc2/semantics/Shape.scala | 19 +++- 2 files changed, 34 insertions(+), 71 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 9abfe011df..4c936ad1b7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -43,7 +43,7 @@ class NewResolver: // * The `FlowSymbol`s are currently used to uniquely identify terms val appShapes: mutable.Map[(TermShape, FlowSymbol), AppShape] = mutable.Map.empty - val newShapes: mutable.Map[(ClassLikeSymbol, FlowSymbol), NewShape] = mutable.Map.empty + val newShapes: mutable.Map[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = mutable.Map.empty val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty @@ -88,16 +88,7 @@ class NewResolver: resolError(src, msg"${funSh.describe.capitalize} expected ${ps.length} ${ "argument".pluralized(ps.length)}, but got ${args.length}" -> funSh.toLoc :: Nil) - - // def patAppShape(lhs: Shape, args: Ls[Pattern], res: Pattern.Constructor): Unit = - // // listen(lhs, sh => - // // sh match - // // case sh: TermShape => - // // zipArgs(sh.unappliedParams.map(_._2), args, N, res.args, res, lhs) - // // case _ => - // // softAssert(res.isErroneous) - // // ) - // ??? + def patApp(lhs: Term, args: Ls[Pattern], res: Pattern.Constructor): Unit = if newResolution then listen(lhs, discardMarks = true): sh => log(s"patApp: lhs = ${lhs.showDbg}, args = ${args.map(_.showDbg)}, res = ${res.showDbg}, sh = ${sh.shwDbg}") @@ -338,33 +329,15 @@ class NewResolver: val cd = cls.defn.get listenExt(cd.ext, extsh => { val dsh = DefnShape(cd, extsh) - val sh = newShapes.getOrElseUpdate((cls, nw.resSym), - new NewShape(dsh, cls, nw.args, nw): - receiver.unappliedParams.lazyZip(argss).foreach: - case ((ps, mss), args) => - args match - case args: Tup => - zipArgs(mss, ps.params, ps.restParam, args.fields, src, dsh) - case _ => ??? - // TODO: mv to NewShape def - protected def getMemberImpl(name: Str): Opt[MemberInfo] = - receiver match - case ds: DefnShape => - ds.defn match - case cd: ClassDef => - cd.body.members.get(name).map(_ -> ss.markss) - case td: TermDefinition => - td.tsym match - case ccs: ClassCtorSymbol => - ccs.associatedCls.defn.getOrElse(die // TODO - ).body.members.get(name).map(_ -> ss.markss) - case _ => - N - case _ => - N - case _ => - N - ) + val sh = newShapes.getOrElseUpdate((cls, ss.markss, nw.resSym), { + dsh.unappliedParams.lazyZip(nw.args).foreach: + case ((ps, mss), args) => + args match + case args: Tup => + zipArgs(mss, ps.params, ps.restParam, args.fields, nw, dsh) + case _ => ??? + NewShape(dsh, cls, ss.markss, nw.args, nw) + }) if nw.shapes.add(sh) then nw.shapeListeners.foreach(listener => listener(sh)) }) @@ -377,14 +350,6 @@ class NewResolver: resolError(nw, msg"Invalid class expression: ${nw.cls.describe}" -> nw.cls.toLoc :: Nil) - // def symShape(sym: BlockMemberSymbol, res: Ref): Unit = - // val sh = symShapes.getOrElseUpdate(sym, { - // log(s"symShape: sym = $sym, res = $res") - // SymShape(sym) - // }) - // if res.shapes.add(sh) then - // res.shapeListeners.foreach(listener => listener(sh)) - def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term): DefineVar = if newResolution then sym match case sym: TermSymbol => @@ -484,30 +449,11 @@ class NewResolver: def listenTerm(trm: Term)(listener: TermShape => Unit): Unit = log(s"listenTerm: trm = ${trm.showDbg}") - trm match - /* - // * Synthetic selections are not really selections from the POV of the resolver. - // * Eg: a plain member reference or plain reference to imported symbol - // * Later, we should make Terms more closely aligned wiht the source and remove SynthSel - case ss: SynthSel => - ss.sym match - case S(ts: TermSymbol) => - ??? - ts.defn.get.body match - case S(body) => - listenTerm(body, listener) - case N => - ??? // TODO error? use sig - case S(bms: BlockMemberSymbol) => - fromBMS(bms, listener, trm) - case N => ??? - */ - case _ => - listen(trm): - case sh: TermShape => - listener(sh) - case ss: SymShape => - fromBMS(ss.sym, ss.resSym, ss.markss, listener, trm) + listen(trm): + case sh: TermShape => + listener(sh) + case ss: SymShape => + fromBMS(ss.sym, ss.resSym, ss.markss, listener, trm) def listen(trm: Term, discardMarks: Bool = false)(listener: Shape => Unit): Unit = log(s"listen: trm = ${trm.showDbg}") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index ed11b70664..d8884c4709 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -257,7 +257,24 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using override def toString: String = s"AppShape($receiver, ${args.showDbg})" // def target: Opt[AppTarget] -abstract class NewShape(val receiver: TermShape, val cls: ClassLikeSymbol, val argss: Ls[Term], val src: Term.New)(using DebugPrinter) extends NonMarkedShape: +class NewShape(val receiver: TermShape, val cls: ClassLikeSymbol, clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New)(using DebugPrinter) extends NonMarkedShape: + protected def getMemberImpl(name: Str): Opt[MemberInfo] = + receiver match + case ds: DefnShape => + ds.defn match + case cd: ClassDef => + cd.body.members.get(name).map(_ -> clsMarks) + case td: TermDefinition => + td.tsym match + case ccs: ClassCtorSymbol => + ccs.associatedCls.defn.getOrElse(die // TODO + ).body.members.get(name).map(_ -> clsMarks) + case _ => + N + case _ => + N + case _ => + N def describe: Str = // s"instantiation of ${receiver.describe}" s"instance of ${cls.defn.get.describeRef}" From 7ead61903566ff21016dbc1e3e6e9d4f079878a8 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 19 Sep 2026 19:29:58 +0800 Subject: [PATCH 005/228] Preserve context marks when returning from lambdas --- .../scala/hkmc2/semantics/NewResolver.scala | 5 +- .../src/test/mlscript/newres/PatMat.mls | 20 ++------ .../src/test/mlscript/newres/ValCtxSens.mls | 50 ++++++++++++++++--- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 9abfe011df..cf238b670f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -286,10 +286,11 @@ class NewResolver: go(body, mss) case _ => softAssert(res.isErroneous) - case (sh: IntroShape, _) => + case (sh: IntroShape, mss) => sh.trm match case Lam(params, body) => - go(body, Nil) + // Exit the same context that zipArgs enters, filtering results from other uses. + go(body, mss) case _ => softAssert(res.isErroneous) case _ => diff --git a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls index 37e9869710..a3688646dd 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls @@ -83,22 +83,12 @@ test(x).b //│ = 123 // :de -:fixme :sr test(x2).b //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ test⁰(x2⁰)‹app¹›.b‹b², b⁰› -//│ ╔══[COMPILATION ERROR] This selection of member 'b' is ambiguous, as it has multiple resolved targets -//│ ║ l.88: test(x2).b -//│ ║ ^^^^^^^^^^ -//│ ╟── target: function 'b' in overloaded function and class 'Baz' -//│ ║ l.7: class Baz(c) { fun b = c } -//│ ║ ^^^^^^^^^ -//│ ╟── target: value 'b' in object 'Bar' -//│ ║ l.6: object Bar { val b = 123 } -//│ ╙── ^^^^^^^^^^^ +//│ test⁰(x2⁰)‹app¹›.b² //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = Bar { b: 123, class: object Bar } let bz = Baz(Bar) @@ -110,7 +100,7 @@ fun test(x) = Foo(y) then y.b test(bz) //│ ╔══[COMPILATION ERROR] This selection of member 'b' has no resolved target -//│ ║ l.110: Foo(y) then y.b +//│ ║ l.100: Foo(y) then y.b //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -130,7 +120,7 @@ let x = if maybe then Bar else bz :e x.b //│ ╔══[COMPILATION ERROR] This selection of member 'b' is ambiguous, as it has multiple resolved targets -//│ ║ l.131: x.b +//│ ║ l.121: x.b //│ ║ ^^^ //│ ╟── target: function 'b' in overloaded function and class 'Baz' //│ ║ l.7: class Baz(c) { fun b = c } @@ -172,7 +162,7 @@ if Foo(Predef) is val f = case Foo(y) then y.b //│ ╔══[COMPILATION ERROR] This selection of member 'b' has no resolved target -//│ ║ l.173: Foo(y) then y.b +//│ ║ l.163: Foo(y) then y.b //│ ╙── ^^^ //│ f = fun diff --git a/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls index d2b08ed174..82cea63b8c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls @@ -27,24 +27,60 @@ foo(A).u foo(A).u //│ = 1 -:fixme // preserve context sensitivity // * We now preserve context sensitivity :sr +:expect 2 foo(B).u //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ foo⁰(B⁰)‹app¹›.u‹u², u⁰, u⁰› +//│ foo⁰(B⁰)‹app¹›.u² +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 2 + +:expect 1 +foo(A).u +//│ = 1 + +:expect 2 +foo(B).u +//│ = 2 + + +// Capturing the function in a value alias still loses context sensitivity, as in CtxSens.mls. +val alias = foo +//│ alias = fun + +:expect 2 +alias(B).u +//│ = 2 + +:todo // The captured reference to foo is shared by both calls of alias. +:sr +alias(A).u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ alias⁰(A⁰)‹app²›.u‹u⁰, u²› //│ ╔══[COMPILATION ERROR] This selection of member 'u' is ambiguous, as it has multiple resolved targets -//│ ║ l.33: foo(B).u -//│ ║ ^^^^^^^^ -//│ ╟── target: value 'u' in module 'B' -//│ ║ l.9: val u = 2 -//│ ║ ^^^^^^^^^ +//│ ║ l.58: alias(A).u +//│ ║ ^^^^^^^^^^ //│ ╟── target: value 'u' in module 'A' //│ ║ l.6: val u = 1 +//│ ║ ^^^^^^^^^ +//│ ╟── target: value 'u' in module 'B' +//│ ║ l.9: val u = 2 //│ ╙── ^^^^^^^^^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +// Returning a captured value +val captured = case x then A +//│ captured = fun + +:expect 1 +captured(B).u +//│ = 1 + +:expect 1 +captured(A).u +//│ = 1 From 84e94b1d16f486ed62d1e28c5f3d8bfddfd7fe84 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Mon, 21 Sep 2026 12:08:09 +0800 Subject: [PATCH 006/228] W --- .../src/test/mlscript/newres/ValCtxSens.mls | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls index d2b08ed174..abbcc4d703 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls @@ -12,11 +12,10 @@ module B { //│ module A⁰::A¹ { val u⁰::u¹ ‹flow:u⁰› = 1 }, module B⁰::B¹ { val u²::u³ ‹flow:u¹› = 2 } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -// :de -val foo = case x then x + +val foo = x => x //│ foo = fun -// :de :sr foo(A).u //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— @@ -34,7 +33,7 @@ foo(B).u //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ foo⁰(B⁰)‹app¹›.u‹u², u⁰, u⁰› //│ ╔══[COMPILATION ERROR] This selection of member 'u' is ambiguous, as it has multiple resolved targets -//│ ║ l.33: foo(B).u +//│ ║ l.32: foo(B).u //│ ║ ^^^^^^^^ //│ ╟── target: value 'u' in module 'B' //│ ║ l.9: val u = 2 @@ -46,5 +45,37 @@ foo(B).u //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +// :de +val foo = case x then x +//│ foo = fun + +// :de +:sr +foo(A).u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ foo¹(A⁰)‹app²›.u⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +foo(A).u +//│ = 1 + +:fixme // preserve context sensitivity +// * We now preserve context sensitivity +:sr +foo(B).u +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ foo¹(B⁰)‹app³›.u‹u², u⁰, u⁰› +//│ ╔══[COMPILATION ERROR] This selection of member 'u' is ambiguous, as it has multiple resolved targets +//│ ║ l.66: foo(B).u +//│ ║ ^^^^^^^^ +//│ ╟── target: value 'u' in module 'B' +//│ ║ l.9: val u = 2 +//│ ║ ^^^^^^^^^ +//│ ╟── target: value 'u' in module 'A' +//│ ║ l.6: val u = 1 +//│ ╙── ^^^^^^^^^ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From fa0da12bbb8e1827518dae52c1dbbee84c9c5c98 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Mon, 21 Sep 2026 13:18:17 +0800 Subject: [PATCH 007/228] W --- .../main/scala/hkmc2/codegen/Lowering.scala | 7 +-- .../scala/hkmc2/semantics/NewResolver.scala | 1 + .../src/main/scala/hkmc2/semantics/Term.scala | 8 +-- .../src/test/mlscript/newres/Basics.mls | 16 +++--- .../src/test/mlscript/newres/Builtins.mls | 34 ++++++++++++ .../src/test/mlscript/newres/Functions.mls | 52 +++++++++---------- .../src/test/mlscript/newres/WIPTest.mls | 42 +++++++-------- 7 files changed, 96 insertions(+), 64 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/Builtins.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 52b5e2cf9f..f9745d9870 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -431,11 +431,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): cls.defn match case S(clsDef: ClassLikeDef) => resl match - case MemberRef(bms: BlockMemberSymbol) => - k(Value.MemberRef(bms, clsDef.sym)) - case MemberRef(tr: TermSymbol) => - ??? - case MemberRef(_) => die // FIXME: should make this unreachable + case ref: MemberRef => + subTerm(ref)(k) case sel: NewSel => subTerm(sel.prefix): pre => k(Select(pre, memberIdent(sel.id, S(clsDef.bsym)))(S(clsDef.sym))(false)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 5a9331dff1..97c35c69b2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -315,6 +315,7 @@ class NewResolver: nw.cls.withoutCaptures match case trm: NewResolvable => listen(trm): shape => + log(s"resolveNew: res = ${nw.showDbg}, cls = ${trm.showDbg}, shape = ${shape.shwDbg}") def reject = nw.isErroneous = true resolError(nw, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 81923efa15..25078f73bf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -1022,10 +1022,10 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case Term.UnitVal() => "()" case Lit(lit) => lit.idStr case Resolved(t, sym) => t.showPlain - case r @ Ref(symbol) => symbol.toString - case r @ SimpleRef(symbol) => symbol.toString - case r @ MemberRef(symbol) => symbol.toString - case r @ SelfRef(sym) => sym.toString + case r @ Ref(symbol) => symbol.showAsPlain + case r @ SimpleRef(symbol) => symbol.showAsPlain + case r @ MemberRef(symbol) => symbol.showAsPlain + case r @ SelfRef(sym) => sym.showAsPlain case Capture(base, thru) => s"${base.showDbg}^${thru.showDbg}" case App(lhs, rhs) => s"${lhs.showDbg}${rhs.showDbgAsParams}" case RcdField(lhs, rhs) => s"${lhs.showDbg}: ${rhs.showDbg}" diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls index acd5f2b1ad..ace3c5eddb 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -29,10 +29,10 @@ Predef.id //│ | Elab term Sel(Ident(Predef),Ident(id)) //│ | | Elab subterm Sel(Ident(Predef),Ident(id)) //│ | | | Elab subterm Ident(Predef) -//│ | | | ~> member:Predef -//│ | | | newSel? sel = member:Predef.id -//│ | | | listenTerm: trm = member:Predef -//│ | | | listen: trm = member:Predef +//│ | | | ~> member:Predef⁰ +//│ | | | newSel? sel = member:Predef⁰.id +//│ | | | listenTerm: trm = member:Predef⁰ +//│ | | | listen: trm = member:Predef⁰ //│ | | | listenBMS: bms = module //│ | | | listenedBMS: bms = module //│ | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() @@ -40,11 +40,11 @@ Predef.id //│ | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? //│ | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | | | PUSH MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | newSel: sel = member:Predef.id, shape = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | newSel: sel = member:Predef⁰.id, shape = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | | | newSel member: bms = member:id⁰, mss = List(↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | ~> member:Predef.id -//│ | ~> member:Predef.id -//│ ~> { member:Predef.id } +//│ | | ~> member:Predef⁰.id +//│ | ~> member:Predef⁰.id +//│ ~> { member:Predef⁰.id } //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ Predef⁰.id⁰ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/Builtins.mls b/hkmc2/shared/src/test/mlscript/newres/Builtins.mls new file mode 100644 index 0000000000..97a49a86e7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Builtins.mls @@ -0,0 +1,34 @@ +:. + + +// :de +:sr +new Map +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ new Map⁰ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Map(0) {} + +// :de +:sr +new Map() +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ new Map⁰() +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Map(0) {} + +:sr +new Map([]) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ new Map⁰([]) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Map(0) {} + +:sr +new Map([[1, 2], [3, 4]]) +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ new Map⁰([ [ 1, 2 ], [ 3, 4 ] ]) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Map(2) {1 => 2, 3 => 4} + + diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 0e673cf3bf..9f1bb0ff24 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -50,46 +50,46 @@ callid1(Predef) //│ | | Elab subterm App(Sel(Ident(p),Ident(id)),Tup(List(IntLit(1)))) //│ | | | Elab subterm Sel(Ident(p),Ident(id)) //│ | | | | Elab subterm Ident(p) -//│ | | | | ~> p -//│ | | | | newSel? sel = p.id -//│ | | | | listenTerm: trm = p -//│ | | | | listen: trm = p -//│ | | | ~> p.id +//│ | | | | ~> p⁰ +//│ | | | | newSel? sel = p⁰.id +//│ | | | | listenTerm: trm = p⁰ +//│ | | | | listen: trm = p⁰ +//│ | | | ~> p⁰.id //│ | | | Elab subterm Tup(List(IntLit(1))) //│ | | | | Elab term IntLit(1) //│ | | | | | Elab subterm IntLit(1) //│ | | | | | ~> 1 //│ | | | | ~> 1 //│ | | | ~> [1] -//│ | | | listenTerm: trm = p.id -//│ | | | listen: trm = p.id -//│ | | ~> p.id(1) -//│ | ~> p.id(1) +//│ | | | listenTerm: trm = p⁰.id +//│ | | | listen: trm = p⁰.id +//│ | | ~> p⁰.id(1) +//│ | ~> p⁰.id(1) //│ | Elab term App(Ident(callid1),Tup(List(Ident(Predef)))) //│ | | Elab subterm App(Ident(callid1),Tup(List(Ident(Predef)))) //│ | | | Elab subterm Ident(callid1) -//│ | | | ~> member:callid1 +//│ | | | ~> member:callid1⁰ //│ | | | Elab subterm Tup(List(Ident(Predef))) //│ | | | | Elab term Ident(Predef) //│ | | | | | Elab subterm Ident(Predef) -//│ | | | | | ~> member:Predef -//│ | | | | ~> member:Predef -//│ | | | ~> [member:Predef] -//│ | | | listenTerm: trm = member:callid1 -//│ | | | listen: trm = member:callid1 +//│ | | | | | ~> member:Predef⁰ +//│ | | | | ~> member:Predef⁰ +//│ | | | ~> [member:Predef⁰] +//│ | | | listenTerm: trm = member:callid1⁰ +//│ | | | listen: trm = member:callid1⁰ //│ | | | listenBMS: bms = function -//│ | | ~> member:callid1(member:Predef) -//│ | ~> member:callid1(member:Predef) +//│ | | ~> member:callid1⁰(member:Predef⁰) +//│ | ~> member:callid1⁰(member:Predef⁰) //│ | listenedBMS: bms = function //│ | fromBMS: bms = member:callid1⁰, sh = member:callid1⁰, flow = callid1¹, markss = List() //│ | .exit MarkedShape (member:callid1⁰, term:callid1², callid1¹) //│ | | class hkmc2.semantics.TermSymbol None //│ | = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | PUSH MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | appShape: lhs = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ), args = [member:Predef], res = member:callid1(member:Predef) +//│ | appShape: lhs = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ), args = [member:Predef⁰], res = member:callid1⁰(member:Predef⁰) //│ | Zipping ParamList(‹›,List(Param(‹›,p,None,Modulefulness(None))),None) (List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ)) with List(Fld(‹›,MemberRef(member:Predef),None)) -//│ | listenTerm: trm = member:Predef -//│ | listen: trm = member:Predef +//│ | listenTerm: trm = member:Predef⁰ +//│ | listen: trm = member:Predef⁰ //│ | listenBMS: bms = module //│ | listenedBMS: bms = module //│ | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() @@ -101,8 +101,8 @@ callid1(Predef) //│ | | .enter MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) (ϵ) //│ | | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | zipArg: p = p⁰, trm = member:Predef, sh = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) true, false -//│ | newSel: sel = p.id, shape = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | zipArg: p = p⁰, trm = member:Predef⁰, sh = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) true, false +//│ | newSel: sel = p⁰.id, shape = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | newSel member: bms = member:id⁰, mss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | listenBMS: bms = function //│ | listenedBMS: bms = function @@ -120,7 +120,7 @@ callid1(Predef) //│ | | = MarkedShape(member:id⁰, ↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | appShape: lhs = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), args = [1], res = p.id(1) +//│ | appShape: lhs = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), args = [1], res = p⁰.id(1) //│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) //│ | listenTerm: trm = 1 //│ | listen: trm = 1 @@ -181,9 +181,9 @@ callid1(Predef) //│ | appShape isSaturated? true; head? (DefnShape(term definition 'callid1'),List(ExitMark(term:callid1,Some(callid1),NoMarks))) //│ | lhs false List(((p⁰),List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ))) //│ | appShape: td.body = Some(App(NewSel(SimpleRef(p),Ident(id)),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))) -//│ | listenTerm: trm = p.id(1) -//│ | listen: trm = p.id(1) -//│ ~> { fun member:callid1(p⁰) = p.id(1); member:callid1(member:Predef) } +//│ | listenTerm: trm = p⁰.id(1) +//│ | listen: trm = p⁰.id(1) +//│ ~> { fun member:callid1(p⁰) = p⁰.id(1); member:callid1⁰(member:Predef⁰) } //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ fun callid1⁰::callid1¹(p⁰) ‹flow:callid1⁰› = p⁰.id⁰(1)‹app⁰›, callid1⁰(Predef⁰)‹app¹› //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls index bb9d74cd9c..2989e3359a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls +++ b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls @@ -78,9 +78,9 @@ module M with //│ | | | Processing term definition Ident(p) //│ | | | Elab term Ident(Predef) //│ | | | | Elab subterm Ident(Predef) -//│ | | | | ~> member:Predef^term:M⁰/p⁰ -//│ | | | ~> member:Predef^term:M⁰/p⁰ -//│ | | ~> { val member:p = member:Predef^term:M⁰/p⁰; } +//│ | | | | ~> member:Predef⁰^term:M⁰/p⁰ +//│ | | | ~> member:Predef⁰^term:M⁰/p⁰ +//│ | | ~> { val member:p = member:Predef⁰^term:M⁰/p⁰; } //│ ~> { Mod M ObjBody(Blk(List(TermDefinition(ImmutVal,member:p,term:M/p,List(),None,None,Some(Capture(MemberRef(member:Predef),term:M/p)),‹›,Modulefulness(None),List(),None)),Lit(UnitLit(false)))); } //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ module M⁰::M¹ { val p²::p³ ‹flow:p¹› = Predef⁰^p³ } @@ -94,10 +94,10 @@ M.p.id //│ | | Elab subterm Sel(Sel(Ident(M),Ident(p)),Ident(id)) //│ | | | Elab subterm Sel(Ident(M),Ident(p)) //│ | | | | Elab subterm Ident(M) -//│ | | | | ~> member:M -//│ | | | | newSel? sel = member:M.p -//│ | | | | listenTerm: trm = member:M -//│ | | | | listen: trm = member:M +//│ | | | | ~> member:M¹ +//│ | | | | newSel? sel = member:M¹.p +//│ | | | | listenTerm: trm = member:M¹ +//│ | | | | listen: trm = member:M¹ //│ | | | | listenBMS: bms = module //│ | | | | listenedBMS: bms = module //│ | | | | fromBMS: bms = member:M¹, sh = module:M⁰, flow = M², markss = List() @@ -105,19 +105,19 @@ M.p.id //│ | | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? //│ | | | | = MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) //│ | | | | PUSH MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) -//│ | | | | newSel: sel = member:M.p, shape = MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) +//│ | | | | newSel: sel = member:M¹.p, shape = MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) //│ | | | | newSel member: bms = member:p¹, mss = List(↗⟨module:M⁰⟩%⟨M²⟩ϵ) -//│ | | | ~> member:M.p -//│ | | | newSel? sel = member:M.p.id -//│ | | | listenTerm: trm = member:M.p -//│ | | | listen: trm = member:M.p +//│ | | | ~> member:M¹.p +//│ | | | newSel? sel = member:M¹.p.id +//│ | | | listenTerm: trm = member:M¹.p +//│ | | | listen: trm = member:M¹.p //│ | | | listenBMS: bms = value //│ | | | listenedBMS: bms = value -//│ | | | listenTerm: td.body = member:Predef^term:M⁰/p⁰ -//│ | | | listenTerm: trm = member:Predef^term:M⁰/p⁰ -//│ | | | listen: trm = member:Predef^term:M⁰/p⁰ -//│ | | | listenTerm: trm = member:Predef -//│ | | | listen: trm = member:Predef +//│ | | | listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ +//│ | | | listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ +//│ | | | listen: trm = member:Predef⁰^term:M⁰/p⁰ +//│ | | | listenTerm: trm = member:Predef⁰ +//│ | | | listen: trm = member:Predef⁰ //│ | | | listenBMS: bms = module //│ | | | listenedBMS: bms = module //│ | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() @@ -137,11 +137,11 @@ M.p.id //│ | | | | = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | | | = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | | | MATCH MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) for term:M⁰/p⁰ % ⋅p⁰ -//│ | | | newSel: sel = member:M.p.id, shape = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | newSel: sel = member:M¹.p.id, shape = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | | | newSel member: bms = member:id⁰, mss = List(↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | ~> member:M.p.id -//│ | ~> member:M.p.id -//│ ~> { member:M.p.id } +//│ | | ~> member:M¹.p.id +//│ | ~> member:M¹.p.id +//│ ~> { member:M¹.p.id } //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ M⁰.p².id⁰ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 6017aa960583b82697fbd51cb976039a9e28887c Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Mon, 21 Sep 2026 13:40:54 +0800 Subject: [PATCH 008/228] WW --- .../main/scala/hkmc2/codegen/Lowering.scala | 13 +- .../mlscript/newres/OverloadedClasses.mls | 261 ++++++++++++++++++ 2 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index f9745d9870..4c3d84bfef 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -430,12 +430,13 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case cls :: Nil => cls.defn match case S(clsDef: ClassLikeDef) => - resl match - case ref: MemberRef => - subTerm(ref)(k) - case sel: NewSel => - subTerm(sel.prefix): pre => - k(Select(pre, memberIdent(sel.id, S(clsDef.bsym)))(S(clsDef.sym))(false)) + // resl match + // case ref: MemberRef => + // subTerm(ref)(k) + // case sel: NewSel => + // subTerm(sel.prefix): pre => + // k(Select(pre, memberIdent(sel.id, S(clsDef.bsym)))(S(clsDef.sym))(false)) + subTerm(resl)(k) case _ => softAssert(resl.isErroneous, s"Unexpected `new` target: ${cls.showDbg}") compError diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls new file mode 100644 index 0000000000..f2f7f9a296 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls @@ -0,0 +1,261 @@ +:. + + +class Bar { + fun id(x) = x +} +val Bar = new Bar +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: blockOrRcd (Elaborator.scala:1860) +//│ ╔══[COMPILATION ERROR] Illegal overloading of value 'Bar' +//│ ║ l.7: val Bar = new Bar +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── with class of the same name +//│ ║ l.4: class Bar { +//│ ║ ^^^^^^^^^^^ +//│ ║ l.5: fun id(x) = x +//│ ║ ^^^^^^^^^^^^^^^ +//│ ║ l.6: } +//│ ╙── ^ +//│ FAILURE: Unexpected internal error +//│ FAILURE LOCATION: softTODO (JSBuilder.scala:588) +//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true +//│ ╟── The compilation result may be incorrect. +//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:264) +//│ ═══[RUNTIME ERROR] TypeError: Cannot set properties of undefined (setting 'class') +//│ at (REPL16:1:84) +//│ at REPL16:1:302 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) +//│ Bar = undefined + +:sr +:sir +:lot +let x = new Bar +let y = new Bar() +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let x⁰, x⁰ = new Bar⁰, let y⁰, y⁰ = new Bar⁰() +//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— +//│ Program: +//│ main = Assign: +//│ lhs = x⁰ +//│ rhs = Instantiate: +//│ mut = false +//│ cls = MemberRef{disamb=class:Bar⁰}: +//│ bms = member:Bar¹ +//│ disamb = class:Bar⁰ +//│ argss = Ls of +//│ Nil +//│ rest = Assign: \ +//│ lhs = y⁰ +//│ rhs = Instantiate: +//│ mut = false +//│ cls = MemberRef{disamb=class:Bar⁰}: +//│ bms = member:Bar¹ +//│ disamb = class:Bar⁰ +//│ argss = Ls of +//│ Nil +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ set x⁰ = new Bar⁰(); set y⁰ = new Bar⁰(); end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:264) +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'class') +//│ at REPL20:1:54 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) +//│ x = undefined +//│ y = undefined + + +class Bar { + fun id(x) = x +} +fun Bar() = new Bar +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: blockOrRcd (Elaborator.scala:1860) +//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Bar' +//│ ║ l.90: fun Bar() = new Bar +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── with class of the same name +//│ ║ l.87: class Bar { +//│ ║ ^^^^^^^^^^^ +//│ ║ l.88: fun id(x) = x +//│ ║ ^^^^^^^^^^^^^^^ +//│ ║ l.89: } +//│ ╙── ^ +//│ FAILURE: Unexpected internal error +//│ FAILURE LOCATION: softTODO (JSBuilder.scala:588) +//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true +//│ ╟── The compilation result may be incorrect. +//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. + +:sr +:sir +:lot +let x = new Bar +let y = new Bar() +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let x¹, x¹ = new Bar¹, let y¹, y¹ = new Bar¹() +//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— +//│ Program: +//│ main = Assign: +//│ lhs = x¹ +//│ rhs = Instantiate: +//│ mut = false +//│ cls = MemberRef{disamb=class:Bar²}: +//│ bms = member:Bar³ +//│ disamb = class:Bar² +//│ argss = Ls of +//│ Nil +//│ rest = Assign: \ +//│ lhs = y¹ +//│ rhs = Instantiate: +//│ mut = false +//│ cls = MemberRef{disamb=class:Bar²}: +//│ bms = member:Bar³ +//│ disamb = class:Bar² +//│ argss = Ls of +//│ Nil +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ set x¹ = new Bar²(); set y¹ = new Bar²(); end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ x = Bar +//│ y = Bar + + +:sr +:sir +module Foo { + class Bar { + fun id(x) = x + } + val Bar = new Bar +}//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: blockOrRcd (Elaborator.scala:1860) +//│ ╔══[COMPILATION ERROR] Illegal overloading of value 'Bar' +//│ ║ l.149: val Bar = new Bar +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── with class of the same name +//│ ║ l.146: class Bar { +//│ ║ ^^^^^^^^^^^ +//│ ║ l.147: fun id(x) = x +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ║ l.148: } +//│ ╙── ^^^ +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: resolError (NewResolver.scala:55) +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.149: val Bar = new Bar +//│ ║ ^^^^^^^ +//│ ╙── Invalid class expression: selection +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ module Foo⁰::Foo¹ { +//│ class Bar²::Bar³ { +//│ fun id⁰::id¹(x²) ‹flow:id⁰› = x² +//│ }, +//│ val Bar²::Bar⁴ ‹flow:Bar⁰› = new ⟨Foo¹.this.⟩Bar²^Bar⁴ +//│ } +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Foo⁰ as class Foo¹ +//│ module Foo² { +//│ val Bar⁴; +//│ constructor { +//│ define Bar⁴ as class Bar⁵ { +//│ method id⁰ = fun id¹(x) { +//│ return x +//│ } +//│ }; +//│ throw "This code cannot be run as its compilation yielded an error." +//│ } +//│ }; +//│ end +//│ FAILURE: Unexpected internal error +//│ FAILURE LOCATION: softTODO (JSBuilder.scala:588) +//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true +//│ ╟── The compilation result may be incorrect. +//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:264) +//│ ═══[RUNTIME ERROR] TypeError: Cannot set properties of undefined (setting 'class') +//│ at (REPL33:1:174) +//│ at (REPL33:1:426) +//│ at REPL33:1:608 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) + +:sr +:sir +:lot +let x = new Foo.Bar +let x = new Foo.Bar() +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ let x³, x³ = new Foo⁰.Bar², let x⁴, x⁴ = new Foo⁰.Bar²() +//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— +//│ Program: +//│ main = Scoped(syms = {x²}): +//│ body = Assign: +//│ lhs = x² +//│ rhs = Instantiate: +//│ mut = false +//│ cls = Select{sym=class:Bar⁵}: +//│ qual = MemberRef{disamb=module:Foo²}: +//│ bms = member:Foo⁰ +//│ disamb = module:Foo² +//│ name = Ident of "Bar" +//│ argss = Ls of +//│ Nil +//│ rest = Assign: \ +//│ lhs = x³ +//│ rhs = Instantiate: +//│ mut = false +//│ cls = Select{sym=class:Bar⁵}: +//│ qual = MemberRef{disamb=module:Foo²}: +//│ bms = member:Foo⁰ +//│ disamb = module:Foo² +//│ name = Ident of "Bar" +//│ argss = Ls of +//│ Nil +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let x; set x = new Foo².Bar⁵(); set x³ = new Foo².Bar⁵(); end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:264) +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'class') +//│ at REPL36:1:52 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) +//│ x = undefined + + + From 8715b643c6c4cbb2c830dce4d822deffa74c42c9 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Mon, 21 Sep 2026 14:04:48 +0800 Subject: [PATCH 009/228] Support bare class overloading with term definitions in new resolution --- .../src/main/scala/hkmc2/CompilerCtx.scala | 8 +- .../shared/src/main/scala/hkmc2/Config.scala | 13 + .../main/scala/hkmc2/codegen/Lowering.scala | 16 ++ .../scala/hkmc2/codegen/js/JSBuilder.scala | 102 ++++++-- .../scala/hkmc2/semantics/Elaborator.scala | 29 ++- .../src/test/mlscript-compile/NoFreeze.mjs | 2 +- .../OverloadedClassLibrary.mls | 15 ++ .../mlscript-compile/OverloadedDefault.mls | 6 + .../src/test/mlscript-compile/Predef.mjs | 2 +- .../src/test/mlscript-compile/Runtime.mjs | 2 +- .../src/test/mlscript/backlog/ToTriage.mls | 14 +- .../test/mlscript/basics/BadOverloading.mls | 38 ++- .../src/test/mlscript/newres/Basics.mls | 16 +- .../src/test/mlscript/newres/Captures.mls | 13 +- .../src/test/mlscript/newres/Functions.mls | 2 +- .../mlscript/newres/MultiParamClasses.mls | 2 +- .../test/mlscript/newres/NestedModules.mls | 2 +- .../newres/OverloadedClassImports.mls | 57 +++++ .../mlscript/newres/OverloadedClassValues.mls | 235 ++++++++++++++++++ .../mlscript/newres/OverloadedClasses.mls | 124 +-------- .../src/test/mlscript/newres/ParamClasses.mls | 14 +- .../src/test/mlscript/newres/PatMat.mls | 2 +- .../src/test/mlscript/newres/ReslScratch.mls | 18 +- .../src/test/mlscript/newres/loose/Basics.mls | 2 +- 24 files changed, 520 insertions(+), 214 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript-compile/OverloadedClassLibrary.mls create mode 100644 hkmc2/shared/src/test/mlscript-compile/OverloadedDefault.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala index 0c65a0e3c9..2c4782ad9a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala @@ -89,11 +89,13 @@ class CompilerCtx( ParserSetup(file) given Elaborator.Ctx = prelude val artifactCtx = derive(parse.origin.fileName, dependencies) + val parsed = parse.resultBlk + val elaborationConfig = Config.elaborationConfig(parsed) val elab = given CompilerCtx = artifactCtx + given Config = elaborationConfig Elaborator(tl, file.up, prelude) - val parsed = parse.resultBlk val nme = file.baseName val exportedSymbol = parsed.definedSymbols.find(_._1 === nme).map(_._2) def collectCompilationUnitSymbols(program: codegen.Program): Set[codegen.BoundSymbol] = @@ -103,7 +105,7 @@ class CompilerCtx( case sym: BlockMemberSymbol => sym .toSet case _ => Set.empty - val (blk0, _) = elab.importFrom(parsed) + val (blk0, _) = elaborationConfig.givenIn(elab.importFrom(parsed)) if file.toString === paths.runtimeSourceFile.toString then state.initRuntimeSymbolsFromBlock(blk0) else @@ -111,7 +113,7 @@ class CompilerCtx( using tl, summon[Raise], artifactCtx) val artifactConfig = Config.extractConfigFromStats(blk0) - artifactConfig.givenIn: + if !artifactConfig.language.useNewResolution then artifactConfig.givenIn: given Elaborator.State = state val resolver = Resolver(backendTL) resolver.traverseBlock(blk0)(using Resolver.ICtx.empty) diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index a5e7440bb8..106d3fe803 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -231,6 +231,19 @@ object Config: * Normally, we avoid inlining into @inline functions as that could lead to unexpected code bloat. */ case class Inliner(inlineThreshold: Int, altSmallThreshold: Int = 2) + /** File directives apply to the whole compilation unit, including elaboration. + * Read them before constructing the elaborator so its resolver agrees with lowering. + * Elaboration still records SetConfig statements and reports directive errors once. */ + def elaborationConfig(prgm: syntax.Tree.Block)(using Config): Config = + import syntax.Tree.* + given Raise = _ => () + def modifier(tree: syntax.Tree): Config => Config = tree match + case App(Directive(prefix, _), args) => modifier(Directive(prefix, args)) + case Directive(Ident("config"), Tup(args)) => ConfigParser.parseOverrides(args) + case Directive(Ident("lang"), Tup(args)) => ConfigParser.parseLanguageDirective(args) + case _ => identity + prgm.desugStmts.foldLeft(config)((cfg, tree) => modifier(tree)(cfg)) + def extractConfigFromStats(prgm: semantics.Term.Blk)(using Config) = // Extract cumulative config modifications from SetConfig statements val configModify = prgm.stats.collect: diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 4c3d84bfef..974d3227bc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1121,6 +1121,22 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case Ref(sym) => subTerm(rhs): r => assignSymbol(resolvedSelectionSymbol.getOrElse(sym), sym, r, k(unit), trm.toLoc) + case sel @ NewSel(prefix, nme) => + // Assignments select the term facet, even when the same member also names a class. + val sym = sel.resolvedTargets.distinct match + case sym :: Nil => sym.asTrm + case Nil => sel.resolvedMembers.distinct match + case bms :: Nil => bms.asTrm + case _ => N + case _ => N + sym match + case S(sym) => + subTerm(prefix): p => + subTerm_nonTail(rhs): r => + AssignField(p, definitionIdent(nme, sym), castTo(r, sym.erasedType, sel.toLoc), k(unit))(S(sym)) + case N => fail: + ErrorReport(msg"Assignment requires an unambiguous term member" -> sel.toLoc :: Nil, + source = Diagnostic.Source.Compilation) case sel @ Sel(prefix, nme) => subTerm(prefix): p => subTerm_nonTail(rhs): r => 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 d1ac5b13bc..7437b6d4c6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -48,6 +48,32 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val freeze = if !config.noFreeze then "globalThis.Object.freeze" else "" lazy val freezeDefns = if freezeDefinitions && !config.noFreeze then "globalThis.Object.freeze" else "" private val privateAccessorSymbols = LinkedHashMap.empty[semantics.TermSymbol, semantics.TempSymbol] + // Filled only by reserveNames for the current program. Refreshed IR members keep + // their original syntax trees, so their live class symbols must come from IR definitions. + private val independentClassSymbols = LinkedHashMap.empty[BlockMemberSymbol, ClassSymbol] + + /** Bare classes overloaded with terms have independent storage: their term may be + * uninitialized, primitive, or frozen. A symbol key keeps member class slots separate + * from source fields, and stable across separately compiled modules and inheritance. */ + private def independentClass(sym: DefinitionSymbol[?]): Opt[ClassSymbol] = + if sym.asTrm.isDefined then N + else (sym.asCls orElse sym.asBlkMember.flatMap(_.asCls)).filter: + cls => cls.asBlkMember.exists(_.asTrm.isDefined) && + cls.defn.exists(d => d.paramsOpt.isEmpty && d.hasDeclareModifier.isEmpty) + + private def classField(cls: ClassSymbol): Document = + doc"[globalThis.Symbol.for(${makeStringLiteral("mlscript.class." + cls.nme)})]" + + private def independentClassBindings(ss: Iterable[ScopedSymbol])(using Raise, Scope): Iterator[(Symbol, Str)] = + ss.toList.sortBy(_.uid).iterator.flatMap: + case bms: BlockMemberSymbol => + independentClassSymbols.get(bms) + case _ => N + .map(cls => cls -> scope.lookup_!(cls, cls.toLoc)) + + private def classExportName(sym: BlockMemberSymbol)(using Raise): Document = + // A string export name cannot collide with the identifier exports allocated for terms. + makeStringLiteral("mlscript.class:" + modulePrivateExportName(sym)) // TODO use this to avoid parens when we generate recomposed expressions later enum Context: @@ -164,8 +190,9 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: private case class Imports( defaults: Ls[ImportSymbol -> Str], privates: Ls[BlockMemberSymbol -> Str], + classes: Ls[BlockMemberSymbol -> Str], ): - def symbols: Ls[ImportSymbol] = defaults.map(_._1) ::: privates.map(_._1) + def symbols: Ls[Symbol] = defaults.map(_._1) ::: privates.map(_._1) ::: classes.map(_._1.asCls.get) /** Recovers imports that are not necessarily recorded in `p.imports`. @@ -200,6 +227,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val defaultImports = collection.mutable.Map.empty[ImportSymbol, Str] val privateImports = collection.mutable.Map.empty[BlockMemberSymbol, Str] + val classImports = collection.mutable.Map.empty[BlockMemberSymbol, Str] /** State identity, rather than equal path strings, is the compilation-unit ownership * invariant: all symbols belonging to the current artifact were created by this builder's @@ -221,7 +249,13 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: override def applyValue(value: Value): Unit = value match case Value.SimpleRef(sym: TempSymbol) => note(sym) case Value.SimpleRef(sym: VarSymbol) => note(sym) - case Value.MemberRef(sym, _) => note(sym) + case Value.MemberRef(sym, disamb) => + note(sym) + if independentClass(disamb).isDefined && !localSymbols(sym) then + sym.getState.externalModuleImport(sym, State) match + case S(ExternalModuleImport.Default(_, path)) => classImports(sym) = path + case S(ExternalModuleImport.Private(_, path)) => classImports(sym) = path + case N => () case _ => ).applyBlock(p.main) @@ -230,6 +264,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: Imports( orderedExternalImports(defaultImports), orderedExternalImports(privateImports), + orderedExternalImports(classImports), ) end externalImports @@ -244,7 +279,11 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: scope.lookup(sym).isEmpty privates.foreach: (sym, _) => scope.allocateName(sym) - Imports(defaults ::: externalDefaults, privates) + val classes = external.classes.filter: (sym, _) => + scope.lookup(sym.asCls.get).isEmpty + classes.foreach: (sym, _) => + scope.allocateName(sym.asCls.get) + Imports(defaults ::: externalDefaults, privates, classes) private def ownCompilationUnitSymbols(p: Program): Ls[BlockMemberSymbol] = p.main match @@ -313,8 +352,11 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case Value.Lit(Tree.StrLit(value)) => makeStringLiteral(value) case Value.Lit(lit) => lit.idStr case Value.MemberRef(bms, disamb) => - if disamb.shouldBeLifted then doc"${scope.lookup_!(bms, bms.toLoc)}.class" - else scope.lookup_!(bms, r.toLoc) + independentClass(disamb) match + case S(cls) => scope.lookup_!(cls, r.toLoc) + case N => + if disamb.shouldBeLifted then doc"${scope.lookup_!(bms, bms.toLoc)}.class" + else scope.lookup_!(bms, r.toLoc) case Value.SimpleRef(l: BuiltinSymbol) => if l.nullary then l.nme else errExpr(msg"Illegal reference to builtin symbol '${l.nme}'") @@ -360,10 +402,11 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case s @ Select(qual, id) => val checkCurrentSelection = checkSelections && s.sanitize val dotClass = s.symbol match - case S(ds) if ds.shouldBeLifted => doc".class" + case S(ds) if independentClass(ds).isEmpty && ds.shouldBeLifted => doc".class" case _ => doc"" val field = s.symbol match case S(ts: semantics.TermSymbol) => selectPrivateField(ts, s.toLoc) + case S(ds) => independentClass(ds).map(classField) case _ => N val name = symbolicSuffixBase(id.name).getOrElse(id.name) val fieldDoc = field.getOrElse: @@ -560,6 +603,12 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: // * in that case, we can't really use it as an inner name, as this would result in unintended capture. case S(otherSym: FreeSymbol) if (otherSym isnt sym) && bod.freeVars.contains(otherSym) => doc"${varName} = $functionKeyword ($params) ${ braced(bodyDoc) };" + case S(cls: ClassSymbol) if independentClass(cls).isDefined && bod.freeVars.exists { + case bms: BlockMemberSymbol => independentClassSymbols.get(bms).orElse(bms.asCls).contains(cls) + case _ => false + } => + // The function's display name must not capture its sibling class binding. + doc"${varName} = $functionKeyword ($params) ${ braced(bodyDoc) };" case _ => doc"${varName} = $functionKeyword ${sym.nme}($params) ${ braced(bodyDoc) };" else @@ -579,13 +628,14 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val backendParamList = auxParams.head val ctorParams = backendParamList.paramSyms.map(p => p -> scope.allocateName(p)) val sourceParamsOpt = isym.defn.flatMap(_.paramsOpt) + val independentCls = independentClass(isym) // * Whether the class should be "lifted" to a "class" property of the companion term // * should currently be consistent with whether the class has source parameters. // * This currently fails for faulty input programs (such as `object O(x)`); // * we should make sure such programs fail compilation before they reach this point. - softTODO(sourceParamsOpt.isDefined === isym.shouldBeLifted, - s"$sourceParamsOpt.isDefined =/= ${isym.shouldBeLifted}") + softTODO(sourceParamsOpt.isDefined === (isym.shouldBeLifted && independentCls.isEmpty), + s"Unexpected class storage for ${isym.nme}: parameters=$sourceParamsOpt, independent=$independentCls") def mkMethodName(td: FunDefn, owner: InnerSymbol): Document = if td.dSym.isPrivate @@ -705,6 +755,9 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: then doc" # new $v" else ownr match + case S(owner) if independentCls.isDefined => + doc" # ${result(owner.asThis)}${classField(independentCls.get)} = $v" + case N if independentCls.isDefined => doc"" case S(owner) => doc" # ${result(owner.asThis)}.${sym.nme}$extraPath = $v" case N => @@ -798,6 +851,8 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: fun match case S(f) => doc"${scope.lookup_!(sym, sym.toLoc)} = ${f}; # $freezeDefns($clsJS);" + case N if independentCls.isDefined => + doc"${outerScope.lookup_!(isym, isym.toLoc)} = $freezeDefns($clsJS);" case N => doc"$freezeDefns(${clsJS});" @@ -953,12 +1008,14 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: * but the result has the same semantics. * */ def reserveNames(p: Program)(using Scope, Raise): Unit = + independentClassSymbols.clear() def go(blk: Block): Unit = tl.trace(s"avoidNames ${blk.toString.take(100)}..."): blk match case Define(defn, rest) => defn match case d: ClsLikeDefn => val nme = scope.allocateName(d.isym) + independentClass(d.isym).foreach(cls => independentClassSymbols(d.sym) = cls) d.companion match case N => () case S(comp) => @@ -998,16 +1055,22 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val privateImps = imports.privates.map: (sym, path) => val relPath = relativeImportPath(path, wd) doc"""import { ${modulePrivateExportName(sym)} as ${scope.lookup_!(sym, N)} } from "${relPath}";""" + val classImps = imports.classes.map: (sym, path) => + val relPath = relativeImportPath(path, wd) + doc"""import { ${classExportName(sym)} as ${scope.lookup_!(sym.asCls.get, N)} } from "${relPath}";""" val bodyDoc = nonNestedScoped(p.main)(block(_, endSemi = false)).stripBreaks - val privateExports = (if exportPrivates then ownCompilationUnitSymbols(p) else Nil).map: sym => - doc"""export { ${scope.lookup_!(sym, sym.toLoc)} as ${modulePrivateExportName(sym)} };""" - withPrivateAccessorDecls((imps ::: privateImps).mkDocument(doc" # ")) + val privateExports = (if exportPrivates then ownCompilationUnitSymbols(p) else Nil).flatMap: sym => + doc"""export { ${scope.lookup_!(sym, sym.toLoc)} as ${modulePrivateExportName(sym)} };""" :: + independentClassSymbols.get(sym).toList.map: cls => + doc"""export { ${scope.lookup_!(cls, cls.toLoc)} as ${classExportName(sym)} };""" + withPrivateAccessorDecls((imps ::: privateImps ::: classImps).mkDocument(doc" # ")) :/: bodyDoc :: (if privateExports.isEmpty then doc"" else doc" # " :: privateExports.mkDocument(doc" # ")) :: locally: exprt match case S(sym) => - doc"\nlet ${sym.nme} = ${scope.lookup_!(sym, sym.toLoc)}; export default ${sym.nme};\n" + // Export the allocated binding directly; the source name may belong to its class facet. + doc"\nexport default ${scope.lookup_!(sym, sym.toLoc)};\n" case N => doc"" def worksheet(p: Program)(using Raise, Scope): (Document, Document) = @@ -1017,13 +1080,15 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val imports = bindImports(p) val importedScopedSymbols: Set[ScopedSymbol] = p.imports.iterator.map(_._1).toSet - val importBindings: Ls[ImportSymbol -> Str] = + val importBindings: Ls[Symbol -> Str] = imports.symbols.map(sym => sym -> scope.lookup_!(sym, N)) val imps = imports.defaults.map: (sym, path) => doc"""${scope.lookup_!(sym, N)} = await import("${path}").then(m => m.default ?? m);""" ::: imports.privates.map: (sym, path) => doc"""${scope.lookup_!(sym, N)} = await import("${path}").then(m => m.${modulePrivateExportName(sym)});""" + ::: imports.classes.map: (sym, path) => + doc"""${scope.lookup_!(sym.asCls.get, N)} = await import("${path}").then(m => m[${classExportName(sym)}]);""" p.main match case Scoped(syms, body) => val fvs = body.freeVars @@ -1031,7 +1096,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: !s.isInstanceOf[TempSymbol] // ^ VarSymbols and TermSymbols should be kept as their value will be acessed and printed by the worksheet || fvs(s))) - genLetDecls(importBindings.iterator ++ allocateScopedBindings(localSymbols)) -> + genLetDecls(importBindings.iterator ++ allocateScopedBindings(localSymbols) ++ independentClassBindings(localSymbols)) -> (withPrivateAccessorDecls(imps.mkDocument(doc" # ")) :/: block(body, endSemi = false).stripBreaks) case body => genLetDecls(importBindings.iterator) -> @@ -1053,7 +1118,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: l -> scope.allocateName(l) def blockPreamble(ss: Iterable[ScopedSymbol])(using Raise, Scope): Document = - genLetDecls(allocateScopedBindings(ss)) + genLetDecls(allocateScopedBindings(ss) ++ independentClassBindings(ss)) /** Specially handle top-level Scoped node: output the bindings, but do not add another pair of braces */ def nonBracedScoped(blk: Block)(k: Scope ?=> Block => Document)(using Raise, Scope): Document = blk match @@ -1182,10 +1247,11 @@ object JSBuilder: }.mkString extension (dsym: DefinitionSymbol[?]) - /** In JS, when a class is overloaded with a term (either explicitly, or because it has a primary parameter list), - * then its class value is stored in a `.class` property of the term. + /** Whether a class-like definition has a term companion. Parameterized classes + * store the class value in the generated constructor function's `.class` property; + * explicitly overloaded bare classes use independent storage instead. * - * This helper is used at reference sites (MemberRef, Select) to decide whether to append `.class` + * After checking for independent storage, reference sites use this to append `.class` * when accessing a class value. It returns true only for class/module/object symbols, * not for term symbols — so constructor calls like `Foo(args)` which resolve to the term * symbol are not affected. */ diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 0815f87dac..365a43b2de 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -449,8 +449,18 @@ object Elaborator: final case class SelElem(base: Elem, nme: Str, symOpt: Opt[MemberSymbol], isImport: Bool) extends Elem: def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term = // * Same remark as in RefElem#ref - Term.SynthSel(base.ref(Ident(base.nme)), - new Ident(nme).withLocOf(id))(symOpt, FlowSymbol.synthSel(nme), N, S(summon)) + val prefix = base.ref(Ident(base.nme)) + val name = new Ident(nme).withLocOf(id) + symOpt match + case S(bms: BlockMemberSymbol) if config.language.useNewResolution => + // Lexical lookup already identifies the member, but must preserve its + // overload set until its use chooses a class, term, or module. + val res = new Term.NewSel(prefix, name)(FlowSymbol.synthSel(nme)) + res.resolvedMembers = bms :: Nil + res.shapes += SymShape(bms, res.resSym, Nil) + res + case _ => + Term.SynthSel(prefix, name)(symOpt, FlowSymbol.synthSel(nme), N, S(summon)) def symbol = symOpt final case class CaptElem(base: Elem, thru: DefinitionSymbol[?]) extends Elem: def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term = @@ -699,6 +709,12 @@ extends Importer: lazy val illegalMemberNameTail = msg"Member names must start with a letter or underscore, followed by letters, digits, or underscores." -> N :: Nil + + private def moduleMembers(sym: BlockMemberSymbol): Opt[Map[Str, BlockMemberSymbol]] = + // Elaborated bodies cover both braced and `with` bodies. Forward references + // still need the syntax table before the module definition is available. + (if newResolution then sym.asModOrObj.flatMap(_.defn).map(_.body.members) else N) + .orElse(sym.modOrObjTree.map(_.definedSymbols)) def mkLetBinding(kw: Tree.Keywrd[?], sym: LocalVarSymbol | TermSymbol, rhs: Term, annotations: Ls[Annot]): Ls[Statement] = LetDecl(sym, annotations).mkLocWith(kw, sym) :: defineVar(sym, rhs) :: Nil @@ -709,7 +725,7 @@ extends Importer: case S(psym: BlockMemberSymbol) => psym.modOrObjTree match case S(cls) => - cls.definedSymbols.get(nme.name) match + moduleMembers(psym).flatMap(_.get(nme.name)) match case s @ S(clsSym) => s case N => raise(ErrorReport(msg"${cls.k.desc.capitalize} '${cls.symbol.nme @@ -1856,7 +1872,10 @@ extends Importer: log(s"Processing overloadings for '$name'") defns.iterator.foreach: defn => if defn.k > k then - if !supportedOverloadings(k -> defn.k) then raise: + val bareClassOverload = newResolution && ((k is Fun) || k.isInstanceOf[Val]) && (defn match + case td: TypeDef => (td.k is Cls) && td.paramLists.isEmpty + case _ => false) + if !supportedOverloadings(k -> defn.k) && !bareClassOverload then raise: ErrorReport: if notYetSupportedOverloadings(k -> defn.k) then msg"Not yet supported: overloading of ${k.desc} '$name'" -> mainDefn.toLoc @@ -1936,7 +1955,7 @@ extends Importer: case N => // "wilcard" open baseElem.symbol match case S(sym: BlockMemberSymbol) if sym.modOrObjTree.isDefined => - sym.modOrObjTree.get.definedSymbols.map: + moduleMembers(sym).getOrElse(Map.empty).map: case (nme, sym) => nme -> Ctx.SelElem(baseElem, sym.nme, S(sym), isImport = true) case _ => raise(ErrorReport(msg"Wildcard 'open' not supported for this kind of symbol." -> baseId.toLoc :: Nil)) diff --git a/hkmc2/shared/src/test/mlscript-compile/NoFreeze.mjs b/hkmc2/shared/src/test/mlscript-compile/NoFreeze.mjs index 7ceb3b71f5..d15852c63e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/NoFreeze.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/NoFreeze.mjs @@ -31,4 +31,4 @@ let NoFreeze1; static [definitionMetadata] = ["class", "NoFreeze"]; }); export { NoFreeze1 as _$_modulePrivate_$_NoFreeze }; -let NoFreeze = NoFreeze1; export default NoFreeze; +export default NoFreeze1; diff --git a/hkmc2/shared/src/test/mlscript-compile/OverloadedClassLibrary.mls b/hkmc2/shared/src/test/mlscript-compile/OverloadedClassLibrary.mls new file mode 100644 index 0000000000..6169be964c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/OverloadedClassLibrary.mls @@ -0,0 +1,15 @@ +#lang(0.3.x, strictResolution: true) + +class Hidden { + fun answer() = 51 +} +val Hidden = 15 + +module OverloadedClassLibrary { + fun fresh() = new Hidden + fun value() = Hidden + class Item { + fun answer() = 52 + } + val Item = 16 +} diff --git a/hkmc2/shared/src/test/mlscript-compile/OverloadedDefault.mls b/hkmc2/shared/src/test/mlscript-compile/OverloadedDefault.mls new file mode 100644 index 0000000000..cffeeebc13 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/OverloadedDefault.mls @@ -0,0 +1,6 @@ +#lang(0.3.x, strictResolution: true) + +class OverloadedDefault { + fun answer() = 54 +} +val OverloadedDefault = 19 diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 7b74f1e082..bf7746f614 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -349,4 +349,4 @@ export { lambda2 as _$_modulePrivate_$_lambda2 }; export { lambda$ as _$_modulePrivate_$_lambda$ }; export { lambda$1 as _$_modulePrivate_$_lambda$1 }; export { lambda$2 as _$_modulePrivate_$_lambda$2 }; -let Predef = Predef1; export default Predef; +export default Predef1; diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index 3b63a488c7..c71e99babf 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -1276,4 +1276,4 @@ export { Capture$scope321 as _$_modulePrivate_$_Capture$scope32 }; export { lambda$3 as _$_modulePrivate_$_lambda$3 }; export { lambda$4 as _$_modulePrivate_$_lambda$4 }; export { continuation$ as _$_modulePrivate_$_continuation$ }; -let Runtime = Runtime1; export default Runtime; +export default Runtime1; diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index db63c47e65..03290acbff 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -82,7 +82,7 @@ fun main() = :ssjs object Cls(val x) with fun huh = x -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: Some(ParamList(‹›,List(Param(‹val›,x,None,Modulefulness(None))),None)).isDefined =/= false +//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: Unexpected class storage for Cls: parameters=Some(ParamList(‹›,List(Param(‹val›,x,None,Modulefulness(None))),None)), independent=None //│ ╟── The compilation result may be incorrect. //│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— @@ -368,10 +368,6 @@ data class Foo(mut x) :e fun Foo class Foo -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true -//│ ╟── The compilation result may be incorrect. -//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. -//│ ═══[RUNTIME ERROR] TypeError: Cannot set properties of undefined (setting 'class') // ——— ——— ——— @@ -379,16 +375,16 @@ class Foo :e class Id(name: UnboundName) //│ ╔══[COMPILATION ERROR] Name not found: UnboundName -//│ ║ l.380: class Id(name: UnboundName) +//│ ║ l.376: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.380: class Id(name: UnboundName) +//│ ║ l.376: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got ‹error› -//│ ║ l.380: class Id(name: UnboundName) +//│ ║ l.376: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.380: class Id(name: UnboundName) +//│ ║ l.376: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ // ——— ——— ——— diff --git a/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls b/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls index 09ed809196..0f57d28b89 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls @@ -42,7 +42,7 @@ object Foo //│ ╟── with object of the same name //│ ║ l.38: object Foo //│ ╙── ^^^^^^^^^^ -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true +//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: Unexpected class storage for Foo: parameters=None, independent=None //│ ╟── The compilation result may be incorrect. //│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. @@ -50,7 +50,7 @@ object Foo // * Note that this does not report an error at the moment: fun Foo: Int object Foo -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true +//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: Unexpected class storage for Foo: parameters=None, independent=None //│ ╟── The compilation result may be incorrect. //│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. @@ -77,13 +77,6 @@ class Foo //│ ╟── with object of the same name //│ ║ l.60: object Foo //│ ╙── ^^^^^^^^^^ -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true -//│ ╟── The compilation result may be incorrect. -//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true -//│ ╟── The compilation result may be incorrect. -//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. -//│ ═══[RUNTIME ERROR] TypeError: Cannot assign to read only property 'class' of object '[object Object]' :todo // TODO: support @@ -91,35 +84,32 @@ class Foo class Foo fun Foo = 1 //│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Foo' -//│ ║ l.92: fun Foo = 1 +//│ ║ l.85: fun Foo = 1 //│ ║ ^^^^^^^^^^^ //│ ╟── with class of the same name -//│ ║ l.91: class Foo +//│ ║ l.84: class Foo //│ ╙── ^^^^^^^^^ -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true -//│ ╟── The compilation result may be incorrect. -//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. // TODO: support :e fun Foo = 1 module Foo //│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Foo' -//│ ║ l.105: fun Foo = 1 -//│ ║ ^^^^^^^^^^^ +//│ ║ l.95: fun Foo = 1 +//│ ║ ^^^^^^^^^^^ //│ ╟── with module of the same name -//│ ║ l.106: module Foo -//│ ╙── ^^^^^^^^^^ +//│ ║ l.96: module Foo +//│ ╙── ^^^^^^^^^^ // TODO: support :e val Foo = 1 module Foo //│ ╔══[COMPILATION ERROR] Not yet supported: overloading of value 'Foo' -//│ ║ l.116: val Foo = 1 +//│ ║ l.106: val Foo = 1 //│ ║ ^^^^^^^^^^^ //│ ╟── with module of the same name -//│ ║ l.117: module Foo +//│ ║ l.107: module Foo //│ ╙── ^^^^^^^^^^ //│ Foo = class Foo @@ -128,10 +118,10 @@ module Foo fun (**) f1(a, b) = "[" + a + " * " + b + "]" fun (**) f2(a, b) = "(" + a + " + " + b + ")" //│ ╔══[COMPILATION ERROR] Symbolic name '**' of definition 'f1' is already used -//│ ║ l.128: fun (**) f1(a, b) = "[" + a + " * " + b + "]" +//│ ║ l.118: fun (**) f1(a, b) = "[" + a + " * " + b + "]" //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── by sibling member 'f2' -//│ ║ l.129: fun (**) f2(a, b) = "(" + a + " + " + b + ")" +//│ ║ l.119: fun (**) f2(a, b) = "(" + a + " + " + b + ")" //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1 ** 2 @@ -142,10 +132,10 @@ fun (**) t() = 0 fun (++) t() = 1 //│ ╔══[COMPILATION ERROR] Multiple definitions of symbol 't' //│ ╟── defined here -//│ ║ l.141: fun (**) t() = 0 +//│ ║ l.131: fun (**) t() = 0 //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── defined here -//│ ║ l.142: fun (++) t() = 1 +//│ ║ l.132: fun (++) t() = 1 //│ ╙── ^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls index ace3c5eddb..4e3e6aec1f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -7,16 +7,16 @@ id //│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = SynthSel{sym=member:id⁰}: +//│ res = NewSel: //│ prefix = MemberRef of member:Predef⁰ -//│ nme = Ident of "id" +//│ id = Ident of "id" //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = SynthSel{sym=member:id⁰}: +//│ res = NewSel: //│ prefix = MemberRef of member:Predef⁰ -//│ nme = Ident of "id" +//│ id = Ident of "id" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ return Predef¹.id¹ +//│ return Predef¹.id⁰ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun id @@ -41,14 +41,14 @@ Predef.id //│ | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | | | PUSH MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | | | newSel: sel = member:Predef⁰.id, shape = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | newSel member: bms = member:id⁰, mss = List(↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | newSel member: bms = member:id¹, mss = List(↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) //│ | | ~> member:Predef⁰.id //│ | ~> member:Predef⁰.id //│ ~> { member:Predef⁰.id } //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ Predef⁰.id⁰ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ return Predef¹.id¹ +//│ return Predef¹.id⁰ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun id @@ -92,7 +92,7 @@ p.id //│ prefix = SimpleRef of p⁰ //│ id = Ident of "id" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ return p⁰.id¹ +//│ return p⁰.id⁰ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun id diff --git a/hkmc2/shared/src/test/mlscript/newres/Captures.mls b/hkmc2/shared/src/test/mlscript/newres/Captures.mls index 1830ec870b..60a11f2e70 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Captures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Captures.mls @@ -6,7 +6,7 @@ fun foo(x) = print(x) x //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun foo⁰::foo¹(x⁰) ‹flow:foo⁰› = { ⟨Predef⁰.⟩print⁰^foo¹(x⁰)‹app⁰›, x⁰ } +//│ fun foo⁰::foo¹(x⁰) ‹flow:foo⁰› = { Predef⁰.print⁰^foo¹(x⁰)‹app⁰›, x⁰ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -48,10 +48,7 @@ fun foo(x) = //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ fun foo²::foo³(x¹) ‹flow:foo¹› = if { //│ let scrut⁰ = >(x¹, 0)‹app⁴›; -//│ scrut⁰ is true do { -//│ ⟨Predef⁰.⟩print⁰^foo³(x¹)‹app⁵›, -//│ foo²^foo³(-(x¹, 1)‹app⁶›)‹app⁷› -//│ }; +//│ scrut⁰ is true do { Predef⁰.print⁰^foo³(x¹)‹app⁵›, foo²^foo³(-(x¹, 1)‹app⁶›)‹app⁷› }; //│ else () //│ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -69,7 +66,7 @@ fun foo(x) = bar //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ fun foo⁴::foo⁵(x²) ‹flow:foo²› = { -//│ ⟨Predef⁰.⟩print⁰^foo⁵(x²)‹app⁸›, +//│ Predef⁰.print⁰^foo⁵(x²)‹app⁸›, //│ fun bar⁰::bar¹(y⁰) ‹flow:bar⁰› = +(x²^bar¹, y⁰)‹app⁹›, //│ bar⁰ //│ } @@ -89,8 +86,8 @@ class foo(x) { //│ fun foo⁶ //│ class foo⁷::foo⁸(x³) { //│ @private val x⁴::x⁵ ‹flow:x⁰› = x³^x⁵, -//│ ⟨Predef⁰.⟩print⁰(⟨foo⁸.this.⟩x⁴)‹app¹⁰›, -//│ fun bar²::bar³(y¹) ‹flow:bar¹› = +(⟨foo⁸.this.⟩x⁴^bar³, y¹)‹app¹¹› +//│ Predef⁰.print⁰(foo⁸.this.x⁴)‹app¹⁰›, +//│ fun bar²::bar³(y¹) ‹flow:bar¹› = +(foo⁸.this.x⁴^bar³, y¹)‹app¹¹› //│ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 9f1bb0ff24..06f0a0257b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -27,7 +27,7 @@ foo(1, 2) :sr id //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ ⟨Predef⁰.⟩id⁰ +//│ Predef⁰.id⁰ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun id diff --git a/hkmc2/shared/src/test/mlscript/newres/MultiParamClasses.mls b/hkmc2/shared/src/test/mlscript/newres/MultiParamClasses.mls index 2826f2baca..c2dcfa45a5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/MultiParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/MultiParamClasses.mls @@ -10,7 +10,7 @@ class Foo(val a)(val b) { //│ class Foo¹::Foo²(val a⁰)(val b⁰) { //│ val a¹::a² ‹flow:a⁰› = a⁰^a², //│ val b¹::b² ‹flow:b⁰› = b⁰^b², -//│ val c⁰::c¹ ‹flow:c⁰› = +(⟨Foo².this.⟩a¹^c¹, ⟨Foo².this.⟩b¹^c¹)‹app⁰› +//│ val c⁰::c¹ ‹flow:c⁰› = +(Foo².this.a¹^c¹, Foo².this.b¹^c¹)‹app⁰› //│ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls index 0484016ee1..b470143fd7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls +++ b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls @@ -70,7 +70,7 @@ b.id :sr let m = if maybe then Foo.Bar else Predef //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ let m⁰, m⁰ = if { let scrut⁰ = ⟨Predef⁰.⟩maybe⁰; scrut⁰ is true then Foo⁰.Bar⁰; else Predef⁰ } +//│ let m⁰, m⁰ = if { let scrut⁰ = Predef⁰.maybe⁰; scrut⁰ is true then Foo⁰.Bar⁰; else Predef⁰ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ m = class Bar diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls new file mode 100644 index 0000000000..2e967d1b6b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls @@ -0,0 +1,57 @@ +:. + + +import "../../mlscript-compile/OverloadedClassLibrary.mls" + +// Inlining must import the private class binding independently from its term. +:sjs +:expect 51 +OverloadedClassLibrary.fresh().answer() +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ import { _$_modulePrivate_$_Hidden as Hidden } from "./../../mlscript-compile/OverloadedClassLibrary.mjs"; +//│ import { "mlscript.class:_$_modulePrivate_$_Hidden" as Hidden1 } from "./../../mlscript-compile/OverloadedClassLibrary.mjs"; +//│ let inlinedVal; +//│ inlinedVal = globalThis.Object.freeze(new Hidden1()); +//│ return runtime.safeCall(inlinedVal.answer()) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 51 + +:noInline +:expect 51 +OverloadedClassLibrary.fresh().answer() +//│ = 51 + +:expect 15 +OverloadedClassLibrary.value() +//│ = 15 + +:expect 16 +OverloadedClassLibrary.Item +//│ = 16 + +:expect 52 +(new OverloadedClassLibrary.Item).answer() +//│ = 52 + +open OverloadedClassLibrary { Item } + +:expect 52 +(new Item).answer() +//│ = 52 + +:expect 16 +Item +//│ = 16 + + +// The default export's term and class also have independent import bindings. +import "../../mlscript-compile/OverloadedDefault.mls" +//│ OverloadedDefault = 19 + +:expect 19 +OverloadedDefault +//│ = 19 + +:expect 54 +(new OverloadedDefault).answer() +//│ = 54 diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls new file mode 100644 index 0000000000..4cca07e0ca --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls @@ -0,0 +1,235 @@ +:. + + +// The term need not be an object that can carry a `.class` property. +class Primitive { + fun answer() = 42 +} +val Primitive = 7 +//│ Primitive = 7 + +:expect 7 +Primitive +//│ = 7 + +:expect 42 +(new Primitive).answer() +//│ = 42 + + +// Declaration order and nullary functions must not change class selection. +val Reverse = 13 +class Reverse { + fun answer() = 43 +} +//│ Reverse = 13 + +:expect 43 +(new Reverse()).answer() +//│ = 43 + +class Getter { + fun answer() = 44 +} +fun Getter = 14 + +:expect 14 +Getter +//│ = 14 + +:expect 44 +(new Getter).answer() +//│ = 44 + + +// Both meanings remain available inside closures and local scopes. +fun local(n) = { + class Item { + fun answer() = n + } + fun Item(x) = (new Item).answer() + x + Item(2) +} + +:expect 42 +local(40) +//│ = 42 + + +module Members { + class Item { + fun answer() = 45 + } + val Item = new Item + fun fresh() = new Item + class Factory { + fun answer() = 46 + } + fun Factory(x) = (new Factory).answer() + x +} + +:expect 45 +Members.Item.answer() +//│ = 45 + +:expect 45 +Members.fresh().answer() +//│ = 45 + +:expect 45 +(new Members.Item).answer() +//│ = 45 + +:expect 48 +Members.Factory(2) +//│ = 48 + +:expect 46 +(new Members.Factory()).answer() +//│ = 46 + + +// An explicit constructor list does not make a bare class a callable class. +class Aux { + constructor(val x) + fun answer() = x +} +fun Aux(x) = new Aux(x + 1) + +// FIXME: constructor-field references are not yet supported by the new resolver. +:fixme +:expect 42 +Aux(41).answer() +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + +:fixme +:expect 41 +(new Aux(41)).answer() +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + + +class Parent { + fun answer() = 47 +} +val Parent = false +class Child extends Parent +//│ Parent = false + +// FIXME: NewShape does not yet look up inherited members. +:fixme +:expect 47 +(new Child).answer() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.121: (new Child).answer() +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Child' does not contain member 'answer' +//│ ║ l.121: (new Child).answer() +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '47', got: 'undefined' + + +class Factory { + fun answer() = 49 +} +fun Factory() = new Factory + +:noInline +:expect 49 +Factory().answer() +//│ = 49 + + +// Bare classes still coexist with independent values inside class instances. +class Container { + class Inner { + fun answer() = 50 + } + val Inner = 5 + fun fresh() = new Inner +} +let container = new Container +//│ container = Container { Inner: 5 } + +:expect 5 +container.Inner +//│ = 5 + +:expect 50 +(new container.Inner).answer() +//│ = 50 + +:expect 50 +container.fresh().answer() +//│ = 50 + + +module MutableMembers { + class Item { + fun answer() = 53 + } + mut val Item = 17 +} +set MutableMembers.Item = 18 + +:expect 18 +MutableMembers.Item +//│ = 18 + +:expect 53 +(new MutableMembers.Item).answer() +//│ = 53 + + +// Direct parameter lists already define a constructor function with this name. +:e +class Parameterized() +fun Parameterized() = 0 +//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Parameterized' +//│ ║ l.187: fun Parameterized() = 0 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── with class of the same name +//│ ║ l.186: class Parameterized() +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ + +:e +class ParameterizedValue(x) +val ParameterizedValue = 0 +//│ ╔══[COMPILATION ERROR] Illegal overloading of value 'ParameterizedValue' +//│ ║ l.197: val ParameterizedValue = 0 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── with class of the same name +//│ ║ l.196: class ParameterizedValue(x) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ParameterizedValue = 0 + +// Distinct term definitions are still duplicate definitions, even beside a class. +:e +class Duplicate +fun Duplicate() = 0 +val Duplicate = 1 +//│ ╔══[COMPILATION ERROR] Illegal overloading of value 'Duplicate' +//│ ║ l.210: val Duplicate = 1 +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── with function of the same name +//│ ║ l.209: fun Duplicate() = 0 +//│ ╙── ^^^^^^^^^^^^^^^^^^^ + + +// Inlining the same local definitions twice must keep their class identities separate. +:inlineThreshold 10000 +:expect [42, 43] +[local(40), local(41)] +//│ = [42, 43] + + +fun unusedClass() = { + class Unused + val Unused = 55 + Unused +} + +:inlineThreshold 10000 +:expect 55 +unusedClass() +//│ = 55 diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls index f2f7f9a296..1125b383f0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls @@ -5,37 +5,7 @@ class Bar { fun id(x) = x } val Bar = new Bar -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: blockOrRcd (Elaborator.scala:1860) -//│ ╔══[COMPILATION ERROR] Illegal overloading of value 'Bar' -//│ ║ l.7: val Bar = new Bar -//│ ║ ^^^^^^^^^^^^^^^^^ -//│ ╟── with class of the same name -//│ ║ l.4: class Bar { -//│ ║ ^^^^^^^^^^^ -//│ ║ l.5: fun id(x) = x -//│ ║ ^^^^^^^^^^^^^^^ -//│ ║ l.6: } -//│ ╙── ^ -//│ FAILURE: Unexpected internal error -//│ FAILURE LOCATION: softTODO (JSBuilder.scala:588) -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true -//│ ╟── The compilation result may be incorrect. -//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:264) -//│ ═══[RUNTIME ERROR] TypeError: Cannot set properties of undefined (setting 'class') -//│ at (REPL16:1:84) -//│ at REPL16:1:302 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) -//│ Bar = undefined +//│ Bar = Bar :sr :sir @@ -67,44 +37,14 @@ let y = new Bar() //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ set x⁰ = new Bar⁰(); set y⁰ = new Bar⁰(); end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:264) -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'class') -//│ at REPL20:1:54 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) -//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) -//│ x = undefined -//│ y = undefined +//│ x = Bar +//│ y = Bar class Bar { fun id(x) = x } fun Bar() = new Bar -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: blockOrRcd (Elaborator.scala:1860) -//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Bar' -//│ ║ l.90: fun Bar() = new Bar -//│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ╟── with class of the same name -//│ ║ l.87: class Bar { -//│ ║ ^^^^^^^^^^^ -//│ ║ l.88: fun id(x) = x -//│ ║ ^^^^^^^^^^^^^^^ -//│ ║ l.89: } -//│ ╙── ^ -//│ FAILURE: Unexpected internal error -//│ FAILURE LOCATION: softTODO (JSBuilder.scala:588) -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true -//│ ╟── The compilation result may be incorrect. -//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. :sr :sir @@ -148,64 +88,31 @@ module Foo { } val Bar = new Bar }//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: blockOrRcd (Elaborator.scala:1860) -//│ ╔══[COMPILATION ERROR] Illegal overloading of value 'Bar' -//│ ║ l.149: val Bar = new Bar -//│ ║ ^^^^^^^^^^^^^^^^^ -//│ ╟── with class of the same name -//│ ║ l.146: class Bar { -//│ ║ ^^^^^^^^^^^ -//│ ║ l.147: fun id(x) = x -//│ ║ ^^^^^^^^^^^^^^^^^ -//│ ║ l.148: } -//│ ╙── ^^^ -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: resolError (NewResolver.scala:55) -//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.149: val Bar = new Bar -//│ ║ ^^^^^^^ -//│ ╙── Invalid class expression: selection //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ module Foo⁰::Foo¹ { //│ class Bar²::Bar³ { //│ fun id⁰::id¹(x²) ‹flow:id⁰› = x² //│ }, -//│ val Bar²::Bar⁴ ‹flow:Bar⁰› = new ⟨Foo¹.this.⟩Bar²^Bar⁴ +//│ val Bar²::Bar⁴ ‹flow:Bar⁰› = new Foo¹.this.Bar²^Bar⁴ //│ } //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define Foo⁰ as class Foo¹ //│ module Foo² { //│ val Bar⁴; //│ constructor { +//│ let tmp; //│ define Bar⁴ as class Bar⁵ { //│ method id⁰ = fun id¹(x) { //│ return x //│ } //│ }; -//│ throw "This code cannot be run as its compilation yielded an error." +//│ set tmp = new Foo².this.Bar⁵(); +//│ define Bar⁴ as val Bar⁶ = tmp; +//│ end //│ } //│ }; //│ end -//│ FAILURE: Unexpected internal error -//│ FAILURE LOCATION: softTODO (JSBuilder.scala:588) -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: None.isDefined =/= true -//│ ╟── The compilation result may be incorrect. -//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:264) -//│ ═══[RUNTIME ERROR] TypeError: Cannot set properties of undefined (setting 'class') -//│ at (REPL33:1:174) -//│ at (REPL33:1:426) -//│ at REPL33:1:608 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) :sr :sir @@ -242,20 +149,7 @@ let x = new Foo.Bar() //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let x; set x = new Foo².Bar⁵(); set x³ = new Foo².Bar⁵(); end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:264) -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'class') -//│ at REPL36:1:52 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) -//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) -//│ x = undefined +//│ x = Bar diff --git a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls index 961352fd48..107fea674b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls @@ -88,7 +88,7 @@ class Foo(val a) { //│ fun Foo³ //│ class Foo⁴::Foo⁵(val a³) { //│ val a⁴::a⁵ ‹flow:a¹› = a³^a⁵, -//│ val b⁰::b¹ ‹flow:b⁰› = +(⟨Foo⁵.this.⟩a⁴^b¹, 1)‹app³› +//│ val b⁰::b¹ ‹flow:b⁰› = +(Foo⁵.this.a⁴^b¹, 1)‹app³› //│ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -176,7 +176,7 @@ class Bar(b) with //│ fun Bar⁰ //│ class Bar¹::Bar²(b²) { //│ @private val b³::b⁴ ‹flow:b¹› = b²^b⁴, -//│ val foo²::foo³ ‹flow:foo¹› = Foo⁴^foo³(⟨Bar².this.⟩b³^foo³)‹app⁸› +//│ val foo²::foo³ ‹flow:foo¹› = Foo⁴^foo³(Bar².this.b³^foo³)‹app⁸› //│ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -278,7 +278,7 @@ let f = new Foo(Predef) //│ fun Foo⁶ //│ class Foo⁷::Foo⁸(pred⁰) { //│ @private val pred¹::pred² ‹flow:pred⁰› = pred⁰^pred², -//│ ⟨Foo⁸.this.⟩pred¹.id⁰(42)‹app⁹› +//│ Foo⁸.this.pred¹.id⁰(42)‹app⁹› //│ }, //│ let f⁴, //│ f⁴ = new Foo⁷(Predef⁰) @@ -309,8 +309,8 @@ let f = Foo(Predef) //│ fun Foo⁹ //│ class Foo¹⁰::Foo¹¹(pred³) { //│ @private val pred⁴::pred⁵ ‹flow:pred¹› = pred³^pred⁵, -//│ ⟨Foo¹¹.this.⟩pred⁴.id⁰(1)‹app¹⁰›, -//│ ⟨Foo¹¹.this.⟩pred⁴.id⁰(2)‹app¹¹› +//│ Foo¹¹.this.pred⁴.id⁰(1)‹app¹⁰›, +//│ Foo¹¹.this.pred⁴.id⁰(2)‹app¹¹› //│ }, //│ let f⁵, //│ f⁵ = Foo¹⁰(Predef⁰)‹app¹²› @@ -327,8 +327,8 @@ let f = new Foo(Predef) //│ fun Foo¹² //│ class Foo¹³::Foo¹⁴(pred⁶) { //│ @private val pred⁷::pred⁸ ‹flow:pred²› = pred⁶^pred⁸, -//│ val r⁰::r¹ ‹flow:r⁰› = ⟨Foo¹⁴.this.⟩pred⁷^r¹.id⁰(42)‹app¹³›, -//│ fun callId⁰::callId¹(x¹) ‹flow:callId⁰› = ⟨Foo¹⁴.this.⟩pred⁷^callId¹.id⁰(x¹)‹app¹⁴› +//│ val r⁰::r¹ ‹flow:r⁰› = Foo¹⁴.this.pred⁷^r¹.id⁰(42)‹app¹³›, +//│ fun callId⁰::callId¹(x¹) ‹flow:callId⁰› = Foo¹⁴.this.pred⁷^callId¹.id⁰(x¹)‹app¹⁴› //│ }, //│ let f⁶, //│ f⁶ = new Foo¹³(Predef⁰) diff --git a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls index a3688646dd..7b09703d23 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls @@ -16,7 +16,7 @@ class Baz(c) { fun b = c } //│ fun Baz⁰ //│ class Baz¹::Baz²(c⁰) { //│ @private val c¹::c² ‹flow:c⁰› = c⁰^c², -//│ fun b²::b³ ‹flow:b¹› = ⟨Baz².this.⟩c¹^b³ +//│ fun b²::b³ ‹flow:b¹› = Baz².this.c¹^b³ //│ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 20517cc6e8..40ecf8c819 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -41,8 +41,8 @@ class Bar(a) with //│ class Bar¹::Bar²(a⁰) { //│ @private val a¹::a² ‹flow:a⁰› = a⁰^a², //│ let x⁰, -//│ x⁰ = ⟨Bar².this.⟩a¹, -//│ ⟨Predef⁰.⟩print⁰(x⁰)‹app⁰› +//│ x⁰ = Bar².this.a¹, +//│ Predef⁰.print⁰(x⁰)‹app⁰› //│ } //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: @@ -73,13 +73,13 @@ class Bar(a) with //│ sym = term:Bar³/x⁰ //│ DefineVar: //│ sym = term:Bar³/x⁰ -//│ rhs = SynthSel{sym=member:a³}: +//│ rhs = NewSel: //│ prefix = SelfRef of class:Bar³ -//│ nme = Ident of "a" +//│ id = Ident of "a" //│ res = App: -//│ lhs = SynthSel{sym=member:print⁰}: +//│ lhs = NewSel: //│ prefix = MemberRef of member:Predef⁰ -//│ nme = Ident of "print" +//│ id = Ident of "print" //│ rhs = Tup of Ls of //│ Fld: //│ term = MemberRef of term:Bar³/x⁰ @@ -92,7 +92,7 @@ class Bar(a) with //│ @private //│ define a³ as val a⁴ = a; //│ set Bar³.this.x⁰ = Bar³.this.a⁴; -//│ do Predef¹.print¹(Bar³.this.x⁰); +//│ do Predef¹.print⁰(Bar³.this.x⁰); //│ end //│ } //│ }; @@ -105,7 +105,7 @@ class Bar(a) with //│ @private //│ define a³ as val a⁴ = a; //│ set Bar³.this.x⁰ = Bar³.this.a⁴; -//│ do Predef¹.print¹(Bar³.this.x⁰); +//│ do Predef¹.print⁰(Bar³.this.x⁰); //│ end //│ } //│ }; @@ -122,7 +122,7 @@ class Bar(a) with //│ constructor(a) { //│ this.#a = a; //│ this.#x = this.#a; -//│ Predef.print(this.#x); +//│ runtime.safeCall(Predef.print(this.#x)); //│ } //│ #a; //│ #x; diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Basics.mls index 71507cc966..98601f1337 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Basics.mls @@ -27,7 +27,7 @@ m.u //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ let m¹, //│ m¹ = if { -//│ let scrut⁰ = ⟨Predef⁰.⟩hide⁰(true)‹app⁰›; +//│ let scrut⁰ = Predef⁰.hide⁰(true)‹app⁰›; //│ scrut⁰ is true then A⁰; //│ else B⁰ //│ }, From 323c580a4b5ea103584c8a4df31392ff546491b8 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 12:50:13 +0800 Subject: [PATCH 010/228] Resolve assignment term targets before lowering and document staged plan --- doc/new-resolution-plan.md | 72 +++++++++++++++ doc/reference.md | 26 ++++++ .../main/scala/hkmc2/codegen/Lowering.scala | 6 +- .../scala/hkmc2/semantics/Elaborator.scala | 13 ++- .../scala/hkmc2/semantics/NewResolver.scala | 22 +++-- .../src/test/mlscript/newres/Assignments.mls | 90 +++++++++++++++++++ 6 files changed, 217 insertions(+), 12 deletions(-) create mode 100644 doc/new-resolution-plan.md create mode 100644 hkmc2/shared/src/test/mlscript/newres/Assignments.mls diff --git a/doc/new-resolution-plan.md b/doc/new-resolution-plan.md new file mode 100644 index 0000000000..453cb79546 --- /dev/null +++ b/doc/new-resolution-plan.md @@ -0,0 +1,72 @@ +# New resolution: overloads and deferred opens + +Resolution owns member discovery and definition selection. Lowering consumes those +decisions; it must not recover missing decisions by inspecting overload sets or +partially elaborated definitions. A member overload set, its selected definition, +and the shape of its value are separate pieces of information. + +## Intended wildcard-open semantics + +For `newResolution`, lookup first searches the complete chain of ordinary `Ctx` +environments. Explicit bindings, including member definitions and selective opens, +always take precedence over wildcard opens, including wildcard opens in an inner +scope. Only a name with no explicit binding is looked up through wildcard opens. + +`Ctx` carries the currently wildcard-opened members. Such a lookup creates an +`UnresolvedRef` and registers listeners on those opened members. As their shapes +arrive, matching members are propagated into the reference's `resolvedMembers`. +A pending shape is not evidence that a member is absent. Candidates retain their +receiver and capture context; module completion must not change lexical priority. + +## Stages + +1. **Record the contracts and update the language reference.** + Document explicit-binding precedence and deferred wildcard lookup. Keep this + plan current with implementation progress and validation results. +2. **Make term selection a resolver responsibility.** + Centralize selection after symbol completion and record the selected definition + separately from propagated value shapes. Register assignment term resolution, + including synthesized assignments, and remove assignment lowering's member + fallback. Preserve receiver evaluation order and avoid reading the assigned + value to determine its target. +3. **Implement deferred opens.** + Add wildcard-open context entries and `UnresolvedRef`. Search explicit bindings + before wildcard sources. Use normal shape resolution for selective opens too; + remove `moduleMembers` and eager syntax/definition-table lookup in new mode. + Preserve receivers and marks through captures, nesting, imports, and forward + references. Resolve competing distinct members without choosing by callback + arrival order. Lowering can report ambiguity from the accumulated candidates; + reporting ambiguity does not entail performing name resolution in lowering. +4. **Enforce the phase boundary for all uses.** + Ensure ordinary value uses request term resolution, while class and pattern + uses request their appropriate interpretation. Remove definition-selection + fallbacks from `MemberRef`/`NewSel` lowering and audit `SelElem` shape seeding. + Distinguish unresolved work from diagnosed errors and successful selections; + assert internal invariants instead of silently guessing. Audit backend class + storage against resolved identities and live IR definitions. +5. **Validate and commit.** + Cover forward definitions, both module-body forms, explicit-vs-wildcard + precedence across scopes and declaration orders, competing opens, captures, + imports, overloaded class/term uses, and ordinary/synthesized assignments. + Run `ctest` before focused tests, then `hkmc2AllTests/test`. Review diagnostics + and golden changes for semantic correctness and commit the resulting outputs + with the implementation under the agent's identity. + +## Progress + +- Stage 1 complete: plan recorded and wildcard lookup rules added to the language + reference. These are the intended rules; stage 3 implements them. +- Stage 2 implemented: `listenTerm` records the definition selected after symbol + completion, independently of its value shapes. Source and synthesized + assignments request term resolution. Assignment lowering no longer selects + from `resolvedMembers`. Declared values publish no implementation shape but + still supply a selected definition. +- Stage 2 regressions cover a forward module definition, overloaded field writes, + backtracking restoration, declared fields, receiver/RHS evaluation order, and + rejection of a class-only assignment target. A nested-module parameter capture + assertion also occurs without the new assignment listener; it is recorded as + a `:fixme` in `newres/Assignments.mls` for the capture work in stage 3. +- Stage 2 validation: `ctest` passed (45 tests); `dtest newres/` passed (24 files); + `hkmc2AllTests/test` passed. Regression outputs reviewed for the stage checkpoint. +- Stages 3 and 4 remain pending. In particular, `moduleMembers`, the lexical + `SelElem` shape shortcut, and the read-side lowering fallbacks are still present. diff --git a/doc/reference.md b/doc/reference.md index ed47194fb5..19afff3efc 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -1336,6 +1336,32 @@ open Iter // open all members do open M; ... // locally open ``` +With `newResolution` enabled, wildcard opens are consulted only when a name has +no explicit binding in the current scope or any enclosing scope. Explicit +bindings include local variables, parameters, member definitions, imports, and +selective opens such as `open Stack { Cons, Nil }`. An inner wildcard open does +not shadow an explicit binding in an outer scope. Moving a wildcard open before +or after an explicit binding does not give it priority over that binding. + +For example, inside the function below, `x` refers to the explicitly bound `x`, +even though `M` also provides a member named `x`: + +```mlscript +val x = 1 +module M { val x = 2; val y = 3 } +fun example() = + open M + x + y // explicit x; y from M +``` + +When no explicit binding exists, lookup considers the wildcard opens visible at +that occurrence. These rules apply to the name itself, before choosing its term or class +interpretation, so an explicit binding also takes precedence over an overloaded +class or function supplied by a wildcard open. + +If wildcard lookup finds several distinct members with the requested name, the +reference is ambiguous; the order of the opens does not choose a winner. + ### `open M in` Scope ```mlscript diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 974d3227bc..f6ccc76bbb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1122,12 +1122,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): subTerm(rhs): r => assignSymbol(resolvedSelectionSymbol.getOrElse(sym), sym, r, k(unit), trm.toLoc) case sel @ NewSel(prefix, nme) => - // Assignments select the term facet, even when the same member also names a class. + // Term resolution selects the definition; lowering only checks that + // the result is an unambiguous term member and emits the field write. val sym = sel.resolvedTargets.distinct match case sym :: Nil => sym.asTrm - case Nil => sel.resolvedMembers.distinct match - case bms :: Nil => bms.asTrm - case _ => N case _ => N sym match case S(sym) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 365a43b2de..d55e68d7dc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1185,6 +1185,13 @@ extends Importer: if newResolution then listenTerm(lt)(shape => appShape(shape, rt, res)) res + private def assignment(lhs: Term, rhs: Term): Term.Assgn = + // Resolve the term interpretation of the l-value before lowering. The + // selected definition is recorded even when its value publishes no shape. + // Use this for synthesized assignments as well as source-level `set`. + if newResolution then listenTerm(lhs)(_ => ()) + Term.Assgn(lhs, rhs) + def ifLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit, loc: Opt[Loc]): Term.IfLike = val res = new Term.IfLike(kw, form, split).withLoc(loc) if newResolution then @@ -1260,7 +1267,7 @@ extends Importer: tree.toLoc :: Nil)) block(LetLike(kw, lhs, rhso, N) :: Nil, hasResult = true)._1 case LetLike(Keywrd(`set`), lhs, S(rhs), N) => - Term.Assgn(subterm(lhs), subterm(rhs)) + assignment(subterm(lhs), subterm(rhs)) case LetLike(Keywrd(`set`), lhs, N, N) => raise(ErrorReport( msg"Expected a right-hand side for this assignment" -> @@ -1278,9 +1285,9 @@ extends Importer: val sym = TempSymbol(S(lt), erasedType = N, "old") Blk( LetDecl(sym, Nil) :: defineVar(sym, lt) :: Nil, Term.Try(Blk( - Term.Assgn(lt, subterm(rhs)) :: Nil, + assignment(lt, subterm(rhs)) :: Nil, subterm(bod), - ), Term.Assgn(lt, sym.ref()))) + ), assignment(lt, sym.ref()))) case LetLike(Keywrd(Keyword.`set`), _, N, S(_)) => raise: ErrorReport(msg"Expected a right-hand side for this assignment" -> tree.toLoc :: Nil) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 97c35c69b2..8391c195ad 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -152,7 +152,7 @@ class NewResolver: res.isErroneous = true resolError(res, msg"${sh.describe.capitalize} cannot used like an applied pattern." -> sh.toLoc :: Nil) - , lhs) + , lhs, _ => ()) def listenPattern(pat: Pattern)(listener: PatternShape => Unit): Unit = // ??? @@ -393,12 +393,17 @@ class NewResolver: case N => listener(N) - def fromBMS(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: TermShape => Unit, trm: Term) = + def fromBMS(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: TermShape => Unit, + trm: Term, selected: DefinitionSymbol[?] => Unit) = log(s"listenBMS: bms = ${bms.describe}") bms.onComplete: () => log(s"listenedBMS: bms = ${bms.describe}") bms.asModOrObj orElse bms.asTrm orElse bms.asCls match case S(sym: (ModuleOrObjectSymbol | TermSymbol | ClassSymbol)) => + // Selection is independent of the selected value's shape. In particular, + // an assignment needs its target even if the value has no inferred shape. + // Pattern resolution supplies its own interpretation of the selected head. + selected(sym) val wrappedListener: TermShape => Unit = sh => log(s"fromBMS: bms = ${bms.showDbg}, sh = ${sh.shwDbg}, flow = ${resSym.showDbg}, markss = ${markss.map(_.showDbg)}") val sh0 = sh @@ -422,7 +427,9 @@ class NewResolver: case S(body) => listenTerm(body)(wrappedListener) case N => - ??? // TODO error + // A declared value has a selected definition but no implementation + // from which to infer a value shape (e.g. an external mutable field). + () case S(d: TermDefinition) => d.tsym match case ccs: ClassCtorSymbol => @@ -455,7 +462,12 @@ class NewResolver: case sh: TermShape => listener(sh) case ss: SymShape => - fromBMS(ss.sym, ss.resSym, ss.markss, listener, trm) + fromBMS(ss.sym, ss.resSym, ss.markss, listener, trm, sym => + trm.withoutCaptures match + case ref: NewResolvable => + if !ref.resolvedTargets.contains(sym) then ref.resolvedTargets ::= sym + case _ => () + ) def listen(trm: Term, discardMarks: Bool = false)(listener: Shape => Unit): Unit = log(s"listen: trm = ${trm.showDbg}") @@ -476,7 +488,7 @@ class NewResolver: case S(bms: BlockMemberSymbol) => // TODO: add mark fromBMS(bms, ss.resSym, Nil//TODO? - , listener, trm) + , listener, trm, _ => ()) case N => ??? case intro: IntroTerm => val sh = introShapes.getOrElseUpdate(intro, { diff --git a/hkmc2/shared/src/test/mlscript/newres/Assignments.mls b/hkmc2/shared/src/test/mlscript/newres/Assignments.mls new file mode 100644 index 0000000000..65c8d40396 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Assignments.mls @@ -0,0 +1,90 @@ +:. + + +// The target must resolve even when the module has not been elaborated yet. +fun writeLater(value) = { + set Later.Item = value +} +module Later { + class Item { + fun answer() = 42 + } + mut val Item = 0 +} +writeLater(7) + +:expect 7 +Later.Item +//│ = 7 + +:expect 42 +(new Later.Item).answer() +//│ = 42 + + +// A declared field has no initializer shape, but still has a definite target. +declare module External with + mut val value +fun writeExternal(value) = { + set External.value = value +} + + +// Both the temporary write and the synthesized restoration use term resolution. +:expect 8 +set Later.Item = 8 in Later.Item +//│ = 8 + +:expect 7 +Later.Item +//│ = 7 + + +// FIXME: Capturing a function parameter through a nested module currently +// mismatches module/function marks, including without assignment resolution. +fun replace(initial) = { + module Local { + class Item + mut val Item = initial + } + set Local.Item = 9 + Local.Item +} + +:fixme +:noInline +:expect 9 +replace(0) +//│ /!!!\ Uncaught error: java.lang.IllegalArgumentException: requirement failed: Expected symbol module:Local⁰ but got term:replace⁰ + + +// Resolving an l-value must preserve receiver-before-RHS evaluation, once each. +module Order { + mut val trace = "" + mut val value = 0 +} +fun receiver() = { + set Order.trace = Order.trace + "receiver;" + Order +} +fun rhs() = { + set Order.trace = Order.trace + "rhs;" + 10 +} +set receiver().value = rhs() + +:expect ["receiver;rhs;", 10] +[Order.trace, Order.value] +//│ = ["receiver;rhs;", 10] + + +// A class without a term definition is not a term-member assignment target. +:e +module Classes { + class Only +} +set Classes.Only = 0 +//│ ╔══[COMPILATION ERROR] Assignment requires an unambiguous term member +//│ ║ l.86: set Classes.Only = 0 +//│ ╙── ^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 8164a56ff69c34baa06475e97f145db58c1a8f64 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 13:03:18 +0800 Subject: [PATCH 011/228] Preserve enclosing captures without module boundary marks --- doc/new-resolution-plan.md | 10 +- .../scala/hkmc2/semantics/NewResolver.scala | 14 ++- .../src/test/mlscript/newres/Assignments.mls | 8 +- .../src/test/mlscript/newres/Basics.mls | 27 ++--- .../src/test/mlscript/newres/Functions.mls | 99 +++++++---------- .../test/mlscript/newres/ModuleCaptures.mls | 105 ++++++++++++++++++ .../src/test/mlscript/newres/WIPTest.mls | 33 ++---- 7 files changed, 187 insertions(+), 109 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ModuleCaptures.mls diff --git a/doc/new-resolution-plan.md b/doc/new-resolution-plan.md index 453cb79546..18fac53282 100644 --- a/doc/new-resolution-plan.md +++ b/doc/new-resolution-plan.md @@ -63,10 +63,14 @@ receiver and capture context; module completion must not change lexical priority still supply a selected definition. - Stage 2 regressions cover a forward module definition, overloaded field writes, backtracking restoration, declared fields, receiver/RHS evaluation order, and - rejection of a class-only assignment target. A nested-module parameter capture - assertion also occurs without the new assignment listener; it is recorded as - a `:fixme` in `newres/Assignments.mls` for the capture work in stage 3. + rejection of a class-only assignment target. - Stage 2 validation: `ctest` passed (45 tests); `dtest newres/` passed (24 files); `hkmc2AllTests/test` passed. Regression outputs reviewed for the stage checkpoint. +- Capture follow-up: module/object references no longer introduce their own exit + marks. Enclosing function and receiver-context marks remain intact. This fixes + the nested-module assertion in `newres/Assignments.mls`; its `:fixme` is removed. + `newres/ModuleCaptures.mls` covers nested and escaping modules, objects, getters, + methods, and separation of captures from distinct calls. Validation passed: + `ctest` (45 tests), `dtest newres/` (25 files), and `hkmc2AllTests/test`. - Stages 3 and 4 remain pending. In particular, `moduleMembers`, the lexical `SelElem` shape shortcut, and the read-side lowering fallbacks are still present. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 8391c195ad..3e8f2abc87 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -407,11 +407,15 @@ class NewResolver: val wrappedListener: TermShape => Unit = sh => log(s"fromBMS: bms = ${bms.showDbg}, sh = ${sh.shwDbg}, flow = ${resSym.showDbg}, markss = ${markss.map(_.showDbg)}") val sh0 = sh - MarkedShape.exit(sh, sym, S(resSym)).exit(markss) match - // sh.exit(markss) match - // case NoShape => - // case sh: TermShape => - // MarkedShape.exit(sh, sym, S(resSym)) match + // Modules and objects introduce no enter/exit boundary of their own. + // Adding an exit here would try to consume an enclosing function's + // entry mark when a member captures one of that function's parameters. + // Receiver-context marks still apply, including when a function returns + // a module: they keep captures from different calls separate. + val exited = sym match + case _: ModuleOrObjectSymbol => sh + case _ => MarkedShape.exit(sh, sym, S(resSym)) + exited.exit(markss) match case NoShape => log(s"FILTER OUT ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") case sh: TermShape => diff --git a/hkmc2/shared/src/test/mlscript/newres/Assignments.mls b/hkmc2/shared/src/test/mlscript/newres/Assignments.mls index 65c8d40396..b5df130ab9 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Assignments.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Assignments.mls @@ -40,8 +40,7 @@ Later.Item //│ = 7 -// FIXME: Capturing a function parameter through a nested module currently -// mismatches module/function marks, including without assignment resolution. +// Resolving the assignment must preserve captures through the nested module. fun replace(initial) = { module Local { class Item @@ -51,11 +50,10 @@ fun replace(initial) = { Local.Item } -:fixme :noInline :expect 9 replace(0) -//│ /!!!\ Uncaught error: java.lang.IllegalArgumentException: requirement failed: Expected symbol module:Local⁰ but got term:replace⁰ +//│ = 9 // Resolving an l-value must preserve receiver-before-RHS evaluation, once each. @@ -85,6 +83,6 @@ module Classes { } set Classes.Only = 0 //│ ╔══[COMPILATION ERROR] Assignment requires an unambiguous term member -//│ ║ l.86: set Classes.Only = 0 +//│ ║ l.84: set Classes.Only = 0 //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls index 4e3e6aec1f..5c4cdb9678 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -36,12 +36,9 @@ Predef.id //│ | | | listenBMS: bms = module //│ | | | listenedBMS: bms = module //│ | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() -//│ | | | .exit MarkedShape (module:Predef¹, module:Predef¹, Predef²) -//│ | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? -//│ | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | PUSH MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | newSel: sel = member:Predef⁰.id, shape = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | newSel member: bms = member:id¹, mss = List(↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | PUSH module:Predef¹ +//│ | | | newSel: sel = member:Predef⁰.id, shape = module:Predef¹ +//│ | | | newSel member: bms = member:id¹, mss = List() //│ | | ~> member:Predef⁰.id //│ | ~> member:Predef⁰.id //│ ~> { member:Predef⁰.id } @@ -69,7 +66,7 @@ Predef.id(1) :e Predef.idd(1) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.70: Predef.idd(1) +//│ ║ l.67: Predef.idd(1) //│ ║ ^^^^^^^^^^ //│ ╟── Module 'Predef' does not contain member 'idd' //│ ║ in ../../mlscript-compile/Predef.mls @@ -122,30 +119,30 @@ p.id :e (1).id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.123: (1).id +//│ ║ l.120: (1).id //│ ║ ^^^^^^ //│ ╟── Integer literal does not contain member 'id' -//│ ║ l.123: (1).id +//│ ║ l.120: (1).id //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme // parse 1.id //│ ╔══[LEXICAL ERROR] Expected at least one digit after the decimal point -//│ ║ l.133: 1.id +//│ ║ l.130: 1.id //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). -//│ ║ l.133: 1.id +//│ ║ l.130: 1.id //│ ╙── ^^ //│ = 1 :fixme "ha ".repeat(2) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.143: "ha ".repeat(2) +//│ ║ l.140: "ha ".repeat(2) //│ ║ ^^^^^^^^^^^^ //│ ╟── String literal does not contain member 'repeat' -//│ ║ l.143: "ha ".repeat(2) +//│ ║ l.140: "ha ".repeat(2) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -158,9 +155,9 @@ print(1) :e ().id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.159: ().id +//│ ║ l.156: ().id //│ ║ ^^^^^ //│ ╟── Unit value does not contain member 'id' -//│ ║ l.159: ().id +//│ ║ l.156: ().id //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 06f0a0257b..21bbeaec65 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -93,88 +93,71 @@ callid1(Predef) //│ | listenBMS: bms = module //│ | listenedBMS: bms = module //│ | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() -//│ | .exit MarkedShape (module:Predef¹, module:Predef¹, Predef²) -//│ | | class hkmc2.semantics.ModuleOrObjectSymbol ? -//│ | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | PUSH MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | .enter MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) (↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | .enter MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) (ϵ) -//│ | | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | zipArg: p = p⁰, trm = member:Predef⁰, sh = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) true, false -//│ | newSel: sel = p⁰.id, shape = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | newSel member: bms = member:id⁰, mss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | PUSH module:Predef¹ +//│ | .enter module:Predef¹ (↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .enter MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) +//│ | | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | zipArg: p = p⁰, trm = member:Predef⁰, sh = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) true, false +//│ | newSel: sel = p⁰.id, shape = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | newSel member: bms = member:id⁰, mss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | listenBMS: bms = function //│ | listenedBMS: bms = function -//│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) //│ | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) //│ | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) -//│ | | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | .exit MarkedShape (MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), module:Predef¹, Predef²) -//│ | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? -//│ | | | = MarkedShape(member:id⁰, ↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | = MarkedShape(member:id⁰, ↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | appShape: lhs = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), args = [1], res = p⁰.id(1) -//│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) +//│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) +//│ | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | appShape: lhs = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), args = [1], res = p⁰.id(1) +//│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) //│ | listenTerm: trm = 1 //│ | listen: trm = 1 //│ | introShape: intro = Lit(IntLit(1)) -//│ | .enter IntroShape(1) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | .enter IntroShape(1) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | .exit MarkedShape (IntroShape(1), term:callid1², callid1¹) //│ | | | class hkmc2.semantics.TermSymbol None //│ | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | .enter MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | .enter MarkedShape(IntroShape(1), ↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | | .enter MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) -//│ | | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false -//│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(module:Predef,Some(Predef),ExitMark(term:Predef/id,Some(⋅id),NoMarks))))) -//│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) +//│ | | .enter MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .enter MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) +//│ | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false +//│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(term:Predef/id,Some(⋅id),NoMarks)))) +//│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) //│ | appShape: td.body = Some(Ref(x)) //│ | listenTerm: trm = x⁰ //│ | listen: trm = x⁰ //│ | listenBMS: bms = function //│ | listenedBMS: bms = function -//│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) //│ | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) //│ | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) -//│ | | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | .exit MarkedShape (MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), module:Predef¹, Predef²) -//│ | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? -//│ | | | = MarkedShape(member:id⁰, ↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | = MarkedShape(member:id⁰, ↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) +//│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) +//│ | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) //│ | listenTerm: trm = 1 //│ | listen: trm = 1 -//│ | .enter IntroShape(1) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | .enter IntroShape(1) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | .exit MarkedShape (IntroShape(1), term:callid1², callid1¹) //│ | | | class hkmc2.semantics.TermSymbol None //│ | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | .enter MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | .enter MarkedShape(IntroShape(1), ↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | | .enter MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) -//│ | | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↘⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false -//│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(module:Predef,Some(Predef),ExitMark(term:Predef/id,Some(⋅id),NoMarks))))) -//│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨module:Predef¹⟩%⟨Predef²⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) +//│ | | .enter MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .enter MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) +//│ | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false +//│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(term:Predef/id,Some(⋅id),NoMarks)))) +//│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) //│ | appShape: td.body = Some(Ref(x)) //│ | listenTerm: trm = x⁰ //│ | listen: trm = x⁰ diff --git a/hkmc2/shared/src/test/mlscript/newres/ModuleCaptures.mls b/hkmc2/shared/src/test/mlscript/newres/ModuleCaptures.mls new file mode 100644 index 0000000000..d4f6835df5 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ModuleCaptures.mls @@ -0,0 +1,105 @@ +:. + +// Module members can capture an enclosing function parameter. +fun get(initial) = { + module Local { + val item = initial + } + Local.item +} + +:expect [0, 1] +[get(0), get(1)] +//│ = [0, 1] + +// Each additional module remains transparent to capture marks. +fun nested(initial) = { + module Outer { + module Inner { + val item = initial + } + } + Outer.Inner.item +} + +:expect [2, 3] +[nested(2), nested(3)] +//│ = [2, 3] + +// Objects follow the same rule; class capture behavior stays unchanged. +fun obj(initial) = { + object Local { + val item = initial + } + Local.item +} + +:expect [4, 5] +[obj(4), obj(5)] +//│ = [4, 5] + +fun cls(initial) = { + class Local { + val item = initial + } + (new Local).item +} + +:expect [6, 7] +[cls(6), cls(7)] +//│ = [6, 7] + +// Member functions retain their own boundaries across the module. +fun getters(initial) = { + module Local { + fun item = initial + fun method() = initial + } + [Local.item, Local.method()] +} + +:expect [[8, 8], [9, 9]] +[getters(8), getters(9)] +//│ = [[8, 8], [9, 9]] + +// Distinct member names expose accidental mixing of shapes from different calls. +module Left { val left = 11 } +module Right { val right = 12 } + +:expect [11, 12] +[get(Left).left, get(Right).right] +//│ = [11, 12] + +// Returning a module must retain its enclosing call context for later selections. +fun escape(initial) = { + module Local { val item = initial } + Local +} + +:expect [11, 12] +[escape(Left).item.left, escape(Right).item.right] +//│ = [11, 12] + +fun escapedNested(initial) = { + module Outer { + module Inner { val item = initial } + } + Outer +} + +:expect [11, 12] +[escapedNested(Left).Inner.item.left, escapedNested(Right).Inner.item.right] +//│ = [11, 12] + +// A returned module's methods retain both their own and their enclosing contexts. +fun escapedMethod(initial) = { + module Local { + fun read() = initial + } + Local +} + +:noInline +:expect [11, 12] +[escapedMethod(Left).read().left, escapedMethod(Right).read().right] +//│ = [11, 12] diff --git a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls index 2989e3359a..5ca1e60f06 100644 --- a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls +++ b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls @@ -101,12 +101,9 @@ M.p.id //│ | | | | listenBMS: bms = module //│ | | | | listenedBMS: bms = module //│ | | | | fromBMS: bms = member:M¹, sh = module:M⁰, flow = M², markss = List() -//│ | | | | .exit MarkedShape (module:M⁰, module:M⁰, M²) -//│ | | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? -//│ | | | | = MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) -//│ | | | | PUSH MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) -//│ | | | | newSel: sel = member:M¹.p, shape = MarkedShape(module:M⁰, ↗⟨module:M⁰⟩%⟨M²⟩ϵ) -//│ | | | | newSel member: bms = member:p¹, mss = List(↗⟨module:M⁰⟩%⟨M²⟩ϵ) +//│ | | | | PUSH module:M⁰ +//│ | | | | newSel: sel = member:M¹.p, shape = module:M⁰ +//│ | | | | newSel member: bms = member:p¹, mss = List() //│ | | | ~> member:M¹.p //│ | | | newSel? sel = member:M¹.p.id //│ | | | listenTerm: trm = member:M¹.p @@ -121,24 +118,14 @@ M.p.id //│ | | | listenBMS: bms = module //│ | | | listenedBMS: bms = module //│ | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() -//│ | | | .exit MarkedShape (module:Predef¹, module:Predef¹, Predef²) -//│ | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? -//│ | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | PUSH MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ), flow = ⋅p⁰, markss = List(↗⟨module:M⁰⟩%⟨M²⟩ϵ) -//│ | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ), term:M⁰/p⁰, ⋅p⁰) +//│ | | | PUSH module:Predef¹ +//│ | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = ⋅p⁰, markss = List() +//│ | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, ⋅p⁰) //│ | | | | class hkmc2.semantics.TermSymbol Some(module:M⁰) -//│ | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | .exit MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) (↗⟨module:M⁰⟩%⟨M²⟩ϵ) -//│ | | | | .exit MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) (ϵ) -//│ | | | | = MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ), module:M⁰, M²) -//│ | | | | | class hkmc2.semantics.ModuleOrObjectSymbol ? -//│ | | | | = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | MATCH MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) for term:M⁰/p⁰ % ⋅p⁰ -//│ | | | newSel: sel = member:M¹.p.id, shape = MarkedShape(module:Predef¹, ↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) -//│ | | | newSel member: bms = member:id⁰, mss = List(↗⟨module:M⁰⟩%⟨M²⟩↗⟨module:Predef¹⟩%⟨Predef²⟩ϵ) +//│ | | | = module:Predef¹ +//│ | | | MATCH module:Predef¹ for term:M⁰/p⁰ % ⋅p⁰ +//│ | | | newSel: sel = member:M¹.p.id, shape = module:Predef¹ +//│ | | | newSel member: bms = member:id⁰, mss = List() //│ | | ~> member:M¹.p.id //│ | ~> member:M¹.p.id //│ ~> { member:M¹.p.id } From e8ce64816b360f94edbc945c8e561a0e0f14a842 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 22 Sep 2026 13:05:18 +0800 Subject: [PATCH 012/228] Tweak comment --- .../shared/src/main/scala/hkmc2/semantics/NewResolver.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 3e8f2abc87..f4645a1fed 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -408,10 +408,7 @@ class NewResolver: log(s"fromBMS: bms = ${bms.showDbg}, sh = ${sh.shwDbg}, flow = ${resSym.showDbg}, markss = ${markss.map(_.showDbg)}") val sh0 = sh // Modules and objects introduce no enter/exit boundary of their own. - // Adding an exit here would try to consume an enclosing function's - // entry mark when a member captures one of that function's parameters. - // Receiver-context marks still apply, including when a function returns - // a module: they keep captures from different calls separate. + // Adding an exit here would create a mismatch because we do not track module captures explicitly. val exited = sym match case _: ModuleOrObjectSymbol => sh case _ => MarkedShape.exit(sh, sym, S(resSym)) From f07653b66ba79bff6d59d21508a91b06e21fce6a Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 13:29:32 +0800 Subject: [PATCH 013/228] Resolve new-resolution opens through deferred shape listeners --- doc/new-resolution-plan.md | 33 +- .../main/scala/hkmc2/codegen/Lowering.scala | 29 ++ .../scala/hkmc2/semantics/Elaborator.scala | 82 ++-- .../main/scala/hkmc2/semantics/Importer.scala | 7 + .../scala/hkmc2/semantics/NewResolver.scala | 87 ++++- .../src/main/scala/hkmc2/semantics/Term.scala | 11 + .../main/scala/hkmc2/semantics/TermImpl.scala | 2 +- .../src/test/mlscript/newres/Functions.mls | 29 -- .../shared/src/test/mlscript/newres/Opens.mls | 358 ++++++++++++++++++ .../src/test/mlscript/newres/PatMat.mls | 4 +- 10 files changed, 574 insertions(+), 68 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/Opens.mls diff --git a/doc/new-resolution-plan.md b/doc/new-resolution-plan.md index 18fac53282..f88f92e407 100644 --- a/doc/new-resolution-plan.md +++ b/doc/new-resolution-plan.md @@ -39,7 +39,9 @@ receiver and capture context; module completion must not change lexical priority reporting ambiguity does not entail performing name resolution in lowering. 4. **Enforce the phase boundary for all uses.** Ensure ordinary value uses request term resolution, while class and pattern - uses request their appropriate interpretation. Remove definition-selection + uses request their appropriate interpretation. Replace eager bare-pattern name + classification so opened constructors are recognized without syntax lookup. + Remove definition-selection fallbacks from `MemberRef`/`NewSel` lowering and audit `SelElem` shape seeding. Distinguish unresolved work from diagnosed errors and successful selections; assert internal invariants instead of silently guessing. Audit backend class @@ -72,5 +74,30 @@ receiver and capture context; module completion must not change lexical priority `newres/ModuleCaptures.mls` covers nested and escaping modules, objects, getters, methods, and separation of captures from distinct calls. Validation passed: `ctest` (45 tests), `dtest newres/` (25 files), and `hkmc2AllTests/test`. -- Stages 3 and 4 remain pending. In particular, `moduleMembers`, the lexical - `SelElem` shape shortcut, and the read-side lowering fallbacks are still present. +- Stage 3 implemented: contexts retain wildcard sources and search the full + explicit environment chain first. `UnresolvedRef` listens to the sources and + retains each candidate's receiver; lowering reports missing or ambiguous + candidates without doing resolution. Selective opens now use selection + listeners, and `moduleMembers` is removed. Import aliases propagate shapes. +- Ordinary wildcard references request term interpretation after elaboration; + constructor targets retain their symbolic interpretation. This traversal will + be generalized in stage 4. Primitive operators retain their existing implicit + binding priority, ahead of wildcard sources. +- Stage 3 regressions cover forward and indented modules, both open forms, + declaration and open orders, outer explicit bindings, parameters, repeated and + competing sources, distinct receivers sharing one member symbol, chained opens, + imports, nested captures, class/value and class/function overloads, applied + constructor patterns, and temporary assignment restoration. +- Deferred uses exposed duplicate shape-listener registration and an unfinished + constructor-shape cache branch. Listeners are now registered once; cached + constructors are reused with assertions checking their definition and base. + An existing applied-pattern test now passes without its `:todo` marker. +- Stage 4 remains pending: the lexical `SelElem` shape shortcut and read-side + lowering fallbacks still exist. Bare constructor names in patterns still use + eager classification; `Opens.mls` records this limitation with a `:fixme` + regression for both open forms. Applied constructor patterns already pass. + +- Stage 3 validation: `ctest` passed (45 tests), `dtest newres/` passed + (26 files), and `hkmc2AllTests/test` passed with the final regression outputs. + Golden changes remove duplicate debug callbacks and replace the fixed + applied-pattern exception with its expected result. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index f6ccc76bbb..77590b9413 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -646,6 +646,23 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case td: TermDefinition => (td.k is syntax.Fun) && td.params.isEmpty case _ => false + /** Consume wildcard lookup results, retaining the receiver selected by resolution. */ + private def openSelection(ref: UnresolvedRef)(k: (Term, DefinitionSymbol[?]) => Block)(using LoweringCtx): Block = + if ref.isErroneous then compError else ref.resolvedMembers.distinct match + case (prefix, _) :: Nil => ref.resolvedTargets.distinct match + case target :: Nil => k(prefix, target) + case targets => fail: + ErrorReport(msg"Wildcard-open reference '${ref.id.name}' requires one resolved definition" -> ref.toLoc :: + targets.map(target => msg"target: ${target.describeKind}" -> target.toLoc), + source = Diagnostic.Source.Compilation) + case Nil => fail: + ErrorReport(msg"Name not found in wildcard opens: ${ref.id.name}" -> ref.toLoc :: Nil, + source = Diagnostic.Source.Compilation) + case candidates => fail: + ErrorReport(msg"Wildcard-open reference '${ref.id.name}' is ambiguous" -> ref.toLoc :: + candidates.map((prefix, member) => msg"candidate from ${prefix.showDbg}: ${member.describe}" -> member.toLoc), + source = Diagnostic.Source.Compilation) + def selSymbol(sel: AnySelTerm): Opt[DefinitionSymbol[?]] = sel.validResolvedTargets match // sel.resolvedTargets match @@ -920,6 +937,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): msg"target: ${t.describeKind}" -> t.toLoc , S(t), source = Diagnostic.Source.Compilation) compError + case ref: UnresolvedRef => + openSelection(ref)((prefix, target) => setupSelection(prefix, ref.id, S(target))(k)) case Capture(base, thru) => term(base, inStmtPos = inStmtPos)(k) case t @ st.Ref(sym) => @@ -1121,6 +1140,16 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case Ref(sym) => subTerm(rhs): r => assignSymbol(resolvedSelectionSymbol.getOrElse(sym), sym, r, k(unit), trm.toLoc) + case ref: UnresolvedRef => + openSelection(ref): (prefix, target) => + target match + case sym: TermSymbol => + subTerm_nonTail(prefix): p => + subTerm_nonTail(rhs): r => + AssignField(p, definitionIdent(ref.id, sym), castTo(r, sym.erasedType, ref.toLoc), k(unit))(S(sym)) + case _ => fail: + ErrorReport(msg"Assignment requires a term member" -> ref.toLoc :: Nil, + source = Diagnostic.Source.Compilation) case sel @ NewSel(prefix, nme) => // Term resolution selects the definition; lowering only checks that // the result is an unambiguous term member and emits the field write. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index d55e68d7dc..2aca5d2fd9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -99,6 +99,7 @@ object Elaborator: env: Map[Str, Ctx.Elem], mode: Mode, labels: Map[LabelSymbol, LabelBinding], + wildcardOpens: Ls[Ctx.Elem], ): override def toString: Str = s"${parent.fold("")(_.toString+"/")}${outer.showDbg}" @@ -137,7 +138,7 @@ object Elaborator: labelSym, resultSym, nonLocalHandlerSym, nonLocalBreakMethodMarker, nonLocalContinueMethodMarker)) ) - def nest(outerCtx: OuterCtx): Ctx = Ctx(outerCtx, Some(this), Map.empty, mode, Map.empty) + def nest(outerCtx: OuterCtx): Ctx = Ctx(outerCtx, Some(this), Map.empty, mode, Map.empty, Nil) def nestLocal(nameHint: Str): Ctx = nest(OuterCtx.LocalScope(nameHint)) def nestInner(inner: InnerSymbol): Ctx = nest(OuterCtx.InnerScope(inner)) @@ -175,14 +176,27 @@ object Elaborator: case _ => parent.flatMap(_.get(name)) else parent.flatMap(_.get(name)) */ + /** Explicit bindings in any enclosing scope take precedence over every wildcard open. */ def get(name: Str)(using config: Config): Opt[Ctx.Elem] = + getExplicit(name).orElse: + if config.language.useNewResolution then + val sources = visibleWildcardOpens.distinct + if sources.isEmpty then N else S(Ctx.WildcardElem(name, sources)) + else N + + private def getExplicit(name: Str)(using config: Config): Opt[Ctx.Elem] = env.get(name).orElse: - val nr = config.language.useNewResolution - if nr then outer match - case OuterCtx.NonReturnContext(S(sym)) => - parent.flatMap(_.get(name)).map(Ctx.CaptElem(_, sym)) - case _ => parent.flatMap(_.get(name)) - else parent.flatMap(_.get(name)) + val inherited = parent.flatMap(_.getExplicit(name)) + if config.language.useNewResolution then outer match + case OuterCtx.NonReturnContext(S(sym)) => inherited.map(Ctx.CaptElem(_, sym)) + case _ => inherited + else inherited + + private lazy val visibleWildcardOpens: Ls[Ctx.Elem] = + val inherited = parent.toList.flatMap(_.visibleWildcardOpens) + wildcardOpens ::: (outer match + case OuterCtx.NonReturnContext(S(sym)) => inherited.map(Ctx.CaptElem(_, sym)) + case _ => inherited) def lookupLabel(name: Str): LabelLookup = @tailrec @@ -423,12 +437,12 @@ object Elaborator: object Ctx: abstract class Elem: def nme: Str - def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term def symbol: Opt[Symbol] def isImport: Bool final case class RefElem(sym: Symbol) extends Elem: val nme = sym.nme - def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term = + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term = if config.language.useNewResolution then sym match case sym: codegen.SimpleSymbol => @@ -447,29 +461,41 @@ object Elaborator: def symbol = S(sym) def isImport: Bool = false final case class SelElem(base: Elem, nme: Str, symOpt: Opt[MemberSymbol], isImport: Bool) extends Elem: - def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term = + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term = // * Same remark as in RefElem#ref val prefix = base.ref(Ident(base.nme)) val name = new Ident(nme).withLocOf(id) symOpt match + case _ if config.language.useNewResolution && isImport => + val res = new Term.NewSel(prefix, name)(FlowSymbol.synthSel(nme)).withLocOf(id) + summon[NewResolver].newSel(res) + res case S(bms: BlockMemberSymbol) if config.language.useNewResolution => // Lexical lookup already identifies the member, but must preserve its // overload set until its use chooses a class, term, or module. - val res = new Term.NewSel(prefix, name)(FlowSymbol.synthSel(nme)) + val res = new Term.NewSel(prefix, name)(FlowSymbol.synthSel(nme)).withLocOf(id) res.resolvedMembers = bms :: Nil res.shapes += SymShape(bms, res.resSym, Nil) res case _ => Term.SynthSel(prefix, name)(symOpt, FlowSymbol.synthSel(nme), N, S(summon)) def symbol = symOpt + final case class WildcardElem(nme: Str, sources: Ls[Elem]) extends Elem: + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term = + val prefixes = sources.map(source => source.ref(Ident(source.nme))) + val res = new Term.UnresolvedRef(prefixes, id)(FlowSymbol.synthSel(nme)).withLocOf(id) + summon[NewResolver].unresolvedRef(res) + res + def symbol: Opt[Symbol] = N + def isImport: Bool = true final case class CaptElem(base: Elem, thru: DefinitionSymbol[?]) extends Elem: - def ref(id: Ident)(using Elaborator.State, Ctx, Config): Term = + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term = Term.Capture(base.ref(Ident(base.nme)), thru) def symbol = base.symbol def isImport: Bool = false def nme: Str = base.nme given Conversion[Symbol, Elem] = RefElem(_) - val empty: Ctx = Ctx(OuterCtx.LocalScope("top-level"), N, Map.empty, Mode.Full, Map.empty) + val empty: Ctx = Ctx(OuterCtx.LocalScope("top-level"), N, Map.empty, Mode.Full, Map.empty, Nil) enum Mode: case Full @@ -703,6 +729,7 @@ class Elaborator(val tl: TraceLogger, val wd: io.Path, val prelude: Ctx) extends Importer: import tl.* given TraceLogger = tl + private given NewResolver = this val newResolution: Bool = config.language.useNewResolution @@ -710,12 +737,6 @@ extends Importer: msg"Member names must start with a letter or underscore, followed by letters, digits, or underscores." -> N :: Nil - private def moduleMembers(sym: BlockMemberSymbol): Opt[Map[Str, BlockMemberSymbol]] = - // Elaborated bodies cover both braced and `with` bodies. Forward references - // still need the syntax table before the module definition is available. - (if newResolution then sym.asModOrObj.flatMap(_.defn).map(_.body.members) else N) - .orElse(sym.modOrObjTree.map(_.definedSymbols)) - def mkLetBinding(kw: Tree.Keywrd[?], sym: LocalVarSymbol | TermSymbol, rhs: Term, annotations: Ls[Annot]): Ls[Statement] = LetDecl(sym, annotations).mkLocWith(kw, sym) :: defineVar(sym, rhs) :: Nil @@ -725,7 +746,7 @@ extends Importer: case S(psym: BlockMemberSymbol) => psym.modOrObjTree match case S(cls) => - moduleMembers(psym).flatMap(_.get(nme.name)) match + cls.definedSymbols.get(nme.name) match case s @ S(clsSym) => s case N => raise(ErrorReport(msg"${cls.k.desc.capitalize} '${cls.symbol.nme @@ -1957,12 +1978,15 @@ extends Importer: base match case baseId: Ident => ctx.get(baseId.name) match + case S(baseElem) if newResolution && importedTrees.isEmpty => + ctx.copy(wildcardOpens = baseElem :: ctx.wildcardOpens).givenIn: + go(sts, Nil, acc) case S(baseElem) => val importedNames = importedTrees match case N => // "wilcard" open baseElem.symbol match case S(sym: BlockMemberSymbol) if sym.modOrObjTree.isDefined => - moduleMembers(sym).getOrElse(Map.empty).map: + sym.modOrObjTree.get.definedSymbols.map: case (nme, sym) => nme -> Ctx.SelElem(baseElem, sym.nme, S(sym), isImport = true) case _ => raise(ErrorReport(msg"Wildcard 'open' not supported for this kind of symbol." -> baseId.toLoc :: Nil)) @@ -1971,8 +1995,10 @@ extends Importer: case id: Ident => if ctx.env.contains(id.name) then raise(WarningReport(msg"Imported name '${id.name}' is shadowed by a name already defined in the same scope" -> id.toLoc :: Nil)) - val sym = resolveField(id, baseElem.symbol, id) + val sym = if newResolution then N else resolveField(id, baseElem.symbol, id) val e = Ctx.SelElem(baseElem, id.name, sym, isImport = true) + // Validate the named selection even when its binding is unused. + if newResolution then e.ref(id) id.name -> e :: Nil case t => raise(ErrorReport(msg"Illegal 'open' statement element." -> t.toLoc :: Nil)) @@ -2747,11 +2773,13 @@ extends Importer: (ParamList(ParamListFlags.empty, Nil, N).withLocOf(t), ctx) def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = ctx.get(id.name) match - case S(elem) => S(elem.ref(id)) - case N => + case candidate @ (S(_: Ctx.WildcardElem) | N) => + // Primitive operators are implicit bindings outside the Ctx environments; + // like explicit bindings, they take precedence over wildcard sources. state.builtinOpsMap.get(id.name) match - case S(bi) => S(bi.ref(id)) - case N => N + case S(bi) => S(bi.ref(id)) + case N => candidate.map(_.ref(id)) + case S(elem) => S(elem.ref(id)) def pattern(t: Tree): Ctxl[Pattern] = import ucs.{Ctor, unapply, error}, ucs.extractors.*, Keyword.*, Pattern.*, InvalidReason.* @@ -2963,12 +2991,14 @@ extends Importer: def importFrom(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) val (res, newCtx) = block(sts, hasResult = false) + if newResolution then resolveOpenUses(res) // TODO handle name clashes (res, newCtx) def topLevel(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) val (res, ctx) = block(sts, hasResult = false) + if newResolution then resolveOpenUses(res) computeVariances(res) (res, ctx) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala index 2045a29a41..43ce9be204 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala @@ -64,6 +64,13 @@ class Importer extends NewResolver: val sym: VarSymbol | BlockMemberSymbol = alias.fold(importedSym): alias => VarSymbol(alias, erasedType = N) + // An import alias denotes the imported value, just like a local + // binding. Publish its shapes so selection and opens can follow it. + if newResolution then sym match + case local: VarSymbol => + pipeTerm(Term.MemberRef(importedSym)(local.id, FlowSymbol.memSym(importedSym)), local) + case _: BlockMemberSymbol => () + val jsFile = file.up / io.RelPath(file.baseName + ".mjs") Import(sym, jsFile.toString, jsFile) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index f4645a1fed..1b08be53fd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -288,6 +288,77 @@ class NewResolver: softAssert(res.isErroneous) else register + private def publishMember(host: ShapeHost, member: BlockMemberSymbol, + flow: FlowSymbol, marks: Ls[Marks]): Unit = + val shape = symShapes.getOrElseUpdate((member, flow, marks), SymShape(member, flow, marks)) + if host.shapes.add(shape) then host.shapeListeners.foreach(_(shape)) + + def unresolvedRef(ref: UnresolvedRef): Unit = + ref.prefixes.foreach: prefix => + listenTerm(prefix): shape => + // A miss in one wildcard source is not an error: another may provide + // the name. A reference with no candidates is diagnosed by lowering. + shape.getMember(ref.id.name).foreach: (member, marks) => + val candidate = prefix -> member + if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate + publishMember(ref, member, ref.resSym, marks) + + /** Bare wildcard references need a term interpretation even when nobody asks + * for their value shape. Walk runtime uses after elaboration so constructor + * targets retain their symbolic interpretation. No member lookup happens here. + */ + def resolveOpenUses(root: Statement): Unit = + def symbolic(term: Term): Unit = term.withoutCaptures match + case UnresolvedRef(prefixes, _) => prefixes.foreach(value) + case NewSel(prefix, _) => value(prefix) + case _: MemberRef => () + case other => other.subTerms.foreach(value) + def pattern(pat: Pattern): Unit = pat match + case Pattern.Constructor(target, arguments) => + symbolic(target) + arguments.foreach(_.foreach(pattern)) + // These children flatten their subpatterns, so visit the patterns explicitly. + case Pattern.Record(fields) => fields.foreach((_, pat) => pattern(pat)) + case Pattern.Guarded(pat, guard) => pattern(pat); value(guard) + case other => other.children.foreach: + case pat: Pattern => pattern(pat) + case term: Term => value(term) + case _ => () + def split(s: SimpleSplit): Unit = s match + case SimpleSplit.Cons(head, tail) => + head match + case SimpleSplit.Head.Match(scrutinee, pat, consequent) => + value(scrutinee) + pattern(pat) + split(consequent) + case SimpleSplit.Head.Let(_, rhs) => value(rhs) + split(tail) + case SimpleSplit.Else(term) => value(term) + case SimpleSplit.End => () + def value(stmt: Statement): Unit = stmt match + case ref: UnresolvedRef => + listenTerm(ref)(_ => ()) + ref.prefixes.foreach(value) + case New(cls, args, refinement) => + symbolic(cls) + args.foreach(value) + refinement.foreach((_, body) => value(body.blk)) + case IfLike(_, _, branches) => split(branches) + case Asc(term, _) => value(term) + case TyApp(term, _) => value(term) + case Lam(_, body) => value(body) + case td: TermDefinition => + td.body.foreach(value) + td.annotations.flatMap(_.subTerms).foreach(value) + case pd: PatternDef => pattern(pd.pattern) + case cd: ClassLikeDef => + value(cd.body.blk) + cd.ext.foreach(value) + cd.annotations.flatMap(_.subTerms).foreach(value) + case _: TypeDef => () + case other => other.subStatements.foreach(value) + value(root) + def newSel(sel: NewSel): Unit = log(s"newSel? sel = ${sel.showDbg}") listenTerm(sel.prefix): shape => @@ -296,9 +367,7 @@ class NewResolver: case S((bms, mss)) => log(s"newSel member: bms = ${bms.showDbg}, mss = ${mss.map(_.showDbg)}") sel.resolvedMembers ::= bms - val sh = symShapes.getOrElseUpdate((bms, sel.resSym, mss), SymShape(bms, sel.resSym, mss)) - if sel.shapes.add(sh) then - sel.shapeListeners.foreach(listener => listener(sh)) + publishMember(sel, bms, sel.resSym, mss) // symShapes.getOrElseUpdate((bms, sel.resSym), SymShape(bms, sel.resSym)) // .exit(mss) match // case NoShape => @@ -436,9 +505,14 @@ class NewResolver: case ccs: ClassCtorSymbol => val cls = ccs.associatedCls.defn.get listenExt(cls.ext, extsh => - defnShapes.get(sym).foreach: existing => - ??? // TODO error? - wrappedListener(defnShapes.getOrElseUpdate(sym, DefnShape(d, S(BaseShape(cls, extsh))))) + // Several uses (including deferred opens) can request the same + // constructor shape. Reuse it while checking the cache invariant. + val shape = defnShapes.getOrElseUpdate(sym, DefnShape(d, S(BaseShape(cls, extsh)))) + softAssert(shape.defn is d) + shape.ext match + case S(base: BaseShape) => softAssert((base.defn is cls) && base.ext == extsh) + case _ => softAssert(false, "Constructor shape is missing its class base") + wrappedListener(shape) ) case _ => wrappedListener(defnShapes.getOrElseUpdate(sym, DefnShape(d, N))) @@ -527,7 +601,6 @@ class NewResolver: // ??? case sh: ShapeHost => sh.shapes.foreach(listener) - sh.shapeListeners += listener case Blk(sts, rs) => listen(rs)(listener) // case u: UnitVal => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 25078f73bf..7435672ca1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -362,6 +362,12 @@ sealed trait NewSelImpl extends NewResolvableImpl: self: Term.NewSel => var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution +sealed trait UnresolvedRefImpl extends NewResolvableImpl: + self: Term.UnresolvedRef => + // Retain the receiver as well as the definition: two instances can expose + // the same member symbol without denoting the same storage location. + var resolvedMembers: Ls[(Term, BlockMemberSymbol)] = Nil + enum Term extends Statement, ShapePublisher: case Error() @@ -374,6 +380,7 @@ enum Term extends Statement, ShapePublisher: case SelfRef(sym: InnerSymbol)(val tree: Tree.Ident) extends Term, NewRefImpl case MemberRef(sym: MemberSymbol)(val tree: Tree.Ident, val resSym: FlowSymbol) extends Term, NewResolvableImpl, NewRefImpl case NewSel(prefix: Term, id: Tree.Ident)(val resSym: FlowSymbol) extends Term, NewSelImpl, ShapeHost + case UnresolvedRef(prefixes: Ls[Term], id: Tree.Ident)(val resSym: FlowSymbol) extends Term, UnresolvedRefImpl, ShapeHost case Capture(base: Term, thru: AnyDefinitionSymbol) extends Term // --- LEGACY --- /** A term that wraps another term, indicating that the symbol of the inner term is resolved. @@ -729,6 +736,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case Capture(base, thru) => base.describe case SimpleRef(sym) => "reference" case MemberRef(sym) => "member reference" + case UnresolvedRef(_, _) => "wildcard-open reference" case App(lhs, rhs) => "application" case TyApp(lhs, targs) => "type application" case NewSel(pre, nme) => "selection" @@ -807,6 +815,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case Sel(pre, _) => Vector.single(pre) case SynthSel(pre, _) => Vector.single(pre) case NewSel(pre, _) => Vector.single(pre) + case UnresolvedRef(prefixes, _) => prefixes.toVector case DynSel(o, f, _) => Vector.double(o, f) case Tup(fields) => fields.flatMap(_.subTerms).toVector case Mut(und) => Vector.single(und) @@ -898,6 +907,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case _: BuiltinSymbol => r.sym.nme case _ => r.sym.showName case r: MemberRef => r.sym.showName + case r: UnresolvedRef => doc"${r.id.name}" case sr: SelfRef => s"${sr.sym.showName}.this" case Capture(base, thru) => // doc"${base.show}⟨${thru.showName}⟩" @@ -1042,6 +1052,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case Sel(pre, nme) => s"${pre.showDbg}.${nme.name}" case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" case NewSel(pre, nme) => s"${pre.showDbg}.${nme.name}" + case UnresolvedRef(_, id) => s"${id.name}‹open›" case DynSel(pre, fld, _) => s"${pre.showDbg}[${fld.showDbg}]" case IfLike(kw, _, split) => s"${kw.name} { ${split.showDbg} }" case SynthIf(split) => s"if { ${split.showDbg} }" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala index 4944a86d8d..cdca2434f1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala @@ -28,7 +28,7 @@ type AnyResolvable = Resolvable | NewResolvableImpl type NewResolvable = NewResolvableImpl & Term trait NewResolvableImpl: - self: MemberRef | NewSel => + self: MemberRef | NewSel | UnresolvedRef => var resolvedTargets: Ls[DefinitionSymbol[?]] = Nil // * filled during flow analysis // val resSym: FlowSymbol = FlowSymbol.simpleRef(self.sym.name) // var disamb: Opt[Disambiguation] = None // * filled during flow analysis diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 21bbeaec65..5e208a2d48 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -132,35 +132,6 @@ callid1(Predef) //│ | appShape: td.body = Some(Ref(x)) //│ | listenTerm: trm = x⁰ //│ | listen: trm = x⁰ -//│ | listenBMS: bms = function -//│ | listenedBMS: bms = function -//│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) -//│ | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) -//│ | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) -//│ | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) -//│ | listenTerm: trm = 1 -//│ | listen: trm = 1 -//│ | .enter IntroShape(1) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | .exit MarkedShape (IntroShape(1), term:callid1², callid1¹) -//│ | | | class hkmc2.semantics.TermSymbol None -//│ | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | .enter MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | .enter MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) -//│ | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false -//│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(term:Predef/id,Some(⋅id),NoMarks)))) -//│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) -//│ | appShape: td.body = Some(Ref(x)) -//│ | listenTerm: trm = x⁰ -//│ | listen: trm = x⁰ //│ | appShape isSaturated? true; head? (DefnShape(term definition 'callid1'),List(ExitMark(term:callid1,Some(callid1),NoMarks))) //│ | lhs false List(((p⁰),List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ))) //│ | appShape: td.body = Some(App(NewSel(SimpleRef(p),Ident(id)),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))) diff --git a/hkmc2/shared/src/test/mlscript/newres/Opens.mls b/hkmc2/shared/src/test/mlscript/newres/Opens.mls new file mode 100644 index 0000000000..eefb8872e3 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Opens.mls @@ -0,0 +1,358 @@ +:. + + +// Explicit bindings across the complete context chain precede wildcard sources. +val explicit = 1 +module Values { + val explicit = 2 + val extra = 3 +} +fun outerBinding() = { + open Values + explicit + extra +} +//│ explicit = 1 + +:expect 4 +outerBinding() +//│ = 4 + + +// Neither order of a wildcard open lets it shadow a member definition. +fun before() = { + open Values + val extra = 4 + extra +} +fun after() = { + val extra = 5 + open Values + extra +} + +:expect [4, 5] +[before(), after()] +//│ = [4, 5] + + +module Selected { val extra = 6 } +fun named() = { + open Selected { extra } + fun nested() = { + open Values + extra + } + nested() +} + +:expect 6 +named() +//│ = 6 + + +// Both open forms work before the module definition is elaborated. +fun forwardWildcard() = { + open Later + answer +} +fun forwardNamed() = { + open Later { answer } + answer +} +module Later { val answer = 7 } + +:expect [7, 7] +[forwardWildcard(), forwardNamed()] +//│ = [7, 7] + + +// A nonmatching wildcard source does not suppress a matching one. +module Other { val unrelated = 8 } +fun several() = { + open Other + open Later + answer +} + +:expect 7 +several() +//│ = 7 + + +// Reopening the same source does not introduce a second candidate. +fun repeated() = { + open Later + open Later + answer +} + +:expect 7 +repeated() +//│ = 7 + + +// Two distinct sources make a name ambiguous, independently of open order. +:e +fun ambiguous() = { + open Values + open Selected + extra +} +ambiguous() +//│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous +//│ ║ l.99: extra +//│ ║ ^^^^^ +//│ ╟── candidate from member:Values⁰^term:ambiguous⁰: value +//│ ║ l.8: val extra = 3 +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── candidate from member:Selected⁰^term:ambiguous⁰: value +//│ ║ l.38: module Selected { val extra = 6 } +//│ ╙── ^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +fun missing() = { + open Later + absent +} +missing() +//│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: absent +//│ ║ l.116: absent +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +open Later { absent } +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.125: open Later { absent } +//│ ║ ^^^^^^ +//│ ╟── Module 'Later' does not contain member 'absent' +//│ ║ l.62: module Later { val answer = 7 } +//│ ╙── ^^^^^ + + +// Wildcard lookup retains overload sets until the use chooses a definition. +module Overloaded { + class Item { fun answer() = 9 } + mut val Item = 10 +} +fun overload() = { + open Overloaded + set Item = 11 + [Item, (new Item).answer()] +} + +:expect [11, 9] +overload() +//│ = [11, 9] + + +// Captured module contexts survive deferred lookup and nested functions. +module Left { val left = 12 } +module Right { val right = 13 } +fun captured(initial) = { + module Local { val item = initial } + open Local + fun read() = item + read() +} + +:expect [12, 13] +[captured(Left).left, captured(Right).right] +//│ = [12, 13] + + +// An opened module can itself come from a wildcard source. +module Outer { + module Inner { val innerValue = 14 } +} +fun chained() = { + open Outer + open Inner + innerValue +} + +:expect 14 +chained() +//│ = 14 + + +// Imported aliases expose the imported module's shapes. +import "../../mlscript-compile/OverloadedClassLibrary.mls" as Library +fun imported() = { + open Library + [Item, (new Item).answer()] +} +fun importedNamed() = { + open Library { Item } + [Item, (new Item).answer()] +} + +:expect [[16, 52], [16, 52]] +[imported(), importedNamed()] +//│ = [[16, 52], [16, 52]] + + +// Indented module bodies use the same completed shapes as braced bodies. +fun indented() = { + open Indented + value +} +module Indented with + val value = 15 + +:expect 15 +indented() +//│ = 15 + + +// Two distinct receivers must not silently select the same member on the wrong instance. +fun instance(n) = { + module M { val value = n } + M +} + +:e +let first = instance(16) +let second = instance(17) +fun twoInstances() = { + open first + open second + value +} +twoInstances() +//│ ╔══[COMPILATION ERROR] Wildcard-open reference 'value' is ambiguous +//│ ║ l.221: value +//│ ║ ^^^^^ +//│ ╟── candidate from first⁰^term:twoInstances⁰: value +//│ ║ l.211: module M { val value = n } +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── candidate from second⁰^term:twoInstances⁰: value +//│ ║ l.211: module M { val value = n } +//│ ╙── ^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ first = class M { value: 16 } +//│ second = class M { value: 17 } + + +// Parameters are explicit bindings even when an open provides the same name. +fun parameter(extra) = { + open Values + extra +} + +:expect 18 +parameter(18) +//│ = 18 + + +// Applied constructor patterns demand the class interpretation, not a term value. +module Constructors { class Box(val value) } +fun unbox(box) = { + open Constructors + if box is Box(value) then value else 0 +} + +:expect 19 +unbox(Constructors.Box(19)) +//│ = 19 + + +// A binding brought in explicitly prevents wildcard class lookup too. +:e +module ClassSource { class Shadowed } +val Shadowed = 20 +open ClassSource +new Shadowed +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.266: new Shadowed +//│ ║ ^^^^^^^^^^^^ +//│ ╙── Value symbol 'Shadowed' cannot be instantiated with keyword 'new'. +//│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ Shadowed = 20 + + +// Explicit selective opens win regardless of their order relative to wildcard opens. +fun namedAfter() = { + open Values + open Selected { extra } + extra +} +fun namedBefore() = { + open Selected { extra } + open Values + extra +} + +:expect [6, 6] +[namedAfter(), namedBefore()] +//│ = [6, 6] + +:e +fun ambiguousReversed() = { + open Selected + open Values + extra +} +ambiguousReversed() +//│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous +//│ ║ l.296: extra +//│ ║ ^^^^^ +//│ ╟── candidate from member:Selected⁰^term:ambiguousReversed⁰: value +//│ ║ l.38: module Selected { val extra = 6 } +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── candidate from member:Values⁰^term:ambiguousReversed⁰: value +//│ ║ l.8: val extra = 3 +//│ ╙── ^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +// Both the temporary assignment and restoration use the selected open member. +fun temporary() = { + open Overloaded + let during = set Item = 21 in Item + [during, Item] +} + +:expect [21, 11] +temporary() +//│ = [21, 11] + + +module Callable { + class Make { fun answer() = 22 } + fun Make() = new Make +} +fun callable() = { + open Callable + [Make().answer(), (new Make).answer()] +} + +:expect [22, 22] +callable() +//│ = [22, 22] + + +// Stage 4: bare pattern names still require deferred constructor-vs-binding classification. +:fixme +:expect [1, 0, 1, 0] +module BarePatterns { class Token } +fun bareWildcard(x) = { + open BarePatterns + if x is Token then 1 else 0 +} +fun bareNamed(x) = { + open BarePatterns { Token } + if x is Token then 1 else 0 +} +[bareWildcard(new BarePatterns.Token), bareWildcard(0), + bareNamed(new BarePatterns.Token), bareNamed(0)] +//│ ╔══[COMPILATION ERROR] Pattern name not found: Token. +//│ ║ l.343: if x is Token then 1 else 0 +//│ ╙── ^^^^^ +//│ ╔══[COMPILATION ERROR] Pattern name not found: Token. +//│ ║ l.347: if x is Token then 1 else 0 +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] Expected: '[1, 0, 1, 0]', got: '[1, 1, 1, 1]' +//│ = [1, 1, 1, 1] diff --git a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls index 7b09703d23..792c28d94a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls @@ -152,10 +152,10 @@ if x2 is //│ = fun id -:todo +:expect fun id if Foo(Predef) is Foo(y) then y.id -//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +//│ = fun id :todo From 13e82386e9a5bf944fe6744b04f65e878e8cc0b6 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 13:50:37 +0800 Subject: [PATCH 014/228] Thread required interpretations through elaboration --- doc/new-resolution-plan.md | 31 +++- .../scala/hkmc2/semantics/Elaborator.scala | 174 ++++++++++-------- .../scala/hkmc2/semantics/NewResolver.scala | 79 ++------ .../src/test/mlscript/newres/Basics.mls | 33 +++- .../src/test/mlscript/newres/Functions.mls | 28 +++ .../test/mlscript/newres/Interpretations.mls | 99 ++++++++++ .../src/test/mlscript/newres/WIPTest.mls | 41 +++++ 7 files changed, 334 insertions(+), 151 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/Interpretations.mls diff --git a/doc/new-resolution-plan.md b/doc/new-resolution-plan.md index f88f92e407..2d1de24dd0 100644 --- a/doc/new-resolution-plan.md +++ b/doc/new-resolution-plan.md @@ -79,10 +79,9 @@ receiver and capture context; module completion must not change lexical priority retains each candidate's receiver; lowering reports missing or ambiguous candidates without doing resolution. Selective opens now use selection listeners, and `moduleMembers` is removed. Import aliases propagate shapes. -- Ordinary wildcard references request term interpretation after elaboration; - constructor targets retain their symbolic interpretation. This traversal will - be generalized in stage 4. Primitive operators retain their existing implicit - binding priority, ahead of wildcard sources. +- Primitive operators retain their existing implicit binding priority, ahead of + wildcard sources. The initial stage-3 post-elaboration traversal has been + replaced by the contextual elaboration described below. - Stage 3 regressions cover forward and indented modules, both open forms, declaration and open orders, outer explicit bindings, parameters, repeated and competing sources, distinct receivers sharing one member symbol, chained opens, @@ -92,7 +91,7 @@ receiver and capture context; module completion must not change lexical priority constructor-shape cache branch. Listeners are now registered once; cached constructors are reused with assertions checking their definition and base. An existing applied-pattern test now passes without its `:todo` marker. -- Stage 4 remains pending: the lexical `SelElem` shape shortcut and read-side +- Remaining stage-4 work: the lexical `SelElem` shape shortcut and read-side lowering fallbacks still exist. Bare constructor names in patterns still use eager classification; `Opens.mls` records this limitation with a `:fixme` regression for both open forms. Applied constructor patterns already pass. @@ -101,3 +100,25 @@ receiver and capture context; module completion must not change lexical priority (26 files), and `hkmc2AllTests/test` passed with the final regression outputs. Golden changes remove duplicate debug callbacks and replace the fixed applied-pattern exception with its expected result. + +- Contextual interpretation is now threaded through `term` and `subterm`: + ordinary term uses request resolution during elaboration; class and pattern + targets retain their specialized interpretation, and type positions do not + request runtime values. `resolveOpenUses` and its second tree walk are removed. +- Only `term` and `subterm` default to term interpretation. This is a deliberate + exception to the usual rule against defaults: ordinary expression calls stay + concise, while symbolic positions and transparent wrappers explicitly specify + or forward the interpretation. Smaller forwarding helpers require it explicitly. + An implicit context was avoided because a target's interpretation must not leak + into its value arguments or selection prefixes. +- The resolver records direct definition references without demanding their value + shapes; overload sets still resolve through listeners. Failed term selection + marks the reference erroneous, avoiding repeated and secondary diagnostics. +- `Interpretations.mls` covers forward value uses, parenthesized and locally opened + class targets, constructor arguments, pattern guards, and type-only references + in signatures, aliases, type arguments, and ascriptions. Bare-pattern lookup and + the remaining lowering/lexical shortcuts are still separate follow-up work. +- Contextual-interpretation validation: `ctest` passed (45 tests), + `dtest newres/` passed (27 files), and the final `hkmc2AllTests/test` passed. + Existing runtime outputs are unchanged; reviewed golden updates record the + additional resolution requests made during elaboration. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 2aca5d2fd9..718fadd20d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -497,6 +497,13 @@ object Elaborator: given Conversion[Symbol, Elem] = RefElem(_) val empty: Ctx = Ctx(OuterCtx.LocalScope("top-level"), N, Map.empty, Mode.Full, Map.empty, Nil) + /** The interpretation required by the enclosing syntax. Class and pattern + * targets are resolved by their enclosing construct; types must not request + * a runtime value merely because they use the same reference syntax. + */ + enum Interpretation: + case Term, Class, Pattern, Type + enum Mode: case Full case Light @@ -1169,11 +1176,15 @@ extends Importer: case test => () => acc((test, Tree.BoolLit(true))) go(tree, ::(_, Nil))() - def term(tree: Tree): Ctxl[Term] = + // Deliberate default: most calls elaborate runtime expressions. Transparent + // wrappers must forward the interpretation; class, pattern, and type positions + // must specify it. An explicit parameter (rather than an implicit context) + // prevents the interpretation of a target from leaking into its value operands. + def term(tree: Tree, interpretation: Interpretation = Interpretation.Term): Ctxl[Term] = trace[Term](s"Elab term ${tree.showDbg}", r => s"~> ${r.showDbg}"): val unders = mutable.ArrayBuffer.empty[VarSymbol] given UnderCtx = new UnderCtx(S(unders)) - val st = subterm(tree) + val st = subterm(tree, interpretation) val params = unders.iterator.map: sym => Param(FldFlags.empty, sym, N, Modulefulness.none) .toList @@ -1222,7 +1233,14 @@ extends Importer: res - def subterm(tree: Tree): Ctxl[UnderCtx ?=> Term] = + /** Register the use when its reference is constructed, rather than revisiting + * the elaborated tree. Listeners can wait for forward definitions to complete. + */ + private def interpretRef(ref: Term, interpretation: Interpretation): Term = + if newResolution && (interpretation is Interpretation.Term) then requireTerm(ref) + ref + + def subterm(tree: Tree, interpretation: Interpretation = Interpretation.Term): Ctxl[UnderCtx ?=> Term] = trace[Term](s"Elab subterm ${tree.showDbg}", r => s"~> ${r.showDbg}"): def error = Term.Error().withLocOf(tree) @@ -1245,7 +1263,7 @@ extends Importer: val res = new Term.NewSel(preTrm, tree.name)(FlowSymbol.sel(tree.name.name)).withLocOf(tree) // listenTerm(preTrm, shape => selShape2(shape, tree.name, res)) newSel(res) - res + interpretRef(res, interpretation) else val sym = if newResolution then N else resolveField(tree.name, preTrm.symbol, tree.name) @@ -1262,7 +1280,7 @@ extends Importer: Term.Sel(preTrm, tree.name)(sym, FlowSymbol.sel(tree.name.name), N, S(summon)) tree.desugared match - case Trm(term) => term + case Trm(term) => interpretRef(term, interpretation) case unt @ Unt() => unit.withLocOf(unt) case Bra(k, e) => k match @@ -1270,18 +1288,18 @@ extends Importer: case Curly => case _ => raise(ErrorReport(msg"Unsupported ${k.name} in this position" -> tree.toLoc :: Nil)) - term(e) // * not `subterm` as `e` could be a lambda shorthand + term(e, interpretation) // * not `subterm` as `e` could be a lambda shorthand case b: Block => ctx.nestLocal("‹block›").givenIn: - block(b, hasResult = true)._1 match + block(b, hasResult = true, resultInterpretation = interpretation)._1 match case Term.Blk(Nil, res) => res case res => res case lit: Literal => Term.Lit(lit) case d: Def => - subterm(Block(d :: Unt() :: Nil)) + subterm(Block(d :: Unt() :: Nil), interpretation) case LetLike(kw @ Keywrd(`let`), lhs, rhso, S(bod)) => - subterm(Block(LetLike(kw, lhs, rhso, N) :: bod :: Nil)) + subterm(Block(LetLike(kw, lhs, rhso, N) :: bod :: Nil), interpretation) case LetLike(kw @ Keywrd(`let`), lhs, rhso, N) => raise(ErrorReport( msg"Expected a body for let bindings in expression position" -> @@ -1307,14 +1325,14 @@ extends Importer: Blk( LetDecl(sym, Nil) :: defineVar(sym, lt) :: Nil, Term.Try(Blk( assignment(lt, subterm(rhs)) :: Nil, - subterm(bod), + subterm(bod, interpretation), ), assignment(lt, sym.ref()))) case LetLike(Keywrd(Keyword.`set`), _, N, S(_)) => raise: ErrorReport(msg"Expected a right-hand side for this assignment" -> tree.toLoc :: Nil) error case TryFinally(tryBody, finallyBody) => - Term.Try(subterm(tryBody), subterm(finallyBody)) + Term.Try(subterm(tryBody, interpretation), subterm(finallyBody)) case (hd @ Hndl(id: Ident, c, Block(sts_), S(bod))) => ctx.nest(OuterCtx.LambdaOrHandlerBlock).givenIn: val sym = VarSymbol(id, erasedType = N) @@ -1352,12 +1370,12 @@ extends Importer: val (cp, p) = c match case App(c, Tup(params)) => - (subterm(c), params.map(subterm(_))) + (subterm(c, Interpretation.Class), params.map(subterm(_))) case c => - (subterm(c), Nil) + (subterm(c, Interpretation.Class), Nil) (ctx + (id.name -> sym)).givenIn: - Term.Handle(sym, cp, p, derivedClsSym, tds, subterm(bod)) + Term.Handle(sym, cp, p, derivedClsSym, tds, subterm(bod, interpretation)) case h: Hndl => raise(ErrorReport( msg"Unsupported handle binding shape" -> @@ -1370,16 +1388,16 @@ extends Importer: raise: ErrorReport(msg"Cannot use 'this' outside of an object scope" -> tree.toLoc :: Nil) error - case id @ Ident(name) => ident(id).getOrElse: + case id @ Ident(name) => ident(id).map(interpretRef(_, interpretation)).getOrElse: raise(ErrorReport(msg"Name not found: $name" -> id.toLoc :: Nil)) error case TyApp(lhs, targs) => - Term.TyApp(subterm(lhs), targs.map { - case Modified(Keywrd(Keyword.`in`), arg) => Term.WildcardTy(S(subterm(arg)), N) - case Modified(Keywrd(Keyword.`out`), arg) => Term.WildcardTy(N, S(subterm(arg))) + Term.TyApp(subterm(lhs, interpretation), targs.map { + case Modified(Keywrd(Keyword.`in`), arg) => Term.WildcardTy(S(subterm(arg, Interpretation.Type)), N) + case Modified(Keywrd(Keyword.`out`), arg) => Term.WildcardTy(N, S(subterm(arg, Interpretation.Type))) case Tup(Modified(Keywrd(Keyword.`in`), arg1) :: Modified(Keywrd(Keyword.`out`), arg2) :: Nil) => - Term.WildcardTy(S(subterm(arg1)), S(subterm(arg2))) - case arg => subterm(arg) + Term.WildcardTy(S(subterm(arg1, Interpretation.Type)), S(subterm(arg2, Interpretation.Type))) + case arg => subterm(arg, Interpretation.Type) })(N).withLocOf(tree) case InfixApp(TyTup(tvs), Keywrd(Keyword.`->`), body) => val boundVars = mutable.HashMap.empty[Str, VarSymbol] @@ -1412,12 +1430,12 @@ extends Importer: given Ctx = ctx ++ boundVars val bds = syms.map: case (sym, ub, lb) => - QuantVar(sym, ub.map(ub => subterm(ub)), lb.map(lb => subterm(lb))) - Term.Forall(bds, outer, subterm(body)) + QuantVar(sym, ub.map(ub => subterm(ub, Interpretation.Type)), lb.map(lb => subterm(lb, Interpretation.Type))) + Term.Forall(bds, outer, subterm(body, Interpretation.Type)) case InfixApp(lhs, Keywrd(Keyword.`->`), Effectful(eff, rhs)) => - Term.FunTy(subterm(lhs), subterm(rhs), S(subterm(eff))) + Term.FunTy(subterm(lhs, Interpretation.Type), subterm(rhs, Interpretation.Type), S(subterm(eff, Interpretation.Type))) case InfixApp(lhs, Keywrd(Keyword.`->`), rhs) => - Term.FunTy(subterm(lhs), subterm(rhs), N) + Term.FunTy(subterm(lhs, Interpretation.Type), subterm(rhs, Interpretation.Type), N) case InfixApp(lhs, Keywrd(Keyword.`=>`), rhs) => lhs match case Tup(_) => @@ -1426,20 +1444,20 @@ extends Importer: Term.Lam(syms, term(rhs)(using nestCtx)) case TyTup(tys) => val constraints = tys.flatMap(maybeConstraint) - val body = term(rhs) + val body = term(rhs, interpretation) Term.Constrained(constraints, body) case _ => lastWords(s"Unexpected lambda parameter shape: $lhs") case InfixApp(lhs, Keywrd(Keyword.`as`), rhs) => - Term.Asc(subterm(lhs), subterm(rhs)) + Term.Asc(subterm(lhs, interpretation), subterm(rhs, Interpretation.Type)) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => - block(tree :: Nil, hasResult = false)._1 + block(Block(tree :: Nil), hasResult = false, resultInterpretation = interpretation)._1 case PrefixApp(kw @ Keywrd(Keyword.`not`), rhs) => app(State.builtinOpsMap("!").ref(new Ident("not").withLocOf(kw)), Term.Tup( PlainFld(subterm(rhs)) :: Nil)(DummyTup))(DummyApp, N, FlowSymbol("not-app")) case tree @ InfixApp(lhs, Keywrd(Keyword.`is` | Keyword.`and` | Keyword.`or`), rhs) => ifLike(Keyword.`if`, IfLikeForm.ReturningIf, shorthandSplit(tree), tree.toLoc) case InfixApp(Sel(pre, idn: Ident), Keywrd(Keyword.`#`), idp: Ident) => - val c = subterm(idn) + val c = subterm(idn, Interpretation.Class) val f = c.symbol.flatMap(_.asCls) match case S(cls: ClassSymbol) => cls.tree.allSymbols.get(idp.name) match @@ -1452,9 +1470,9 @@ extends Importer: N Term.SelProj(subterm(pre), c, idp)(f, FlowSymbol.selProj(idp.name), N, S(summon)) case InfixApp(lhs, op @ Keywrd(Keyword.`|`), rhs) => - Term.CompType(subterm(lhs), subterm(rhs), true)//.withLocOf(tree) + Term.CompType(subterm(lhs, Interpretation.Type), subterm(rhs, Interpretation.Type), true)//.withLocOf(tree) case InfixApp(lhs, op @ Keywrd(Keyword.`&`), rhs) => - Term.CompType(subterm(lhs), subterm(rhs), false)//.withLocOf(tree) + Term.CompType(subterm(lhs, Interpretation.Type), subterm(rhs, Interpretation.Type), false)//.withLocOf(tree) case InfixApp(lhs, kw, rhs) => raise: ErrorReport(msg"Unexpected infix use of keyword '${kw.name}' here" -> tree.toLoc :: Nil) @@ -1464,14 +1482,14 @@ extends Importer: case App(Ident("!"), Tup(rhs :: Nil)) => Term.Deref(subterm(rhs)) case App(Ident("~"), Tup(rhs :: Nil)) => - Term.Neg(subterm(rhs)) + Term.Neg(subterm(rhs, Interpretation.Type)) case PrefixApp(Keywrd(Keyword.`|` | Keyword.`&`), rhs) => - subterm(rhs) + subterm(rhs, interpretation) case tree @ OpSplit(lhs, rhss) => val tree = rhss.foldLeft(lhs): case (acc, rhs) => rhs.splitOn(acc) - subterm(tree) + subterm(tree, interpretation) case tree @ App(sel @ Sel(labelId @ Ident(labelName), nme @ Ident("break")), Tup(args)) => val value = args match case Nil => N @@ -1564,7 +1582,7 @@ extends Importer: case sel @ Sel(pre, nme) => elaborateSelection(sel) case MemberProj(ct, nme) => - val c = subterm(ct) + val c = subterm(ct, Interpretation.Class) val f = c.symbol.flatMap(_.asCls) match case S(cls: ClassSymbol) => cls.tree.allSymbols.get(nme.name) match @@ -1594,11 +1612,11 @@ extends Importer: , N, rs) ) case tree @ Tup(TermDef(Ins, f, N) :: fs) => - Term.CtxTup((f :: fs).map(fld(_)))(tree) + Term.CtxTup((f :: fs).map(fld(_, interpretation)))(tree) case Modified(kw @ Keywrd(Keyword.`mut`), tree @ Tup(fields)) => - Term.Mut(Term.Tup(fields.map(fld(_)))(tree)).mkLocWith(kw) + Term.Mut(Term.Tup(fields.map(fld(_, interpretation)))(tree)).mkLocWith(kw) case tree @ Tup(fields) => - Term.Tup(fields.map(fld(_)))(tree) + Term.Tup(fields.map(fld(_, interpretation)))(tree) case DynamicNew(Apps(c, args)) => val (mut, c2) = c match @@ -1616,14 +1634,14 @@ extends Importer: clsSym -> // TODO integrate context inherited from cls // TODO make context with var symbols for class parameters - ObjBody(block(rft, hasResult = false)._1) + ObjBody(block(rft, hasResult = false, resultInterpretation = Interpretation.Term)._1) body match case S(Apps(c, args)) => val (mut, c2) = c match case Modified(Keywrd(Keyword.`mut`), c) => (true, c) case c => (false, c) val inner = mkNew( - subterm(c2), // * Note: we'll catch bad `new` targets during type checking + subterm(c2, Interpretation.Class), // * Note: we'll catch bad `new` targets during type checking args.map(subterm(_)), bodo )(N) @@ -1714,7 +1732,7 @@ extends Importer: raise(ErrorReport(msg"Illegal outer binding." -> tree.toLoc :: Nil)) error case Outer(N) => ctx.get("outer") match - case S(sym) => sym.ref(Ident("outer")) + case S(sym) => interpretRef(sym.ref(Ident("outer")), interpretation) case N => raise(ErrorReport(msg"Illegal outer reference." -> tree.toLoc :: Nil)) error @@ -1730,7 +1748,7 @@ extends Importer: raise(ErrorReport(msg"Illegal type declaration in term position." -> tree.toLoc :: Nil)) error case Modified(Keywrd(Keyword.`mut`), body: Block) => - blockOrRcd(body, hasResult = true) match + blockOrRcd(body, hasResult = true, resultInterpretation = interpretation) match case (Blk(Nil, Term.UnitVal()), ctx) => Rcd(mut = true, Nil).withLocOf(body) case (blk: Blk, ctx) => @@ -1755,12 +1773,12 @@ extends Importer: val sym = FlowSymbol.app() val fl = Fld(FldFlags.empty, res, N) val ap_ = app(subterm(f), Term.Tup( - fl :: args.map(fld))(tup))(ap, N, sym) + fl :: args.map(fld(_, Interpretation.Term)))(tup))(ap, N, sym) go(ap_, trees) case (ap @ App(f, tup @ Tup(args))) :: trees => val sym = FlowSymbol.app() go(app(subterm(f), - Term.Tup(Fld(FldFlags.empty, acc, N) :: args.map(fld))(tup) + Term.Tup(Fld(FldFlags.empty, acc, N) :: args.map(fld(_, Interpretation.Term)))(tup) )(ap, N, sym), trees) case Block(sts) :: trees => go(acc, sts ::: trees) @@ -1773,7 +1791,7 @@ extends Importer: raise(ErrorReport(msg"Illegal position for 'open' statement." -> tree.toLoc :: Nil)) error case OpenIn(op, body) => - subterm(Block(Open(op) :: body :: Nil)) + subterm(Block(Open(op) :: body :: Nil), interpretation) case DynAccess(obj, rhs) => rhs match case Bra(bk @ (Round | Square), fld) => Term.DynSel(subterm(obj), subterm(fld), bk is Square) @@ -1796,8 +1814,8 @@ extends Importer: unds += sym sym.ref() case Annotated(lhs, rhs) => - annot(lhs).fold(subterm(rhs))(ann => - Term.Annotated(ann, subterm(rhs))) + annot(lhs).fold(subterm(rhs, interpretation))(ann => + Term.Annotated(ann, subterm(rhs, interpretation))) case Keywrd(kw) => raise(ErrorReport(msg"Unexpected keyword '${kw.name}' in this position." -> tree.toLoc :: Nil)) error @@ -1813,20 +1831,20 @@ extends Importer: raise(ErrorReport(msg"Unsupported term in this position (${tree.describe})." -> tree.toLoc :: Nil)) error - def arg(tree: Tree)(using UnderCtx): Ctxl[Term] = tree match - case u: Under => subterm(tree) // Note: currently `f(a, _, c)` is treated the same as `f of a, _, c` - case _ => term(tree) - def fld(tree: Tree)(using UnderCtx): Ctxl[Elem] = tree match + def arg(tree: Tree, interpretation: Interpretation)(using UnderCtx): Ctxl[Term] = tree match + case u: Under => subterm(tree, interpretation) // Note: currently `f(a, _, c)` is treated the same as `f of a, _, c` + case _ => term(tree, interpretation) + def fld(tree: Tree, interpretation: Interpretation)(using UnderCtx): Ctxl[Elem] = tree match case InfixApp(id: Ident, Keywrd(Keyword.`:`), rhs) => - Fld(FldFlags.empty, Term.Lit(StrLit(id.name).withLocOf(id)), S(arg(rhs))) + Fld(FldFlags.empty, Term.Lit(StrLit(id.name).withLocOf(id)), S(arg(rhs, Interpretation.Type))) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => - Fld(FldFlags.empty, term(lhs), S(arg(rhs))) + Fld(FldFlags.empty, term(lhs, interpretation), S(arg(rhs, Interpretation.Type))) case Spread(Keywrd(Keyword.`..`), S(trm)) => - Spd(SpreadKind.Lazy, arg(trm)) + Spd(SpreadKind.Lazy, arg(trm, interpretation)) case Spread(Keywrd(Keyword.`...`), S(trm)) => - Spd(SpreadKind.Eager, arg(trm)) + Spd(SpreadKind.Eager, arg(trm, interpretation)) case _ => - val t = arg(tree) + val t = arg(tree, interpretation) var flags = FldFlags.empty Fld(flags, t, N) @@ -1835,10 +1853,10 @@ extends Importer: def block(sts: Ls[Tree], hasResult: Bool)(using UnderCtx): Ctxl[(Blk, Ctx)] = - block(new Block(sts), hasResult) + block(new Block(sts), hasResult, Interpretation.Term) - def block(blk: Block, hasResult: Bool)(using UnderCtx): Ctxl[(Blk, Ctx)] = - blockOrRcd(blk, hasResult) match + def block(blk: Block, hasResult: Bool, resultInterpretation: Interpretation)(using UnderCtx): Ctxl[(Blk, Ctx)] = + blockOrRcd(blk, hasResult, resultInterpretation) match case (blk: Blk, ctx) => (blk, ctx) case (rcd: Rcd, ctx) => (Blk(Nil, rcd), ctx) @@ -1860,13 +1878,16 @@ extends Importer: // * for these, elaborate with `hasResult = false`, which uses `undefined` as the result // * when there is no other result available. This is fine since the value is never used. // * These useless trailing `undefined`s are then removed by `Lowering`. - def blockOrRcd(blk: Block, hasResult: Bool)(using UnderCtx) + def blockOrRcd(blk: Block, hasResult: Bool, resultInterpretation: Interpretation)(using UnderCtx) : Ctxl[(Blk | Rcd, Ctx)] = trace[(Blk | Rcd, Ctx)]( pre = s"Elab block ${blk.desugStmts.toString.truncate(100, "[...]")} ${ctx.outer}", r => s"~> ${r._1.showDbg}" ): val members = blk.definedSymbols.toMap + val fieldInterpretation = resultInterpretation match + case Interpretation.Type => Interpretation.Type + case _ => Interpretation.Term val newSignatureTrees = mutable.Map.empty[Str, Tree] // * Store trees of signatures // * Check for double/incompatible definitions and declarations @@ -2039,17 +2060,17 @@ extends Importer: case Spread(Keywrd(Keyword.`...`), S(body)) :: sts => reportUnusedAnnotations - go(sts, Nil, RcdSpread(term(body)) :: acc) + go(sts, Nil, RcdSpread(term(body, fieldInterpretation)) :: acc) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) :: sts => var newCtx = ctx val (rlhs, rhs_t) = rhs match - case _: Under => (lhs, subterm(rhs)) + case _: Under => (lhs, subterm(rhs, fieldInterpretation)) case _ => lhs match case Apps(base, tups) => val rrhs = tups.foldRight(rhs): InfixApp(_, Keywrd(Keyword.`=>`), _) - (base, term(rrhs)) + (base, term(rrhs, fieldInterpretation)) val newAcc = rlhs match case id: Ident => val sym = new VarSymbol(id, erasedType = N) @@ -2164,8 +2185,8 @@ extends Importer: // unwrap possible module modifier // e.g, `fun f: module M` // ^^^^^^ - case TypeDef(Mod, st, N) => term(st)(using newCtx) - case st => term(st)(using newCtx) + case TypeDef(Mod, st, N) => term(st, Interpretation.Type)(using newCtx) + case st => term(st, Interpretation.Type)(using newCtx) val body: Opt[Term] = rhs match case N => N @@ -2450,7 +2471,7 @@ extends Importer: def mkBody(extraParams: Ls[ParamList])(using Ctx) = withFields(extraParams): body match case N | S(Error()) => (new Blk(Nil, Term.Lit(UnitLit(false))), ctx) - case S(b: Block) => block(b, hasResult = false) + case S(b: Block) => block(b, hasResult = false, resultInterpretation = Interpretation.Term) case S(t) => raise(ErrorReport( msg"Illegal body of ${k.desc} definition (should be a block; found ${t.describe})." -> t.toLoc :: Nil)) @@ -2465,7 +2486,7 @@ extends Importer: assert(body.isEmpty) val d = given Ctx = newCtx - semantics.TypeDef(alsSym, sym, tps, rhs.map(term(_)), N, annotations) + semantics.TypeDef(alsSym, sym, tps, rhs.map(term(_, Interpretation.Type)), N, annotations) alsSym.defn = S(d) d case Pat => @@ -2620,7 +2641,8 @@ extends Importer: go(sts, annotations, acc) case (st: Tree) :: sts => // TODO reject plain term statements? Currently, `(1, 2)` is allowed to elaborate (tho it should be rejected in type checking later) - val res = annotations.foldLeft(term(st)): + val interpretation = if sts.isEmpty then resultInterpretation else Interpretation.Term + val res = annotations.foldLeft(term(st, interpretation)): case (acc, ann) => Term.Annotated(ann, acc) sts match case Nil => (mkBlk(acc, S(res), hasResult), ctx) @@ -2692,7 +2714,7 @@ extends Importer: new Ident(base).withLocOf(id) -> (id.name :: Nil) case N => id -> Nil - val sig = sign.map(term(_)) + val sig = sign.map(term(_, Interpretation.Type)) val mfn = Modulefulness.ofSign(sig)(Mod in modifiers) // * As for return signatures, a moduleful parameter (`module m: M`) denotes the module itself, which // * `eraseSign` would miss by resolving the name through `asTpe`. @@ -2717,8 +2739,8 @@ extends Importer: /** Elaborate a subtyping constraint. */ def constraint(lhs: Tree, op: "<:<" | ">:>", rhs: Tree): Ctxl[SubConstraint] = - val l = term(lhs) - val r = term(rhs) + val l = term(lhs, Interpretation.Type) + val r = term(rhs, Interpretation.Type) val dir = op match case "<:<" => SubDir.Sub case ">:>" => SubDir.Sup @@ -2904,7 +2926,7 @@ extends Importer: Composition(op is Keyword.`|`, go(lhs), go(rhs)) // Constructor patterns with pattern arguments and arguments. case App(ctor: Ctor, Tup(argTrees)) => // TODO: rm this weird `: Ctor` guard - val lt = term(ctor) + val lt = term(ctor, Interpretation.Pattern) val rt = argTrees.map(go(_)) val res = new Constructor(lt, S(rt)) // if newResolution then listenTerm(lt, shape => patAppShape(shape, rt, res)) @@ -2956,7 +2978,7 @@ extends Importer: // pattern translation. case OpApp(lhs, Ident("~"), rhs :: Nil) => Pattern.Concatenation(go(lhs), go(rhs)) // Constructor patterns can be written in the infix form. - case OpApp(lhs, op, rhs :: Nil) => Pattern.Constructor(term(op), S(Ls(go(lhs), go(rhs)))) + case OpApp(lhs, op, rhs :: Nil) => Pattern.Constructor(term(op, Interpretation.Pattern), S(Ls(go(lhs), go(rhs)))) // Constructor patterns without arguments case id @ Ident(name) if name.isUncapitalized => Variable(id) case id @ Ident(name) => ident(id) match @@ -2965,7 +2987,7 @@ extends Importer: raise: ErrorReport(msg"Pattern name not found: ${id.name}." -> id.toLoc :: Nil) Pattern.Wildcard() - case sel: (SynthSel | Sel) => Constructor(term(sel), N) + case sel: (SynthSel | Sel) => Constructor(term(sel, Interpretation.Pattern), N) case _: Tree => raise(ErrorReport(msg"Unrecognized pattern (${t.describe})." -> t.toLoc :: Nil)) Pattern.Wildcard() @@ -2990,15 +3012,13 @@ extends Importer: def importFrom(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) - val (res, newCtx) = block(sts, hasResult = false) - if newResolution then resolveOpenUses(res) + val (res, newCtx) = block(sts, hasResult = false, resultInterpretation = Interpretation.Term) // TODO handle name clashes (res, newCtx) def topLevel(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) - val (res, ctx) = block(sts, hasResult = false) - if newResolution then resolveOpenUses(res) + val (res, ctx) = block(sts, hasResult = false, resultInterpretation = Interpretation.Term) computeVariances(res) (res, ctx) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 1b08be53fd..21fbc93edd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -303,62 +303,6 @@ class NewResolver: if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate publishMember(ref, member, ref.resSym, marks) - /** Bare wildcard references need a term interpretation even when nobody asks - * for their value shape. Walk runtime uses after elaboration so constructor - * targets retain their symbolic interpretation. No member lookup happens here. - */ - def resolveOpenUses(root: Statement): Unit = - def symbolic(term: Term): Unit = term.withoutCaptures match - case UnresolvedRef(prefixes, _) => prefixes.foreach(value) - case NewSel(prefix, _) => value(prefix) - case _: MemberRef => () - case other => other.subTerms.foreach(value) - def pattern(pat: Pattern): Unit = pat match - case Pattern.Constructor(target, arguments) => - symbolic(target) - arguments.foreach(_.foreach(pattern)) - // These children flatten their subpatterns, so visit the patterns explicitly. - case Pattern.Record(fields) => fields.foreach((_, pat) => pattern(pat)) - case Pattern.Guarded(pat, guard) => pattern(pat); value(guard) - case other => other.children.foreach: - case pat: Pattern => pattern(pat) - case term: Term => value(term) - case _ => () - def split(s: SimpleSplit): Unit = s match - case SimpleSplit.Cons(head, tail) => - head match - case SimpleSplit.Head.Match(scrutinee, pat, consequent) => - value(scrutinee) - pattern(pat) - split(consequent) - case SimpleSplit.Head.Let(_, rhs) => value(rhs) - split(tail) - case SimpleSplit.Else(term) => value(term) - case SimpleSplit.End => () - def value(stmt: Statement): Unit = stmt match - case ref: UnresolvedRef => - listenTerm(ref)(_ => ()) - ref.prefixes.foreach(value) - case New(cls, args, refinement) => - symbolic(cls) - args.foreach(value) - refinement.foreach((_, body) => value(body.blk)) - case IfLike(_, _, branches) => split(branches) - case Asc(term, _) => value(term) - case TyApp(term, _) => value(term) - case Lam(_, body) => value(body) - case td: TermDefinition => - td.body.foreach(value) - td.annotations.flatMap(_.subTerms).foreach(value) - case pd: PatternDef => pattern(pd.pattern) - case cd: ClassLikeDef => - value(cd.body.blk) - cd.ext.foreach(value) - cd.annotations.flatMap(_.subTerms).foreach(value) - case _: TypeDef => () - case other => other.subStatements.foreach(value) - value(root) - def newSel(sel: NewSel): Unit = log(s"newSel? sel = ${sel.showDbg}") listenTerm(sel.prefix): shape => @@ -526,11 +470,26 @@ class NewResolver: // sym.defnListeners += (d => listener(defnShapes.getOrElseUpdate(sym, DefnShape(d)))) softAssert(false, s"Symbol definition of ${sym} is not set upon completion of ${bms}") case _ => - resolError(trm, - msg"Expected a term; got ${bms.describe} '${bms.nme}'" -> N :: Nil, - // msg"expected a term shape, but got ${bms.describe} (${bms.toString})" -> trm.toLoc :: Nil, - ) + val ref = trm.withoutCaptures match + case ref: NewResolvable => S(ref) + case _ => N + if !ref.exists(_.isErroneous) then + ref.foreach(_.isErroneous = true) + resolError(trm, + msg"Expected a term; got ${bms.describe} '${bms.nme}'" -> N :: Nil) + /** Request the term interpretation of a reference without requiring a consumer + * of its value shape. Direct definition references already identify the target; + * overload sets need listeners, including when their definitions arrive later. + */ + def requireTerm(trm: Term): Unit = trm.withoutCaptures match + case direct @ MemberRef(sym: TermSymbol) => + // Selecting a known field does not require resolving the field's value. + softAssert(direct.resolvedTargets.forall(_ is sym)) + direct.resolvedTargets = sym :: Nil + case _: NewResolvable => listenTerm(trm)(_ => ()) + case _ => () + def listenTerm(trm: Term)(listener: TermShape => Unit): Unit = log(s"listenTerm: trm = ${trm.showDbg}") listen(trm): diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls index 5c4cdb9678..cd41f6743e 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -29,6 +29,12 @@ Predef.id //│ | Elab term Sel(Ident(Predef),Ident(id)) //│ | | Elab subterm Sel(Ident(Predef),Ident(id)) //│ | | | Elab subterm Ident(Predef) +//│ | | | | listenTerm: trm = member:Predef⁰ +//│ | | | | listen: trm = member:Predef⁰ +//│ | | | | listenBMS: bms = module +//│ | | | | listenedBMS: bms = module +//│ | | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() +//│ | | | | PUSH module:Predef¹ //│ | | | ~> member:Predef⁰ //│ | | | newSel? sel = member:Predef⁰.id //│ | | | listenTerm: trm = member:Predef⁰ @@ -39,6 +45,15 @@ Predef.id //│ | | | PUSH module:Predef¹ //│ | | | newSel: sel = member:Predef⁰.id, shape = module:Predef¹ //│ | | | newSel member: bms = member:id¹, mss = List() +//│ | | | listenTerm: trm = member:Predef⁰.id +//│ | | | listen: trm = member:Predef⁰.id +//│ | | | listenBMS: bms = function +//│ | | | listenedBMS: bms = function +//│ | | | fromBMS: bms = member:id¹, sh = member:id¹, flow = ⋅id⁰, markss = List() +//│ | | | .exit MarkedShape (member:id¹, term:Predef¹/id⁰, ⋅id⁰) +//│ | | | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) +//│ | | | = MarkedShape(member:id¹, ↗⟨term:Predef¹/id⁰⟩%⟨⋅id⁰⟩ϵ) +//│ | | | PUSH MarkedShape(member:id¹, ↗⟨term:Predef¹/id⁰⟩%⟨⋅id⁰⟩ϵ) //│ | | ~> member:Predef⁰.id //│ | ~> member:Predef⁰.id //│ ~> { member:Predef⁰.id } @@ -66,7 +81,7 @@ Predef.id(1) :e Predef.idd(1) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.67: Predef.idd(1) +//│ ║ l.82: Predef.idd(1) //│ ║ ^^^^^^^^^^ //│ ╟── Module 'Predef' does not contain member 'idd' //│ ║ in ../../mlscript-compile/Predef.mls @@ -119,30 +134,30 @@ p.id :e (1).id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.120: (1).id +//│ ║ l.135: (1).id //│ ║ ^^^^^^ //│ ╟── Integer literal does not contain member 'id' -//│ ║ l.120: (1).id +//│ ║ l.135: (1).id //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme // parse 1.id //│ ╔══[LEXICAL ERROR] Expected at least one digit after the decimal point -//│ ║ l.130: 1.id +//│ ║ l.145: 1.id //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). -//│ ║ l.130: 1.id +//│ ║ l.145: 1.id //│ ╙── ^^ //│ = 1 :fixme "ha ".repeat(2) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.140: "ha ".repeat(2) +//│ ║ l.155: "ha ".repeat(2) //│ ║ ^^^^^^^^^^^^ //│ ╟── String literal does not contain member 'repeat' -//│ ║ l.140: "ha ".repeat(2) +//│ ║ l.155: "ha ".repeat(2) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -155,9 +170,9 @@ print(1) :e ().id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.156: ().id +//│ ║ l.171: ().id //│ ║ ^^^^^ //│ ╟── Unit value does not contain member 'id' -//│ ║ l.156: ().id +//│ ║ l.171: ().id //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 5e208a2d48..bb0a8e372c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -54,6 +54,8 @@ callid1(Predef) //│ | | | | newSel? sel = p⁰.id //│ | | | | listenTerm: trm = p⁰ //│ | | | | listen: trm = p⁰ +//│ | | | | listenTerm: trm = p⁰.id +//│ | | | | listen: trm = p⁰.id //│ | | | ~> p⁰.id //│ | | | Elab subterm Tup(List(IntLit(1))) //│ | | | | Elab term IntLit(1) @@ -68,10 +70,19 @@ callid1(Predef) //│ | Elab term App(Ident(callid1),Tup(List(Ident(Predef)))) //│ | | Elab subterm App(Ident(callid1),Tup(List(Ident(Predef)))) //│ | | | Elab subterm Ident(callid1) +//│ | | | | listenTerm: trm = member:callid1⁰ +//│ | | | | listen: trm = member:callid1⁰ +//│ | | | | listenBMS: bms = function //│ | | | ~> member:callid1⁰ //│ | | | Elab subterm Tup(List(Ident(Predef))) //│ | | | | Elab term Ident(Predef) //│ | | | | | Elab subterm Ident(Predef) +//│ | | | | | | listenTerm: trm = member:Predef⁰ +//│ | | | | | | listen: trm = member:Predef⁰ +//│ | | | | | | listenBMS: bms = module +//│ | | | | | | listenedBMS: bms = module +//│ | | | | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() +//│ | | | | | | PUSH module:Predef¹ //│ | | | | | ~> member:Predef⁰ //│ | | | | ~> member:Predef⁰ //│ | | | ~> [member:Predef⁰] @@ -86,6 +97,12 @@ callid1(Predef) //│ | | class hkmc2.semantics.TermSymbol None //│ | = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | PUSH MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | listenedBMS: bms = function +//│ | fromBMS: bms = member:callid1⁰, sh = member:callid1⁰, flow = callid1¹, markss = List() +//│ | .exit MarkedShape (member:callid1⁰, term:callid1², callid1¹) +//│ | | class hkmc2.semantics.TermSymbol None +//│ | = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | PUSH MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | appShape: lhs = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ), args = [member:Predef⁰], res = member:callid1⁰(member:Predef⁰) //│ | Zipping ParamList(‹›,List(Param(‹›,p,None,Modulefulness(None))),None) (List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ)) with List(Fld(‹›,MemberRef(member:Predef),None)) //│ | listenTerm: trm = member:Predef⁰ @@ -112,6 +129,17 @@ callid1(Predef) //│ | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | listenBMS: bms = function +//│ | listenedBMS: bms = function +//│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) +//│ | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) +//│ | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) +//│ | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | appShape: lhs = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), args = [1], res = p⁰.id(1) //│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) //│ | listenTerm: trm = 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/Interpretations.mls b/hkmc2/shared/src/test/mlscript/newres/Interpretations.mls new file mode 100644 index 0000000000..c1c0c44b26 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Interpretations.mls @@ -0,0 +1,99 @@ +:. + + +// Ordinary references must resolve even when no caller asks for their shapes. +fun forwardValue() = { + open Later + value +} +module Later { val value = 23 } + +:expect 23 +forwardValue() +//│ = 23 + + +// Parentheses and locally opened blocks preserve a class target's interpretation. +module Choices { + class Item { fun answer() = 24 } + val Item = 25 +} +fun choices() = { + open Choices + [Item, (new (Item)).answer(), (new (Choices.{Item})).answer()] +} + +:expect [25, 24, 24] +choices() +//│ = [25, 24, 24] + + +// Constructor arguments are terms even though the constructor target is a class. +module Arguments { + class Box(val value) + class Item + val Item = 26 +} +fun construct() = { + open Arguments + (new Box(Item)).value +} + +:expect 26 +construct() +//│ = 26 + + +// Pattern targets retain the class interpretation; guards and results are terms. +fun matchBox(box) = { + open Arguments + if box is Box(value) and value > 0 then Item else 0 +} + +:expect [26, 0] +[matchBox(Arguments.Box(1)), matchBox(Arguments.Box(0))] +//│ = [26, 0] + + +// Type-only members must not be interpreted as runtime values in signatures, +// aliases, type arguments, or ascriptions. +module Types { type Alias = Int } +fun annotated() = { + open Types + type Local = (Alias) + type Pair = [Alias, Alias] + type Fn = Alias -> Alias + type Record = { field: Alias } + type Excluded = ~Alias + type Combined = Alias | (Alias & Alias) + fun echo[A](x: Alias): Alias = x + (echo[Alias](27) as Alias) +} + +:expect 27 +annotated() +//│ = 27 + + +// The same type-only name in an expression must still be rejected. +:e +fun invalidValue() = { + open Types + Alias +} +invalidValue() +//│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference +//│ ║ l.82: Alias +//│ ║ ^^^^^ +//│ ╙── Expected a term; got type alias 'Alias' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +// The prefix of a class selection is a term, even if that name also has a class. +class Carrier +val Carrier = Arguments +//│ Carrier = class Arguments { Box: fun Box { class: class Box }, Item: 26 } + +:expect 26 +(new Carrier.Box(Carrier.Item)).value +//│ = 26 diff --git a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls index 5ca1e60f06..ce8d5145a3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls +++ b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls @@ -78,6 +78,14 @@ module M with //│ | | | Processing term definition Ident(p) //│ | | | Elab term Ident(Predef) //│ | | | | Elab subterm Ident(Predef) +//│ | | | | | listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ +//│ | | | | | listen: trm = member:Predef⁰^term:M⁰/p⁰ +//│ | | | | | listenTerm: trm = member:Predef⁰ +//│ | | | | | listen: trm = member:Predef⁰ +//│ | | | | | listenBMS: bms = module +//│ | | | | | listenedBMS: bms = module +//│ | | | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() +//│ | | | | | PUSH module:Predef¹ //│ | | | | ~> member:Predef⁰^term:M⁰/p⁰ //│ | | | ~> member:Predef⁰^term:M⁰/p⁰ //│ | | ~> { val member:p = member:Predef⁰^term:M⁰/p⁰; } @@ -94,6 +102,12 @@ M.p.id //│ | | Elab subterm Sel(Sel(Ident(M),Ident(p)),Ident(id)) //│ | | | Elab subterm Sel(Ident(M),Ident(p)) //│ | | | | Elab subterm Ident(M) +//│ | | | | | listenTerm: trm = member:M¹ +//│ | | | | | listen: trm = member:M¹ +//│ | | | | | listenBMS: bms = module +//│ | | | | | listenedBMS: bms = module +//│ | | | | | fromBMS: bms = member:M¹, sh = module:M⁰, flow = M², markss = List() +//│ | | | | | PUSH module:M⁰ //│ | | | | ~> member:M¹ //│ | | | | newSel? sel = member:M¹.p //│ | | | | listenTerm: trm = member:M¹ @@ -104,6 +118,24 @@ M.p.id //│ | | | | PUSH module:M⁰ //│ | | | | newSel: sel = member:M¹.p, shape = module:M⁰ //│ | | | | newSel member: bms = member:p¹, mss = List() +//│ | | | | listenTerm: trm = member:M¹.p +//│ | | | | listen: trm = member:M¹.p +//│ | | | | listenBMS: bms = value +//│ | | | | listenedBMS: bms = value +//│ | | | | listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ +//│ | | | | listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ +//│ | | | | listen: trm = member:Predef⁰^term:M⁰/p⁰ +//│ | | | | listenTerm: trm = member:Predef⁰ +//│ | | | | listen: trm = member:Predef⁰ +//│ | | | | listenBMS: bms = module +//│ | | | | listenedBMS: bms = module +//│ | | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() +//│ | | | | PUSH module:Predef¹ +//│ | | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = ⋅p⁰, markss = List() +//│ | | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, ⋅p⁰) +//│ | | | | | class hkmc2.semantics.TermSymbol Some(module:M⁰) +//│ | | | | = module:Predef¹ +//│ | | | | MATCH module:Predef¹ for term:M⁰/p⁰ % ⋅p⁰ //│ | | | ~> member:M¹.p //│ | | | newSel? sel = member:M¹.p.id //│ | | | listenTerm: trm = member:M¹.p @@ -126,6 +158,15 @@ M.p.id //│ | | | MATCH module:Predef¹ for term:M⁰/p⁰ % ⋅p⁰ //│ | | | newSel: sel = member:M¹.p.id, shape = module:Predef¹ //│ | | | newSel member: bms = member:id⁰, mss = List() +//│ | | | listenTerm: trm = member:M¹.p.id +//│ | | | listen: trm = member:M¹.p.id +//│ | | | listenBMS: bms = function +//│ | | | listenedBMS: bms = function +//│ | | | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List() +//│ | | | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) +//│ | | | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) +//│ | | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | PUSH MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | ~> member:M¹.p.id //│ | ~> member:M¹.p.id //│ ~> { member:M¹.p.id } From c393ca92ec67120594010defd996225a03d7fdd6 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 13:55:00 +0800 Subject: [PATCH 015/228] Simplify resolution error reporting and clarify use comment --- .../main/scala/hkmc2/semantics/Elaborator.scala | 4 ++-- .../main/scala/hkmc2/semantics/NewResolver.scala | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 718fadd20d..9fcf56cb84 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1233,8 +1233,8 @@ extends Importer: res - /** Register the use when its reference is constructed, rather than revisiting - * the elaborated tree. Listeners can wait for forward definitions to complete. + /** Request the reference's term interpretation in expression positions. + * Listeners can wait for forward definitions to complete. */ private def interpretRef(ref: Term, interpretation: Interpretation): Term = if newResolution && (interpretation is Interpretation.Term) then requireTerm(ref) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 21fbc93edd..01432e1b35 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -470,13 +470,14 @@ class NewResolver: // sym.defnListeners += (d => listener(defnShapes.getOrElseUpdate(sym, DefnShape(d)))) softAssert(false, s"Symbol definition of ${sym} is not set upon completion of ${bms}") case _ => - val ref = trm.withoutCaptures match - case ref: NewResolvable => S(ref) - case _ => N - if !ref.exists(_.isErroneous) then - ref.foreach(_.isErroneous = true) - resolError(trm, - msg"Expected a term; got ${bms.describe} '${bms.nme}'" -> N :: Nil) + def reportError = resolError(trm, + msg"Expected a term; got ${bms.describe} '${bms.nme}'" -> N :: Nil) + trm.withoutCaptures match + case ref: NewResolvable => + if !ref.isErroneous then + ref.isErroneous = true + reportError + case _ => reportError /** Request the term interpretation of a reference without requiring a consumer * of its value shape. Direct definition references already identify the target; From 18d73e6ef861cfcda3cde9809ec5475a45c17d56 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 14:00:49 +0800 Subject: [PATCH 016/228] Shorten interpretation names and require explicit term context --- doc/new-resolution-plan.md | 9 +- .../scala/hkmc2/semantics/Elaborator.scala | 140 +++++++++--------- 2 files changed, 76 insertions(+), 73 deletions(-) diff --git a/doc/new-resolution-plan.md b/doc/new-resolution-plan.md index 2d1de24dd0..df51fbba94 100644 --- a/doc/new-resolution-plan.md +++ b/doc/new-resolution-plan.md @@ -105,10 +105,11 @@ receiver and capture context; module completion must not change lexical priority ordinary term uses request resolution during elaboration; class and pattern targets retain their specialized interpretation, and type positions do not request runtime values. `resolveOpenUses` and its second tree walk are removed. -- Only `term` and `subterm` default to term interpretation. This is a deliberate - exception to the usual rule against defaults: ordinary expression calls stay - concise, while symbolic positions and transparent wrappers explicitly specify - or forward the interpretation. Smaller forwarding helpers require it explicitly. +- `term` requires an explicit interpretation; only `subterm` defaults to `Trm`. + This is a deliberate exception to the usual rule against defaults: ordinary + recursive operands stay concise, while symbolic positions and transparent + wrappers explicitly specify or forward the interpretation. The enum cases are + imported as `Trm`, `Clss`, `Ptrn`, and `Tpe`. Other helpers require it explicitly. An implicit context was avoided because a target's interpretation must not leak into its value arguments or selection prefixes. - The resolver records direct definition references without demanding their value diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 9fcf56cb84..a813918465 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -502,7 +502,7 @@ object Elaborator: * a runtime value merely because they use the same reference syntax. */ enum Interpretation: - case Term, Class, Pattern, Type + case Trm, Clss, Ptrn, Tpe enum Mode: case Full @@ -729,6 +729,7 @@ end Elaborator import Elaborator.* +import Elaborator.Interpretation.{Trm, Clss, Ptrn, Tpe} class Elaborator(val tl: TraceLogger, val wd: io.Path, val prelude: Ctx) @@ -776,7 +777,7 @@ extends Importer: case App(Ident("config"), Tup(args)) => val modify = ConfigParser.parseOverrides(args) S(Annot.Config(modify)) - case _ => term(tree) match + case _ => term(tree, Trm) match case Term.Error() => N case trm => trm.symbol match @@ -1034,22 +1035,22 @@ extends Importer: // Interleaved-`let` bindings like `{ x is A then 0; let x = 1; ... }`. case LetLike(Keywrd(`let`), ident: Ident, S(rhsTree), N) => val symbol = VarSymbol(ident, erasedType = N) - val head = mkSplitLet(symbol, term(rhsTree)) + val head = mkSplitLet(symbol, term(rhsTree, Trm)) ((ctx + (ident.name -> symbol)), head ~: End) // Interleaved-`do` statements like `{ x is A then 0; do log(1); ... }`. case PrefixApp(Keywrd(`do`), rhsTree) => - (ctx, mkSplitLet(TempSymbol(N, erasedType = N, "unused"), term(rhsTree)) ~: End) + (ctx, mkSplitLet(TempSymbol(N, erasedType = N, "unused"), term(rhsTree, Trm)) ~: End) // Although the `else`-clause marks the end of the split, we cannot // stop and still have to elaborate the remaining trees. case PrefixApp(kwTree @ Keywrd(`else`), elseTree) => - (ctx, Else(term(elseTree))(S(new Keywrd(`else`).withLocOf(kwTree)))) + (ctx, Else(term(elseTree, Trm))(S(new Keywrd(`else`).withLocOf(kwTree)))) private def expandMatches(matchesTree: Ls[TT])(consequent: Ctxl[SimpleSplit]): Ctxl[SimpleSplit] = val z = (ctx, Ls[(Term, Pattern)]()) // Elaborate the term and the pattern in each match. val (innerCtx, matches) = matchesTree.foldLeft(z): case ((curCtx, matches), (scrutineeTree, patternTree)) => - val scrutinee = term(scrutineeTree)(using curCtx) + val scrutinee = term(scrutineeTree, Trm)(using curCtx) val pattern = this.pattern(patternTree)(using curCtx) pattern.variables.report val resCtx = curCtx ++ pattern.variables.allocate @@ -1063,32 +1064,32 @@ extends Importer: private def termBranch(t: Tree, mk: Term => Term): Ctxl[(Ctx, SimpleSplit)] = branch.appOrElse(t): case block: Block => (ctx, termSplit(block.desugStmts, mk)) - case lhs is rhs => (ctx, mk(term(lhs)).reference(patternBranch(_, rhs, identity))) + case lhs is rhs => (ctx, mk(term(lhs, Trm)).reference(patternBranch(_, rhs, identity))) // Several matches followed by `and`, `do`, or `then`. case matchesTree ~> consequent => val (coda, patternTree) :: matches = disaggregate(matchesTree) def innerSplit(using ctx: Ctx) = expandMatches(matches): consequent match case L(tree) => termSplit(Ls(tree), identity) - case R((kw, tree)) => Else(term(tree))(S(kw)) + case R((kw, tree)) => Else(term(tree, Trm))(S(kw)) val split = coda match case Under() => innerSplit - case coda => mk(term(coda)).reference: scrutinee => + case coda => mk(term(coda, Trm)).reference: scrutinee => val pattern = this.pattern(patternTree) val innerCtx = ctx ++ pattern.variables.allocate mkMatch(scrutinee(), pattern, innerSplit(using innerCtx)) ~: End (ctx, split) // Handle splits on binary operators. case OpApp(lhs, ident: Ident, rhss) => - val op = term(ident) - val split = term(lhs).reference: lhs => + val op = term(ident, Trm) + val split = term(lhs, Trm).reference: lhs => val mk2 = (rhs: Term) => val args = Term.Tup(PlainFld(lhs()) :: PlainFld(rhs) :: Nil)(DummyTup) app(op, args)(Tree.DummyApp, N, FlowSymbol("‹operator-split›")) termSplit(rhss, mk2 andThen mk) (ctx, split) case OpSplit(lhs, rhss) => - val split = mk(term(lhs)).reference: lhs => + val split = mk(term(lhs, Trm)).reference: lhs => val (_, splits) = rhss.foldLeft((ctx, Ls[SimpleSplit]())): case ((curCtx, splits), t) => operatorBranch(lhs, t)(using curCtx).mapSecond(_ :: splits) @@ -1101,7 +1102,7 @@ extends Importer: private def operatorBranch(scrutinee: Reference, rhs: Tree): Ctxl[(Ctx, SimpleSplit)] = branch.appOrElse(rhs): rhsTree => - termBranch(rhsTree.splitOn(Trm(scrutinee())), identity) + termBranch(rhsTree.splitOn(Tree.Trm(scrutinee())), identity) private def patternBranch(scrutinee: Reference, t: Tree, mk: Tree => Tree): Ctxl[SimpleSplit] = t match case block: Block => @@ -1125,7 +1126,7 @@ extends Importer: consequentTree match case L(tree) => termSplit(Ls(tree), identity) - case R((kw, tree)) => Else(term(tree))(S(kw)) + case R((kw, tree)) => Else(term(tree, Trm))(S(kw)) mkMatch(scrutinee(), firstPattern, split) ~: End case _ => error(msg"Unrecognized pattern split (${t.describe})." -> t.toLoc) @@ -1176,11 +1177,10 @@ extends Importer: case test => () => acc((test, Tree.BoolLit(true))) go(tree, ::(_, Nil))() - // Deliberate default: most calls elaborate runtime expressions. Transparent - // wrappers must forward the interpretation; class, pattern, and type positions - // must specify it. An explicit parameter (rather than an implicit context) - // prevents the interpretation of a target from leaking into its value operands. - def term(tree: Tree, interpretation: Interpretation = Interpretation.Term): Ctxl[Term] = + // Callers specify the interpretation at each elaboration entry point. Keeping + // it explicit (rather than implicit) prevents a target's interpretation from + // leaking into its value operands. + def term(tree: Tree, interpretation: Interpretation): Ctxl[Term] = trace[Term](s"Elab term ${tree.showDbg}", r => s"~> ${r.showDbg}"): val unders = mutable.ArrayBuffer.empty[VarSymbol] given UnderCtx = new UnderCtx(S(unders)) @@ -1237,10 +1237,12 @@ extends Importer: * Listeners can wait for forward definitions to complete. */ private def interpretRef(ref: Term, interpretation: Interpretation): Term = - if newResolution && (interpretation is Interpretation.Term) then requireTerm(ref) + if newResolution && (interpretation is Trm) then requireTerm(ref) ref - def subterm(tree: Tree, interpretation: Interpretation = Interpretation.Term): Ctxl[UnderCtx ?=> Term] = + // Most recursive operands are terms. Transparent wrappers must explicitly + // forward their interpretation; class, pattern, and type operands specify theirs. + def subterm(tree: Tree, interpretation: Interpretation = Trm): Ctxl[UnderCtx ?=> Term] = trace[Term](s"Elab subterm ${tree.showDbg}", r => s"~> ${r.showDbg}"): def error = Term.Error().withLocOf(tree) @@ -1280,7 +1282,7 @@ extends Importer: Term.Sel(preTrm, tree.name)(sym, FlowSymbol.sel(tree.name.name), N, S(summon)) tree.desugared match - case Trm(term) => interpretRef(term, interpretation) + case Tree.Trm(term) => interpretRef(term, interpretation) case unt @ Unt() => unit.withLocOf(unt) case Bra(k, e) => k match @@ -1370,9 +1372,9 @@ extends Importer: val (cp, p) = c match case App(c, Tup(params)) => - (subterm(c, Interpretation.Class), params.map(subterm(_))) + (subterm(c, Clss), params.map(subterm(_))) case c => - (subterm(c, Interpretation.Class), Nil) + (subterm(c, Clss), Nil) (ctx + (id.name -> sym)).givenIn: Term.Handle(sym, cp, p, derivedClsSym, tds, subterm(bod, interpretation)) @@ -1393,11 +1395,11 @@ extends Importer: error case TyApp(lhs, targs) => Term.TyApp(subterm(lhs, interpretation), targs.map { - case Modified(Keywrd(Keyword.`in`), arg) => Term.WildcardTy(S(subterm(arg, Interpretation.Type)), N) - case Modified(Keywrd(Keyword.`out`), arg) => Term.WildcardTy(N, S(subterm(arg, Interpretation.Type))) + case Modified(Keywrd(Keyword.`in`), arg) => Term.WildcardTy(S(subterm(arg, Tpe)), N) + case Modified(Keywrd(Keyword.`out`), arg) => Term.WildcardTy(N, S(subterm(arg, Tpe))) case Tup(Modified(Keywrd(Keyword.`in`), arg1) :: Modified(Keywrd(Keyword.`out`), arg2) :: Nil) => - Term.WildcardTy(S(subterm(arg1, Interpretation.Type)), S(subterm(arg2, Interpretation.Type))) - case arg => subterm(arg, Interpretation.Type) + Term.WildcardTy(S(subterm(arg1, Tpe)), S(subterm(arg2, Tpe))) + case arg => subterm(arg, Tpe) })(N).withLocOf(tree) case InfixApp(TyTup(tvs), Keywrd(Keyword.`->`), body) => val boundVars = mutable.HashMap.empty[Str, VarSymbol] @@ -1430,25 +1432,25 @@ extends Importer: given Ctx = ctx ++ boundVars val bds = syms.map: case (sym, ub, lb) => - QuantVar(sym, ub.map(ub => subterm(ub, Interpretation.Type)), lb.map(lb => subterm(lb, Interpretation.Type))) - Term.Forall(bds, outer, subterm(body, Interpretation.Type)) + QuantVar(sym, ub.map(ub => subterm(ub, Tpe)), lb.map(lb => subterm(lb, Tpe))) + Term.Forall(bds, outer, subterm(body, Tpe)) case InfixApp(lhs, Keywrd(Keyword.`->`), Effectful(eff, rhs)) => - Term.FunTy(subterm(lhs, Interpretation.Type), subterm(rhs, Interpretation.Type), S(subterm(eff, Interpretation.Type))) + Term.FunTy(subterm(lhs, Tpe), subterm(rhs, Tpe), S(subterm(eff, Tpe))) case InfixApp(lhs, Keywrd(Keyword.`->`), rhs) => - Term.FunTy(subterm(lhs, Interpretation.Type), subterm(rhs, Interpretation.Type), N) + Term.FunTy(subterm(lhs, Tpe), subterm(rhs, Tpe), N) case InfixApp(lhs, Keywrd(Keyword.`=>`), rhs) => lhs match case Tup(_) => ctx.nest(OuterCtx.LambdaOrHandlerBlock).givenIn: val (syms, nestCtx) = funParams(lhs) - Term.Lam(syms, term(rhs)(using nestCtx)) + Term.Lam(syms, term(rhs, Trm)(using nestCtx)) case TyTup(tys) => val constraints = tys.flatMap(maybeConstraint) val body = term(rhs, interpretation) Term.Constrained(constraints, body) case _ => lastWords(s"Unexpected lambda parameter shape: $lhs") case InfixApp(lhs, Keywrd(Keyword.`as`), rhs) => - Term.Asc(subterm(lhs, interpretation), subterm(rhs, Interpretation.Type)) + Term.Asc(subterm(lhs, interpretation), subterm(rhs, Tpe)) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => block(Block(tree :: Nil), hasResult = false, resultInterpretation = interpretation)._1 case PrefixApp(kw @ Keywrd(Keyword.`not`), rhs) => @@ -1457,7 +1459,7 @@ extends Importer: case tree @ InfixApp(lhs, Keywrd(Keyword.`is` | Keyword.`and` | Keyword.`or`), rhs) => ifLike(Keyword.`if`, IfLikeForm.ReturningIf, shorthandSplit(tree), tree.toLoc) case InfixApp(Sel(pre, idn: Ident), Keywrd(Keyword.`#`), idp: Ident) => - val c = subterm(idn, Interpretation.Class) + val c = subterm(idn, Clss) val f = c.symbol.flatMap(_.asCls) match case S(cls: ClassSymbol) => cls.tree.allSymbols.get(idp.name) match @@ -1470,9 +1472,9 @@ extends Importer: N Term.SelProj(subterm(pre), c, idp)(f, FlowSymbol.selProj(idp.name), N, S(summon)) case InfixApp(lhs, op @ Keywrd(Keyword.`|`), rhs) => - Term.CompType(subterm(lhs, Interpretation.Type), subterm(rhs, Interpretation.Type), true)//.withLocOf(tree) + Term.CompType(subterm(lhs, Tpe), subterm(rhs, Tpe), true)//.withLocOf(tree) case InfixApp(lhs, op @ Keywrd(Keyword.`&`), rhs) => - Term.CompType(subterm(lhs, Interpretation.Type), subterm(rhs, Interpretation.Type), false)//.withLocOf(tree) + Term.CompType(subterm(lhs, Tpe), subterm(rhs, Tpe), false)//.withLocOf(tree) case InfixApp(lhs, kw, rhs) => raise: ErrorReport(msg"Unexpected infix use of keyword '${kw.name}' here" -> tree.toLoc :: Nil) @@ -1482,7 +1484,7 @@ extends Importer: case App(Ident("!"), Tup(rhs :: Nil)) => Term.Deref(subterm(rhs)) case App(Ident("~"), Tup(rhs :: Nil)) => - Term.Neg(subterm(rhs, Interpretation.Type)) + Term.Neg(subterm(rhs, Tpe)) case PrefixApp(Keywrd(Keyword.`|` | Keyword.`&`), rhs) => subterm(rhs, interpretation) case tree @ OpSplit(lhs, rhss) => @@ -1582,7 +1584,7 @@ extends Importer: case sel @ Sel(pre, nme) => elaborateSelection(sel) case MemberProj(ct, nme) => - val c = subterm(ct, Interpretation.Class) + val c = subterm(ct, Clss) val f = c.symbol.flatMap(_.asCls) match case S(cls: ClassSymbol) => cls.tree.allSymbols.get(nme.name) match @@ -1634,14 +1636,14 @@ extends Importer: clsSym -> // TODO integrate context inherited from cls // TODO make context with var symbols for class parameters - ObjBody(block(rft, hasResult = false, resultInterpretation = Interpretation.Term)._1) + ObjBody(block(rft, hasResult = false, resultInterpretation = Trm)._1) body match case S(Apps(c, args)) => val (mut, c2) = c match case Modified(Keywrd(Keyword.`mut`), c) => (true, c) case c => (false, c) val inner = mkNew( - subterm(c2, Interpretation.Class), // * Note: we'll catch bad `new` targets during type checking + subterm(c2, Clss), // * Note: we'll catch bad `new` targets during type checking args.map(subterm(_)), bodo )(N) @@ -1773,12 +1775,12 @@ extends Importer: val sym = FlowSymbol.app() val fl = Fld(FldFlags.empty, res, N) val ap_ = app(subterm(f), Term.Tup( - fl :: args.map(fld(_, Interpretation.Term)))(tup))(ap, N, sym) + fl :: args.map(fld(_, Trm)))(tup))(ap, N, sym) go(ap_, trees) case (ap @ App(f, tup @ Tup(args))) :: trees => val sym = FlowSymbol.app() go(app(subterm(f), - Term.Tup(Fld(FldFlags.empty, acc, N) :: args.map(fld(_, Interpretation.Term)))(tup) + Term.Tup(Fld(FldFlags.empty, acc, N) :: args.map(fld(_, Trm)))(tup) )(ap, N, sym), trees) case Block(sts) :: trees => go(acc, sts ::: trees) @@ -1836,9 +1838,9 @@ extends Importer: case _ => term(tree, interpretation) def fld(tree: Tree, interpretation: Interpretation)(using UnderCtx): Ctxl[Elem] = tree match case InfixApp(id: Ident, Keywrd(Keyword.`:`), rhs) => - Fld(FldFlags.empty, Term.Lit(StrLit(id.name).withLocOf(id)), S(arg(rhs, Interpretation.Type))) + Fld(FldFlags.empty, Term.Lit(StrLit(id.name).withLocOf(id)), S(arg(rhs, Tpe))) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => - Fld(FldFlags.empty, term(lhs, interpretation), S(arg(rhs, Interpretation.Type))) + Fld(FldFlags.empty, term(lhs, interpretation), S(arg(rhs, Tpe))) case Spread(Keywrd(Keyword.`..`), S(trm)) => Spd(SpreadKind.Lazy, arg(trm, interpretation)) case Spread(Keywrd(Keyword.`...`), S(trm)) => @@ -1853,7 +1855,7 @@ extends Importer: def block(sts: Ls[Tree], hasResult: Bool)(using UnderCtx): Ctxl[(Blk, Ctx)] = - block(new Block(sts), hasResult, Interpretation.Term) + block(new Block(sts), hasResult, Trm) def block(blk: Block, hasResult: Bool, resultInterpretation: Interpretation)(using UnderCtx): Ctxl[(Blk, Ctx)] = blockOrRcd(blk, hasResult, resultInterpretation) match @@ -1886,8 +1888,8 @@ extends Importer: val members = blk.definedSymbols.toMap val fieldInterpretation = resultInterpretation match - case Interpretation.Type => Interpretation.Type - case _ => Interpretation.Term + case Tpe => Tpe + case _ => Trm val newSignatureTrees = mutable.Map.empty[Str, Tree] // * Store trees of signatures // * Check for double/incompatible definitions and declarations @@ -2084,7 +2086,7 @@ extends Importer: RcdField(Term.Lit(lit).withLocOf(lit), rhs_t) :: acc case Bra(Round, inner) => reportUnusedAnnotations - RcdField(term(inner), rhs_t) :: acc + RcdField(term(inner, Trm), rhs_t) :: acc case _ => raise(ErrorReport(msg"Unexpected record key shape." -> rlhs.toLoc :: Nil)) RcdField(Term.Error().withLocOf(rlhs), rhs_t) :: acc @@ -2102,7 +2104,7 @@ extends Importer: case S(rhs) => val rrhs = tups.foldRight(rhs): InfixApp(_, Keywrd(Keyword.`=>`), _) - mkLetBinding(kw, sym, term(rrhs), annotations) reverse_::: acc + mkLetBinding(kw, sym, term(rrhs, Trm), annotations) reverse_::: acc case N => if tups.nonEmpty then raise(ErrorReport(msg"Expected a right-hand side for let bindings with parameters" -> hd.toLoc :: Nil)) @@ -2116,7 +2118,7 @@ extends Importer: reportUnusedAnnotations lhs match case id: Ident => - val r = term(rhs) + val r = term(rhs, Trm) ctx.get(id.name) match case S(elem) => elem.symbol match @@ -2185,8 +2187,8 @@ extends Importer: // unwrap possible module modifier // e.g, `fun f: module M` // ^^^^^^ - case TypeDef(Mod, st, N) => term(st, Interpretation.Type)(using newCtx) - case st => term(st, Interpretation.Type)(using newCtx) + case TypeDef(Mod, st, N) => term(st, Tpe)(using newCtx) + case st => term(st, Tpe)(using newCtx) val body: Opt[Term] = rhs match case N => N @@ -2198,7 +2200,7 @@ extends Importer: if pss.isEmpty && hasGeneratorAnnotation then raise(ErrorReport(msg"Generators are not supported on functions without a parameter list" -> td.toLoc :: Nil)) newCtx.nest(OuterCtx.Function(nonLocalRetHandler, S(tsym))(pss.nonEmpty && hasGeneratorAnnotation, hasAsyncAnnotation)).givenIn: newCtx ?=> - val b = term(rhs)(using newCtx) + val b = term(rhs, Trm)(using newCtx) if nonLocalRetHandler.directRefs.isEmpty then b else mkEffectHandleAbortive( nonLocalRetHandler, @@ -2471,7 +2473,7 @@ extends Importer: def mkBody(extraParams: Ls[ParamList])(using Ctx) = withFields(extraParams): body match case N | S(Error()) => (new Blk(Nil, Term.Lit(UnitLit(false))), ctx) - case S(b: Block) => block(b, hasResult = false, resultInterpretation = Interpretation.Term) + case S(b: Block) => block(b, hasResult = false, resultInterpretation = Trm) case S(t) => raise(ErrorReport( msg"Illegal body of ${k.desc} definition (should be a block; found ${t.describe})." -> t.toLoc :: Nil)) @@ -2486,7 +2488,7 @@ extends Importer: assert(body.isEmpty) val d = given Ctx = newCtx - semantics.TypeDef(alsSym, sym, tps, rhs.map(term(_, Interpretation.Type)), N, annotations) + semantics.TypeDef(alsSym, sym, tps, rhs.map(term(_, Tpe)), N, annotations) alsSym.defn = S(d) d case Pat => @@ -2641,7 +2643,7 @@ extends Importer: go(sts, annotations, acc) case (st: Tree) :: sts => // TODO reject plain term statements? Currently, `(1, 2)` is allowed to elaborate (tho it should be rejected in type checking later) - val interpretation = if sts.isEmpty then resultInterpretation else Interpretation.Term + val interpretation = if sts.isEmpty then resultInterpretation else Trm val res = annotations.foldLeft(term(st, interpretation)): case (acc, ann) => Term.Annotated(ann, acc) sts match @@ -2689,7 +2691,7 @@ extends Importer: def newOf(td: TypeDef): Ctxl[Opt[Term.New]] = td.extension match - case S(ext) => S(term(ProperNew(S(ext), N))) + case S(ext) => S(term(ProperNew(S(ext), N), Trm)) case N => N match case S(n: Term.New) => S(n) @@ -2714,7 +2716,7 @@ extends Importer: new Ident(base).withLocOf(id) -> (id.name :: Nil) case N => id -> Nil - val sig = sign.map(term(_, Interpretation.Type)) + val sig = sign.map(term(_, Tpe)) val mfn = Modulefulness.ofSign(sig)(Mod in modifiers) // * As for return signatures, a moduleful parameter (`module m: M`) denotes the module itself, which // * `eraseSign` would miss by resolving the name through `asTpe`. @@ -2739,8 +2741,8 @@ extends Importer: /** Elaborate a subtyping constraint. */ def constraint(lhs: Tree, op: "<:<" | ">:>", rhs: Tree): Ctxl[SubConstraint] = - val l = term(lhs, Interpretation.Type) - val r = term(rhs, Interpretation.Type) + val l = term(lhs, Tpe) + val r = term(rhs, Tpe) val dir = op match case "<:<" => SubDir.Sub case ">:>" => SubDir.Sup @@ -2859,7 +2861,7 @@ extends Importer: (name -> parameterSymbol, symbol -> parameterSymbol) .toList.unzip pattern.variables.report // Report all invalid variables we found in `pattern`. - Transform(pattern, correspondence, term(rhs)(using ctx ++ contextEntries)) + Transform(pattern, correspondence, term(rhs, Trm)(using ctx ++ contextEntries)) /** Elaborate tuple patterns like `[p1, p2, ...ps, pn]`. */ def tuple(ts: Ls[Tree]): Ctxl[Pattern.Tuple] = // We are accumulating two components: the leading patterns, the spread @@ -2905,7 +2907,7 @@ extends Importer: t match // Annotated patterns like `@compile P`. case Tree.Annotated(annotation, target) => - go(target).annotate(term(annotation), t.toLoc) + go(target).annotate(term(annotation, Trm), t.toLoc) // Brackets. case Bra(BracketKind.Round | BracketKind.Curly, t) => go(t) // Tuple patterns like `[p1, p2, ...ps, pn]`. @@ -2926,7 +2928,7 @@ extends Importer: Composition(op is Keyword.`|`, go(lhs), go(rhs)) // Constructor patterns with pattern arguments and arguments. case App(ctor: Ctor, Tup(argTrees)) => // TODO: rm this weird `: Ctor` guard - val lt = term(ctor, Interpretation.Pattern) + val lt = term(ctor, Ptrn) val rt = argTrees.map(go(_)) val res = new Constructor(lt, S(rt)) // if newResolution then listenTerm(lt, shape => patAppShape(shape, rt, res)) @@ -2953,7 +2955,7 @@ extends Importer: case _: Tree => Chain(go(p), go(q)) case p where t => val q = go(p) - Guarded(q, term(t)(using ctx ++ q.variables.allocate)) + Guarded(q, term(t, Trm)(using ctx ++ q.variables.allocate)) case Under() => Pattern.Wildcard().withLocOf(t) // Singleton blocks like `{1}`. case Block(p :: Nil) => go(p) @@ -2978,7 +2980,7 @@ extends Importer: // pattern translation. case OpApp(lhs, Ident("~"), rhs :: Nil) => Pattern.Concatenation(go(lhs), go(rhs)) // Constructor patterns can be written in the infix form. - case OpApp(lhs, op, rhs :: Nil) => Pattern.Constructor(term(op, Interpretation.Pattern), S(Ls(go(lhs), go(rhs)))) + case OpApp(lhs, op, rhs :: Nil) => Pattern.Constructor(term(op, Ptrn), S(Ls(go(lhs), go(rhs)))) // Constructor patterns without arguments case id @ Ident(name) if name.isUncapitalized => Variable(id) case id @ Ident(name) => ident(id) match @@ -2987,7 +2989,7 @@ extends Importer: raise: ErrorReport(msg"Pattern name not found: ${id.name}." -> id.toLoc :: Nil) Pattern.Wildcard() - case sel: (SynthSel | Sel) => Constructor(term(sel, Interpretation.Pattern), N) + case sel: (SynthSel | Sel) => Constructor(term(sel, Ptrn), N) case _: Tree => raise(ErrorReport(msg"Unrecognized pattern (${t.describe})." -> t.toLoc :: Nil)) Pattern.Wildcard() @@ -3012,13 +3014,13 @@ extends Importer: def importFrom(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) - val (res, newCtx) = block(sts, hasResult = false, resultInterpretation = Interpretation.Term) + val (res, newCtx) = block(sts, hasResult = false, resultInterpretation = Trm) // TODO handle name clashes (res, newCtx) def topLevel(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) - val (res, ctx) = block(sts, hasResult = false, resultInterpretation = Interpretation.Term) + val (res, ctx) = block(sts, hasResult = false, resultInterpretation = Trm) computeVariances(res) (res, ctx) From 8fa221f2164b8697d034c6c05fde48df5ca126b3 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 14:14:03 +0800 Subject: [PATCH 017/228] Resolve lexical selections through receivers and remove lowering fallbacks --- doc/new-resolution-plan.md | 34 ++++++++-- .../main/scala/hkmc2/codegen/Lowering.scala | 67 +++++-------------- .../scala/hkmc2/semantics/Elaborator.scala | 12 +--- .../scala/hkmc2/semantics/NewResolver.scala | 11 +++ .../src/test/mlscript/newres/CtxSens.mls | 4 +- .../test/mlscript/newres/NestedModules.mls | 4 +- .../src/test/mlscript/newres/PatMat.mls | 4 +- .../mlscript/newres/ReceiverResolution.mls | 38 +++++++++++ .../src/test/mlscript/newres/ValCtxSens.mls | 4 +- 9 files changed, 105 insertions(+), 73 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ReceiverResolution.mls diff --git a/doc/new-resolution-plan.md b/doc/new-resolution-plan.md index df51fbba94..7a39e7a59e 100644 --- a/doc/new-resolution-plan.md +++ b/doc/new-resolution-plan.md @@ -91,10 +91,10 @@ receiver and capture context; module completion must not change lexical priority constructor-shape cache branch. Listeners are now registered once; cached constructors are reused with assertions checking their definition and base. An existing applied-pattern test now passes without its `:todo` marker. -- Remaining stage-4 work: the lexical `SelElem` shape shortcut and read-side - lowering fallbacks still exist. Bare constructor names in patterns still use - eager classification; `Opens.mls` records this limitation with a `:fixme` - regression for both open forms. Applied constructor patterns already pass. +- Bare constructor names in patterns still use eager classification; `Opens.mls` + records this limitation with a `:fixme` regression for both open forms. Applied + constructor patterns already pass. The stage-4 checkpoint below removes the + lexical shortcut and read-side lowering fallbacks. - Stage 3 validation: `ctest` passed (45 tests), `dtest newres/` passed (26 files), and `hkmc2AllTests/test` passed with the final regression outputs. @@ -123,3 +123,29 @@ receiver and capture context; module completion must not change lexical priority `dtest newres/` passed (27 files), and the final `hkmc2AllTests/test` passed. Existing runtime outputs are unchanged; reviewed golden updates record the additional resolution requests made during elaboration. + +- Stage 4, receiver and lowering checkpoint: lexical `SelElem` references now + register ordinary selection listeners, just like selective opens. `SelfRef` + obtains its base shape through definition-completion listeners, handling both + pending and already available definitions and retaining inherited shapes. + The resolver caches these base shapes; no new mutable symbol state is needed. +- `MemberRef` and `NewSel` lowering now consume `resolvedTargets` exclusively. + Diagnosed references produce the error result; missing targets produce an + unresolved-target diagnostic; strict ambiguous selections report all selected + definitions. Existing non-strict dynamic selection behavior is preserved. +- `ReceiverResolution.mls` covers forward lexical members, overloaded class/value + names, nested receiver capture, later selective opens, and inherited selection. + Ambiguity diagnostics retain owner context and now point at selected definition + names rather than the entire overload declaration. +- Reviewed the existing independent class storage: name reservation records live + IR class symbols, and class and term slots remain separate. No storage change + is needed for this checkpoint. +- Remaining stage 4: replace eager bare-pattern classification and the pattern + resolver's single-result field with accumulated candidates, including receiver + ambiguity. Integrate bare, applied, selected, and infix constructors without + changing alias-binding semantics. Keep this separate from the receiver/lowering + checkpoint so those semantic changes have their own regression review. +- Receiver/lowering validation: `ctest` passed (45 tests), the focused receiver + regression passed, and final `hkmc2AllTests/test` passed (including 659 diff + tests). Reviewed the four changed ambiguity snapshots and the new regression's + runtime outputs. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 77590b9413..b28feb58ce 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -912,21 +912,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case t @ st.SelfRef(sym) => ref(t, annots, N, inStmtPos = inStmtPos)(k) case t @ st.MemberRef(bms) => - t.resolvedTargets.distinct match + if t.isErroneous then compError else t.resolvedTargets.distinct match case Nil => - // fail: - // ErrorReport( - // msg"Member reference '${sym.nme}' has no resolved target" -> t.toLoc :: Nil, S(t), - // source = Diagnostic.Source.Compilation) - bms.asTrm orElse bms.asModOrObj orElse bms.asCls match - case s @ S(_) => - ref(t, annots, s, inStmtPos = inStmtPos)(k) - case N => - if t.isErroneous then raise: - ErrorReport( - msg"Member reference '${bms.nme}' cannot be used as a term" -> t.toLoc :: Nil, S(t), - source = Diagnostic.Source.Compilation) - compError + fail: + ErrorReport(msg"Member reference '${bms.nme}' has no resolved target" -> t.toLoc :: Nil, + source = Diagnostic.Source.Compilation) case trgt :: Nil => ref(t, annots, S(trgt), inStmtPos = inStmtPos)(k) case ts => @@ -1224,46 +1214,21 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case whltrm: st.SynthWhile => ucs.Normalization(this)(whltrm)(k) case sel @ NewSel(prefix, id) => - if sel.isErroneous then compError else - // /* - // TODO dedup with MemberRef - sel.resolvedMembers.distinct match - case Nil => - fail: - // ErrorReport( - // msg"Member reference '${sym.nme}' has no resolved target" -> t.toLoc :: Nil, S(t), - // source = Diagnostic.Source.Compilation) - ErrorReport( - msg"This selection of member '${sel.id.name}' has no resolved target" -> sel.toLoc :: - Nil, S(sel), source = Diagnostic.Source.Compilation) - case bms :: Nil => - sel.resolvedTargets.distinct match - case Nil => - bms.asTrm orElse bms.asModOrObj orElse bms.asCls match - case s @ S(_) => - setupSelection(prefix, id, s)(k) - case N => - if sel.isErroneous then raise: - ErrorReport( - msg"Member reference '${bms.nme}' cannot be used as a term" -> sel.toLoc :: Nil, S(sel), - source = Diagnostic.Source.Compilation) - compError - case trgt :: Nil => setupSelection(prefix, id, S(trgt))(k) - case ts => ??? - case ts => - // if t.isErroneous then + if sel.isErroneous then compError else sel.resolvedTargets.distinct match + case Nil => fail: + ErrorReport(msg"This selection of member '${id.name}' has no resolved target" -> sel.toLoc :: Nil, + source = Diagnostic.Source.Compilation) + case target :: Nil => setupSelection(prefix, id, S(target))(k) + case targets => if strictResolution then fail: ErrorReport( - msg"This selection of member '${sel.id.name}' is ambiguous, as it has multiple resolved targets" -> t.toLoc :: - ts.map: t => - msg"target: ${t.describe} '${t.nme}'${ - t.asTrm.fold("")(_.owner.fold("")(o => s" in ${o.asBlkMember.get.describe} '${o.nme}'")) // TOOD other kinds of owned symbols - }" -> t.toLoc - , S(ts.map(s => s.showDbg + " " + s.tsym.map(_.showDbg))), source = Diagnostic.Source.Compilation) - // compError + msg"This selection of member '${id.name}' is ambiguous, as it has multiple resolved targets" -> sel.toLoc :: + targets.map: target => + val owner = target.asTrm.flatMap(_.owner).fold("")(o => s" in ${o.asBlkMember.get.describe} '${o.nme}'") + msg"target: ${target.describeKind} '${target.nme}'${owner}" -> target.toLoc + , + source = Diagnostic.Source.Compilation) else setupSelection(prefix, id, N)(k) - // ??? - // */ case sel @ Sel(prefix, nme) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index a813918465..db71cd69f3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -465,19 +465,11 @@ object Elaborator: // * Same remark as in RefElem#ref val prefix = base.ref(Ident(base.nme)) val name = new Ident(nme).withLocOf(id) - symOpt match - case _ if config.language.useNewResolution && isImport => + if config.language.useNewResolution then val res = new Term.NewSel(prefix, name)(FlowSymbol.synthSel(nme)).withLocOf(id) summon[NewResolver].newSel(res) res - case S(bms: BlockMemberSymbol) if config.language.useNewResolution => - // Lexical lookup already identifies the member, but must preserve its - // overload set until its use chooses a class, term, or module. - val res = new Term.NewSel(prefix, name)(FlowSymbol.synthSel(nme)).withLocOf(id) - res.resolvedMembers = bms :: Nil - res.shapes += SymShape(bms, res.resSym, Nil) - res - case _ => + else Term.SynthSel(prefix, name)(symOpt, FlowSymbol.synthSel(nme), N, S(summon)) def symbol = symOpt final case class WildcardElem(nme: Str, sources: Ls[Elem]) extends Elem: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 01432e1b35..1459bf0db0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -46,6 +46,7 @@ class NewResolver: val newShapes: mutable.Map[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = mutable.Map.empty val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty + private val selfShapes: mutable.Map[(InnerSymbol, Opt[TermShape]), BaseShape] = mutable.Map.empty val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty def isOwnedSym(sym: Symbol): Bool = @@ -539,6 +540,16 @@ class NewResolver: case loc: LocalSymbol => loc.shapes.foreach(listener) loc.shapeListeners += listener + case SelfRef(sym) => + // A receiver can be referenced before its body is complete or after its + // definition has already been published. The definition listener handles both. + def completed(defn: ClassLikeDef): Unit = + listenExt(defn.ext, ext => + listener(selfShapes.getOrElseUpdate((sym, ext), BaseShape(defn, ext)))) + val symbol = sym.asDefnSym + symbol.defn match + case S(defn) => completed(defn) + case N => symbol.defnListeners += completed case ref @ MemberRef(sym: TermSymbol) => ??? // listenDefn(sym, sh => diff --git a/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls b/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls index 2ba61244cd..9ee64c9220 100644 --- a/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls @@ -63,10 +63,10 @@ bar(B).u //│ ║ ^^^^^^^^ //│ ╟── target: value 'u' in module 'B' //│ ║ l.9: val u = 2 -//│ ║ ^^^^^^^^^ +//│ ║ ^ //│ ╟── target: value 'u' in module 'A' //│ ║ l.6: val u = 1 -//│ ╙── ^^^^^^^^^ +//│ ╙── ^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls index b470143fd7..940cd65927 100644 --- a/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls +++ b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls @@ -85,11 +85,11 @@ m.id //│ ╟── target: function 'id' in module 'Predef' //│ ║ in ../../mlscript-compile/Predef.mls //│ ║ l.18: fun id(x) = x -//│ ║ ^^^^^^^^^^^^^ +//│ ║ ^^ //│ ╟── target: function 'id' in module 'Bar' //│ ║ in NestedModules.mls:6 //│ ║ l.8: fun id(x) = x -//│ ╙── ^^^^^^^^^^^^^ +//│ ╙── ^^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls index 792c28d94a..8104c19a02 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls @@ -124,10 +124,10 @@ x.b //│ ║ ^^^ //│ ╟── target: function 'b' in overloaded function and class 'Baz' //│ ║ l.7: class Baz(c) { fun b = c } -//│ ║ ^^^^^^^^^ +//│ ║ ^ //│ ╟── target: value 'b' in object 'Bar' //│ ║ l.6: object Bar { val b = 123 } -//│ ╙── ^^^^^^^^^^^ +//│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :todo diff --git a/hkmc2/shared/src/test/mlscript/newres/ReceiverResolution.mls b/hkmc2/shared/src/test/mlscript/newres/ReceiverResolution.mls new file mode 100644 index 0000000000..208a37fc45 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ReceiverResolution.mls @@ -0,0 +1,38 @@ +:. + + +// Lexical member references resolve through their receiver, including members +// declared after the method and overloaded class/term names. +module Receiver { + fun values() = [value, Item, (new Item).answer()] + val value = 31 + class Item { fun answer() = value } + val Item = 32 +} + +:expect [31, 32, 31] +Receiver.values() +//│ = [31, 32, 31] + + +// A later lookup must also obtain the already completed receiver's shape. +fun later() = { + open Receiver {values} + values() +} + +:expect [31, 32, 31] +later() +//│ = [31, 32, 31] + + +// Inherited selections and lexical selections share the same receiver path. +class Parent { fun inherited() = 33 } +class Child extends Parent { + fun values() = [local(), this.inherited()] + fun local() = 34 +} + +:expect [34, 33] +(new Child).values() +//│ = [34, 33] diff --git a/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls index 67813b83a9..2ca3d9573d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls @@ -62,10 +62,10 @@ alias(A).u //│ ║ ^^^^^^^^^^ //│ ╟── target: value 'u' in module 'A' //│ ║ l.6: val u = 1 -//│ ║ ^^^^^^^^^ +//│ ║ ^ //│ ╟── target: value 'u' in module 'B' //│ ║ l.9: val u = 2 -//│ ╙── ^^^^^^^^^ +//│ ╙── ^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 0a56bf230294fdd54d21d62340e12ce2b965f943 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 14:23:58 +0800 Subject: [PATCH 018/228] Enforce one self shape per inner symbol --- .../src/main/scala/hkmc2/semantics/NewResolver.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 1459bf0db0..fdf5d86359 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -46,7 +46,7 @@ class NewResolver: val newShapes: mutable.Map[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = mutable.Map.empty val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty - private val selfShapes: mutable.Map[(InnerSymbol, Opt[TermShape]), BaseShape] = mutable.Map.empty + private val selfShapes: mutable.Map[InnerSymbol, BaseShape] = mutable.Map.empty val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty def isOwnedSym(sym: Symbol): Bool = @@ -545,7 +545,11 @@ class NewResolver: // definition has already been published. The definition listener handles both. def completed(defn: ClassLikeDef): Unit = listenExt(defn.ext, ext => - listener(selfShapes.getOrElseUpdate((sym, ext), BaseShape(defn, ext)))) + // Each inner symbol has one self shape; repeated notifications must agree. + val shape = selfShapes.getOrElseUpdate(sym, BaseShape(defn, ext)) + softAssert(shape.defn is defn) + softAssert(shape.ext == ext) + listener(shape)) val symbol = sym.asDefnSym symbol.defn match case S(defn) => completed(defn) From 06e710d97b524b970b8eaef39648def4f03a0e93 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 14:46:21 +0800 Subject: [PATCH 019/228] Complete deferred constructor-pattern resolution and inheritance lookup --- doc/new-resolution-plan.md | 75 +++++- doc/reference.md | 13 +- .../main/scala/hkmc2/codegen/Lowering.scala | 10 +- .../scala/hkmc2/semantics/Elaborator.scala | 29 ++- .../scala/hkmc2/semantics/NewResolver.scala | 233 ++++++++---------- .../main/scala/hkmc2/semantics/Pattern.scala | 2 +- .../scala/hkmc2/semantics/PatternShape.scala | 9 +- .../main/scala/hkmc2/semantics/Shape.scala | 44 +--- .../main/scala/hkmc2/semantics/Symbol.scala | 5 +- .../src/main/scala/hkmc2/semantics/Term.scala | 7 + .../hkmc2/semantics/ups/SplitCompiler.scala | 114 +++++---- .../mlscript/newres/ConstructorResolution.mls | 225 +++++++++++++++++ .../src/test/mlscript/newres/Inheritance.mls | 30 +-- .../shared/src/test/mlscript/newres/Opens.mls | 12 +- .../newres/OverloadedClassImports.mls | 14 +- .../mlscript/newres/OverloadedClassValues.mls | 24 +- .../src/test/mlscript/newres/PatMat.mls | 8 +- .../src/test/mlscript/newres/ReslScratch.mls | 2 +- 18 files changed, 563 insertions(+), 293 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls diff --git a/doc/new-resolution-plan.md b/doc/new-resolution-plan.md index 7a39e7a59e..92a608fe4c 100644 --- a/doc/new-resolution-plan.md +++ b/doc/new-resolution-plan.md @@ -1,5 +1,8 @@ # New resolution: overloads and deferred opens +**Status: all five planned stages complete.** The final review and regression +coverage are recorded below. + Resolution owns member discovery and definition selection. Lowering consumes those decisions; it must not recover missing decisions by inspecting overload sets or partially elaborated definitions. A member overload set, its selected definition, @@ -91,10 +94,9 @@ receiver and capture context; module completion must not change lexical priority constructor-shape cache branch. Listeners are now registered once; cached constructors are reused with assertions checking their definition and base. An existing applied-pattern test now passes without its `:todo` marker. -- Bare constructor names in patterns still use eager classification; `Opens.mls` - records this limitation with a `:fixme` regression for both open forms. Applied - constructor patterns already pass. The stage-4 checkpoint below removes the - lexical shortcut and read-side lowering fallbacks. +- At the stage-3 checkpoint, bare constructor names still used eager classification; + `Opens.mls` recorded this with a `:fixme` regression for both open forms. The + final stage-4 work below removes that limitation and the marker. - Stage 3 validation: `ctest` passed (45 tests), `dtest newres/` passed (26 files), and `hkmc2AllTests/test` passed with the final regression outputs. @@ -118,7 +120,7 @@ receiver and capture context; module completion must not change lexical priority - `Interpretations.mls` covers forward value uses, parenthesized and locally opened class targets, constructor arguments, pattern guards, and type-only references in signatures, aliases, type arguments, and ascriptions. Bare-pattern lookup and - the remaining lowering/lexical shortcuts are still separate follow-up work. + lowering/lexical shortcuts were handled in the subsequent stage-4 checkpoints. - Contextual-interpretation validation: `ctest` passed (45 tests), `dtest newres/` passed (27 files), and the final `hkmc2AllTests/test` passed. Existing runtime outputs are unchanged; reviewed golden updates record the @@ -140,12 +142,65 @@ receiver and capture context; module completion must not change lexical priority - Reviewed the existing independent class storage: name reservation records live IR class symbols, and class and term slots remain separate. No storage change is needed for this checkpoint. -- Remaining stage 4: replace eager bare-pattern classification and the pattern - resolver's single-result field with accumulated candidates, including receiver - ambiguity. Integrate bare, applied, selected, and infix constructors without - changing alias-binding semantics. Keep this separate from the receiver/lowering - checkpoint so those semantic changes have their own regression review. +- The receiver/lowering checkpoint left constructor-pattern resolution for a + separate regression review. That work is discharged below. The agreed alias + rule for new resolution uses capitalization, replacing lookup-dependent binding. - Receiver/lowering validation: `ctest` passed (45 tests), the focused receiver regression passed, and final `hkmc2AllTests/test` passed (including 659 diff tests). Reviewed the four changed ambiguity snapshots and the new regression's runtime outputs. + +## Final stage-4 review + +- Bare, applied, selected, and infix constructor patterns now use one resolver + entry point. It waits for member completion, selects the pattern/class/object + interpretation independently of term overloads, and accumulates candidates. + Full and string-prefix pattern lowering share candidate validation. Lowering + diagnoses multiple targets and distinct wildcard receivers, including + receivers exposing the same member symbol. Missing targets and already reported + errors are handled separately; no candidate is chosen by callback order. +- Bare uppercase names use ordinary contextual lookup. As agreed, `p as name` + binds a lowercase name and `p as Name` matches an uppercase constructor; an + unknown uppercase name is an error. The language reference documents this rule + and applies wildcard precedence to pattern interpretations too. +- Constructor shape propagation handles aliases, chains, and nominal inheritance, + so bindings receive matching scrutinee shapes. Guards and literal tests may + conservatively retain shapes. This does not attempt to implement all unfinished + shape inference for other pattern forms, such as tuple bindings or conjunction + outputs. Constructor calls and explicit `new` now share inherited member lookup, + preserving inherited capture marks; this also discharges three existing + inheritance `:fixme` cases. +- Reentrant member-completion callbacks now see the completed state immediately, + rather than appending to the listener buffer currently being traversed. +- Symbol queries consume recorded new-resolution targets, including captures. + Synthesized runtime constructors carry explicit resolved class symbols, which + lowering now accepts directly. Ambiguous `new` targets produce diagnostics. +- The `selfShapes` cache is keyed only by inner symbol, with assertions that + repeated notifications agree on the definition and extension. +- Backend review: class storage continues to use live IR identities and separate + slots for class/term overloads. The new pattern path retains the selected class + identity through lowering; imported overload patterns exercise the exported + class slots. No backend storage workaround was added. + +| Planned contract | Regression coverage | +| --- | --- | +| Explicit bindings beat wildcard opens across scopes and declaration orders | `Opens.mls` | +| Deferred forward definitions, both module-body forms, captures and imports | `Opens.mls`, `ModuleCaptures.mls`, `ReceiverResolution.mls`, `OverloadedClassImports.mls` | +| Class/function and class/value overloads, storage and live class identities | `OverloadedClasses.mls`, `OverloadedClassValues.mls`, `OverloadedClassImports.mls` | +| Ordinary and synthesized assignments, evaluation order and restoration | `Assignments.mls`, `Opens.mls` | +| Contextual term/class/pattern/type interpretation | `Interpretations.mls`, `ConstructorResolution.mls` | +| Bare/applied/selected/infix patterns, objects, aliases, named patterns and pattern parameters | `ConstructorResolution.mls`, `Opens.mls`, `PatMat.mls` | +| Ambiguity independent of open order, repeated sources and distinct receivers | `ConstructorResolution.mls`, `Opens.mls` | + +## Final validation + +- `ctest`: 45 tests passed. +- `dtest newres/`: 29 tests passed; the final added pattern-parameter regression + also passed its focused run. +- Final `hkmc2AllTests/test`: all suites passed, including 660 diff tests. +- Reviewed the golden changes: bare opens and constructor aliases now succeed; + inherited member regressions now return their expected values; ambiguity and + invalid-pattern cases produce diagnostics. Two JS snapshots omit `safeCall` + now that symbol queries expose the already recorded function targets. +- Legacy-resolution diagnostic spans are unchanged. Implementation, language + reference, regression inputs, and final golden outputs are committed together. diff --git a/doc/reference.md b/doc/reference.md index 19afff3efc..a1b683cfb8 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -946,6 +946,13 @@ if r is { 'x: x } then x ### `as` Pattern Alias +With `newResolution` enabled, capitalization distinguishes binding from matching: +`p as name` binds the result of `p` to the lowercase name, even if that name is +already in scope. `p as Name` matches the result of `p` against the uppercase +constructor name; it does not introduce a binding. Constructor names follow the +usual lookup rules, including selective and wildcard opens. An unknown uppercase +name is an error. + ```mlscript fun map(f) = case Some(x as n) then Some(f(n)) @@ -1355,9 +1362,9 @@ fun example() = ``` When no explicit binding exists, lookup considers the wildcard opens visible at -that occurrence. These rules apply to the name itself, before choosing its term or class -interpretation, so an explicit binding also takes precedence over an overloaded -class or function supplied by a wildcard open. +that occurrence. These rules apply to the name itself, before choosing its term, +class, or pattern interpretation, so an explicit binding also takes precedence +over an overloaded class or function supplied by a wildcard open. If wildcard lookup finds several distinct members with the requested name, the reference is ambiguous; the order of the opens does not choose a winner. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index b28feb58ce..f55690ea5c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -425,6 +425,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): def classOf(trm: Term, nw: Resolvable)(k: Path => Block)(using LoweringCtx): Block = if newResolution then trm.withoutCaptures match + case resolved @ Resolved(_, _: ClassSymbol) => + // Synthesized runtime constructors already carry an explicit class target. + subTerm(resolved)(k) case resl: NewResolvable => resl.resolvedTargets.distinct match case cls :: Nil => @@ -446,8 +449,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): msg"Cannot resolve the class instantiated here" -> trm.toLoc :: Nil, source = Diagnostic.Source.Compilation) compError - case _ => - ??? + case targets => + fail: + ErrorReport(msg"The class instantiated here is ambiguous" -> trm.toLoc :: + targets.map(target => msg"target: ${target.describeKind} '${target.nme}'" -> target.toLoc), + source = Diagnostic.Source.Compilation) case _ => softAssert(nw.isErroneous, "Unexpected class term shape") compError diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index db71cd69f3..15d756a5dd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -2808,7 +2808,7 @@ extends Importer: ds += msg"The upper bound of character ranges must be a single character." -> hi.toLoc if ds.nonEmpty then error(ds.toSeq*) ds.nonEmpty - /** Resolve an identifier. We need to perform a very preliminary check to + /** Legacy pattern lookup. We need to perform a very preliminary check to * determine whether this identifier refers to a pattern, a class, an * object, or creates a new binding. * @@ -2830,6 +2830,11 @@ extends Importer: state.builtinOpsMap.get(id.name) match case S(bi) => S(bi.ref(id)) case N => N + def constructor(target: Term, arguments: Opt[Ls[Pattern]], source: Tree): Pattern.Constructor = + val res = new Constructor(target, arguments) + if newResolution then res.withLocOf(source) + constructorPattern(res) + res /** Elaborate arrow patterns like `p => t`. Meanwhile, report all invalid * variables we found in `p`. */ def arrow(lhs: Tree, rhs: Tree): Ctxl[Pattern] = @@ -2920,12 +2925,7 @@ extends Importer: Composition(op is Keyword.`|`, go(lhs), go(rhs)) // Constructor patterns with pattern arguments and arguments. case App(ctor: Ctor, Tup(argTrees)) => // TODO: rm this weird `: Ctor` guard - val lt = term(ctor, Ptrn) - val rt = argTrees.map(go(_)) - val res = new Constructor(lt, S(rt)) - // if newResolution then listenTerm(lt, shape => patAppShape(shape, rt, res)) - patApp(lt, rt, res) - res + constructor(term(ctor, Ptrn), S(argTrees.map(go(_))), t) // `[p1, p2, ...ps, pn] => term`: All patterns are in the `TyTup`. case (lhs: TyTup) `=>` rhs => arrow(lhs, rhs) // `pattern => term`: Note that `pattern` is wrapped in a `Tup`. @@ -2935,13 +2935,17 @@ extends Importer: case lhs :: Nil => arrow(lhs, rhs) case _ :: _ | Nil => ??? // TODO: this case reached by, eg, `pattern p = () => Unit` case p as q => q match - // `p as id` is elaborated into alias if `id` is not a constructor. + // New resolution distinguishes bindings from constructors by capitalization. + case id: Ident if newResolution => + if id.name.isUncapitalized then go(p) binds id + else Chain(go(p), constructor(term(id, Ptrn), N, id)) + // Legacy resolution classifies the name by eager lookup. case id: Ident => ident(id) match case S(target) if target.symbol.exists(_.isInstanceOf[VarSymbol]) => // If the target is a variable, we should shadow it. This check is // probably insufficient as there are more cases. go(p) binds id - case S(target) => Chain(go(p), Constructor(target, N)) + case S(target) => Chain(go(p), constructor(target, N, id)) case N => go(p) binds id // Fallback to alias. // `p as q` where `q` is not an identifier is elaborated into chain. case _: Tree => Chain(go(p), go(q)) @@ -2972,16 +2976,17 @@ extends Importer: // pattern translation. case OpApp(lhs, Ident("~"), rhs :: Nil) => Pattern.Concatenation(go(lhs), go(rhs)) // Constructor patterns can be written in the infix form. - case OpApp(lhs, op, rhs :: Nil) => Pattern.Constructor(term(op, Ptrn), S(Ls(go(lhs), go(rhs)))) + case OpApp(lhs, op, rhs :: Nil) => constructor(term(op, Ptrn), S(Ls(go(lhs), go(rhs))), t) // Constructor patterns without arguments case id @ Ident(name) if name.isUncapitalized => Variable(id) + case id: Ident if newResolution => constructor(term(id, Ptrn), N, id) case id @ Ident(name) => ident(id) match - case S(target) => Constructor(target, N) + case S(target) => constructor(target, N, id) case N => raise: ErrorReport(msg"Pattern name not found: ${id.name}." -> id.toLoc :: Nil) Pattern.Wildcard() - case sel: (SynthSel | Sel) => Constructor(term(sel, Ptrn), N) + case sel: (SynthSel | Sel) => constructor(term(sel, Ptrn), N, sel) case _: Tree => raise(ErrorReport(msg"Unrecognized pattern (${t.describe})." -> t.toLoc :: Nil)) Pattern.Wildcard() diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index fdf5d86359..e4f01a047c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -90,139 +90,118 @@ class NewResolver: msg"${funSh.describe.capitalize} expected ${ps.length} ${ "argument".pluralized(ps.length)}, but got ${args.length}" -> funSh.toLoc :: Nil) - def patApp(lhs: Term, args: Ls[Pattern], res: Pattern.Constructor): Unit = if newResolution then - listen(lhs, discardMarks = true): sh => - log(s"patApp: lhs = ${lhs.showDbg}, args = ${args.map(_.showDbg)}, res = ${res.showDbg}, sh = ${sh.shwDbg}") - def checkEmpty = () // TODO - sh match - case sh: TermShape => - // zipArgs(sh.unappliedParams.map(_._2), args, N, res.args, res, lhs) - ??? - case sh: SymShape => - // softAssert(res.isErroneous) - val bms = sh.sym - bms.onComplete: () => - bms.asPat match - case S(pat) => - checkEmpty - res.resolvedSym = S(pat) + /** Resolve every constructor pattern through the same symbolic interpretation. + * A class overload takes precedence over its term companion in this context. + * Keep every candidate so lowering can diagnose ambiguity independently of + * the order in which definitions and receiver shapes become available. */ + def constructorPattern(res: Pattern.Constructor): Unit = if newResolution then + val lhs = res.target + def select(sym: DefinitionSymbol[?]): Bool = + lhs.withoutCaptures match + case trm: NewResolvable => + if !trm.resolvedTargets.contains(sym) then trm.resolvedTargets ::= sym + case _ => () + if res.resolvedTargets.contains(sym) then false + else + res.resolvedTargets ::= sym + true + def reject(sh: TermShape): Unit = + res.isErroneous = true + resolError(res, msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> N :: Nil) + def classPattern(cls: ClassLikeDef): Unit = if select(cls.sym) then + val assoc = res.arguments match + case N => Nil + case S(args) => cls.paramsOpt match case N => - val flow = FlowSymbol.app() - fromBMS(bms, flow, sh.markss, sh => - sh match - case sh: TermShape => - // zipArgs(sh.unappliedParams.map(_._2), args, N, res.args, res, lhs) - // sh.unappliedParams - // ???FlowSymbol.app - sh.applicationHead match - case (ds: DefnShape, mss) => - val cls = ds.defn match - case cd: ClassDef => cd - case td: TermDefinition => - td.tsym match - case ccs: ClassCtorSymbol => ccs.associatedCls.defn.get - case _ => ??? - log(s"Pattern's class: $cls") - lhs.withoutCaptures match - case trm: NewResolvable => - trm.resolvedTargets ::= cls.sym - res.resolvedSym = S(cls.sym) - cls.paramsOpt match - case N => - res.isErroneous = true - resolError(res, - msg"${sh.describe.capitalize} does not take pattern arguments." -> sh.toLoc :: Nil) - case S(ps) => - if ps.restParam.nonEmpty then TODO(ps.restParam) - if args.sizeCompare(ps.params) =/= 0 then - res.isErroneous = true - resolError(res, - msg"${sh.describe.capitalize} expected ${ps.params.length} ${ - "pattern argument".pluralized(ps.params.length)}, but got ${args.length}" -> sh.toLoc :: Nil) - val assoc = ps.params.lazyZip(args).map: (p, a) => - log(s"Pattern's param: ${p.showDbg} (${p.fldSym}), arg: ${a.showDbg}") - p.fldSym match - case S(fldSym: BlockMemberSymbol) => - (fldSym, a) - case S(fldSym) => die - case N => ??? - val psh = CtorPatternShape(cls, assoc, res, FlowSymbol.pat()) - if res.shapes.add(psh) then - res.shapeListeners.foreach(listener => listener(psh)) + if args.nonEmpty || cls.isInstanceOf[ClassDef] then + res.isErroneous = true + resolError(res, msg"${cls.describe.capitalize} does not take pattern arguments." -> cls.toLoc :: Nil) + Nil + case S(ps) => + if ps.restParam.nonEmpty then TODO(ps.restParam) + if args.sizeCompare(ps.params) =/= 0 then + res.isErroneous = true + resolError(res, + msg"${cls.describe.capitalize} expected ${ps.params.length} ${ + "pattern argument".pluralized(ps.params.length)}, but got ${args.length}" -> cls.toLoc :: Nil) + ps.params.lazyZip(args).flatMap: (p, a) => + p.fldSym match + case S(fldSym: BlockMemberSymbol) => (fldSym -> a) :: Nil case _ => res.isErroneous = true - resolError(res, - msg"${sh.describe.capitalize} cannot used like an applied pattern." -> sh.toLoc :: Nil) - , lhs, _ => ()) - - def listenPattern(pat: Pattern)(listener: PatternShape => Unit): Unit = - // ??? - log(s"listenPattern: pat = ${pat.showDbg} ${pat.getClass.getSimpleName}") - pat.shapeListeners += listener // FIXME: sus - pat match - case pat: PatternShapeHost => - pat.shapes.foreach(listener) - // pat.shapeListeners += listener - case pat: Pattern.Alias => - // TODO: also handle the alias symbol's shape listeners? - // listenTerm(pat.) - /* - def listen(sh: Shape): Unit = - matchShapePat(sh, pat.pattern) - pat.symbol.shapes.foreach(listen) - pat.symbol.shapeListeners += listen - listenPattern(pat.pattern)(listener) - */ - listener(AliasPatternShape(pat.symbol, pat)) - case Pattern.Literal(_) | Pattern.Tuple(_, _) => // TODO - case Pattern.Wildcard() => - () - // case _ => ??? + resolError(res, msg"Pattern argument requires an accessible constructor field." -> p.toLoc :: Nil) + Nil + if !res.isErroneous then + val psh = CtorPatternShape(cls, assoc, res, FlowSymbol.pat()) + if res.shapes.add(psh) then res.shapeListeners.foreach(_(psh)) + def valuePattern(sh: TermShape): Unit = sh.applicationHead match + case (ds: DefnShape, _) => ds.defn match + case cls: ClassDef => classPattern(cls) + case obj: ModuleOrObjectDef if obj.sym.asObj.isDefined => classPattern(obj) + case td: TermDefinition => td.tsym match + case ctor: ClassCtorSymbol => classPattern(ctor.associatedCls.defn.get) + case _ => reject(sh) + case _ => reject(sh) + case _ => reject(sh) + lhs.withoutCaptures match + case Term.Error() => res.isErroneous = true + case Term.Ref(sym: VarSymbol) if sym.decl.exists(_.isPatternConstructor) => + res.resolvedTargets ::= sym + case Term.SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isPatternConstructor) => + res.resolvedTargets ::= sym + case _ => listen(lhs, discardMarks = true): sh => + sh match + case sh: SymShape => + val bms = sh.sym + bms.onComplete: () => + bms.asPat.orElse(bms.asCls).orElse(bms.asObj) match + case S(sym: PatternSymbol) => select(sym) + case S(sym: (ClassSymbol | ModuleOrObjectSymbol)) => + sym.defn match + case S(cls) => classPattern(cls) + case N => softAssert(false, "Completed pattern member has no definition") + case N => + fromBMS(bms, FlowSymbol.pat(), sh.markss, valuePattern, lhs, _ => ()) + case sh: TermShape => valuePattern(sh) - def matchShapePat(shape: Shape, pattern: Pattern): Unit = + /** Propagate possible values to pattern bindings. Constructor tests filter by + * nominal class; guards and literal tests may conservatively retain shapes. + * Both scrutinee and constructor shapes can arrive after this registration. */ + def matchShapePat(shape: Shape, pattern: Pattern)(matched: Shape => Unit): Unit = pattern match - case al @ Pattern.Alias(pat, id) => - matchShapePat(shape, pat) - log(s"TODO: $id ${al.symbol}") - if al.symbol.shapes.add(shape) then - al.symbol.shapeListeners.foreach(listener => listener(shape)) - // pipeTerm(id, shape) - case Pattern.Wildcard() => - // case _ => ??? + case al @ Pattern.Alias(pat, _) => + matchShapePat(shape, pat): sh => + if al.symbol.shapes.add(sh) then al.symbol.shapeListeners.foreach(_(sh)) + matched(sh) + case Pattern.Wildcard() | Pattern.Literal(_) => matched(shape) + case Pattern.Chain(left, right) => + matchShapePat(shape, left)(sh => matchShapePat(sh, right)(matched)) + case Pattern.Composition(true, left, right) => + matchShapePat(shape, left)(matched) + matchShapePat(shape, right)(matched) + case Pattern.Guarded(pat, _) => matchShapePat(shape, pat)(matched) + case ctor: Pattern.Constructor => + def listenConstructor(psh: PatternShape): Unit = psh match + case CtorPatternShape(cls, fs, _, resSym) => + def check(sh: TermShape): Unit = + if sh.isSaturated && sh.applicationHead._1.extendsCls(cls) then + fs.foreach: (bms, pat) => + sh.getMember(bms.nme) match + case S((sym, marks)) => + val field = symShapes.getOrElseUpdate((sym, resSym, marks), SymShape(sym, resSym, marks)) + matchShapePat(field, pat)(_ => ()) + case N => softAssert(false, "Matched constructor is missing its field") + matched(sh) + shape match + case sh: TermShape => check(sh) + case sh: SymShape => fromBMS(sh.sym, sh.resSym, sh.markss, check, ctor.target, _ => ()) + ctor.shapeListeners += listenConstructor + ctor.shapes.foreach(listenConstructor) + case Pattern.Tuple(_, _) => () // Tuple binding shapes are not inferred yet. + case _ => TODO(pattern) - def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern): Unit = - log(s"matchScrutPat? scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}") - if newResolution then - listenPattern(pattern): psh => - trace(s"matchScrutPat: scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, psh = ${psh.showDbg}"): - listenTerm(scrutinee): sh => - trace(s"matchScrutPat scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, sh = ${sh.shwDbg}"): - if !sh.isSaturated then - ??? - psh match - case AliasPatternShape(sym, pat) => - log(s"matchScrutPat: scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, psh = ${psh.showDbg}") - matchShapePat(sh, pat) - if sym.shapes.add(sh) then - sym.shapeListeners.foreach(listener => listener(sh)) - case CtorPatternShape(cls, fs, src, resSym) => - sh.applicationHead match - case (ds: DefnShape, mss) => - if ds.extendsCls(cls) then - log(s"Subclass: ${ds.defn.sym.showDbg} of ${cls.sym.showDbg}") - fs.foreach: (bms, pat) => - // bms.onComplete: () => - val nme = bms.nme - sh.getMember(nme) match - case S((sym, mss)) => - val sh = symShapes.getOrElseUpdate((bms, resSym, mss), SymShape(bms, resSym, mss)) - // if src.trmHost.shapes.add(sh) then - // src.trmHost.shapeListeners.foreach(listener => listener(sh)) - matchShapePat(sh, pat) - case N => - ??? - else - log(s"Not a subclass: ${ds.defn.sym.showDbg} of ${cls.sym.showDbg}") + def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern): Unit = if newResolution then + listenTerm(scrutinee)(sh => matchShapePat(sh, pattern)(_ => ())) def appShape(lhs: TermShape, args: Term, res: App): Unit = // log(s"appShape? lhs = $lhs, args = $args, res = $res") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index df8951a47c..6bed9c2e8a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -152,7 +152,7 @@ object Pattern: var isErroneous: Bool = false // val resSym: FlowSymbol = FlowSymbol.app - var resolvedSym: Opt[DefinitionSymbol[?]] = N + var resolvedTargets: Ls[DefinitionSymbol[?] | VarSymbol] = Nil /** Get the resolved symbol of the target term. */ def symbol: Opt[Symbol] = self.target.resolvedSym diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala index a6319c3df9..aec784956f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala @@ -28,7 +28,7 @@ sealed abstract class PatternShape: // TODO: not a case class...? case class CtorPatternShape( - cls: ClassDef, + cls: ClassLikeDef, fs: Ls[BlockMemberSymbol -> Pattern], src: Pattern.Constructor, resSym: FlowSymbol, @@ -36,10 +36,3 @@ case class CtorPatternShape( def showDbg(using DebugPrinter): Str = src.showDbg -case class AliasPatternShape( - sym: VarSymbol, - src: Pattern.Alias, -) extends PatternShape: - def showDbg(using DebugPrinter): Str = src.showDbg - - diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index d8884c4709..f220d0592e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -234,21 +234,7 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using else applicationHead match case (ds: DefnShape, mss) => - // TODO: handle `mss` - - // ds.members - // ds.defn match - // case td: TermDefinition => td.sym match - // case cs: ClassCtorSymbol => - // val cd = cs.associatedCls.defn.get - // cd.ext.fold(Map.empty)(_.members) ++ cd.body.members - // case _ => ??? // TODO: add softRequire on ction – should not be possible - // case cd: ClassDef => cd.ext.fold(Map.empty)(_.members) ++ cd.body.members - ds.defn match - case cd: ClassDef => cd.body.members.get(name).map(_ -> mss).orElse: - ds.ext.flatMap(_.getMember(name)).map(_.mapSecond(_ ::: mss)) - case td: TermDefinition => - ds.ext.flatMap(_.getMember(name)).map(_.mapSecond(_ ::: mss)) + ds.getInstanceMember(name).map(_.mapSecond(_ ::: mss)) case _ => N def describe: Str = // s"application of ${receiver.describe}" @@ -260,21 +246,8 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using class NewShape(val receiver: TermShape, val cls: ClassLikeSymbol, clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New)(using DebugPrinter) extends NonMarkedShape: protected def getMemberImpl(name: Str): Opt[MemberInfo] = receiver match - case ds: DefnShape => - ds.defn match - case cd: ClassDef => - cd.body.members.get(name).map(_ -> clsMarks) - case td: TermDefinition => - td.tsym match - case ccs: ClassCtorSymbol => - ccs.associatedCls.defn.getOrElse(die // TODO - ).body.members.get(name).map(_ -> clsMarks) - case _ => - N - case _ => - N - case _ => - N + case ds: DefnShape => ds.getInstanceMember(name).map(_.mapSecond(_ ::: clsMarks)) + case _ => N def describe: Str = // s"instantiation of ${receiver.describe}" s"instance of ${cls.defn.get.describeRef}" @@ -298,14 +271,23 @@ class ThisShape(val defn: Definition) extends NonAppTermShape: // TODO: make it not a TermShape? class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppTermShape: + override def extendsCls(cls: ClassLikeDef): Bool = + (defn is cls) || ext.exists(_.applicationHead._1.extendsCls(cls)) def describe: Str = s"${defn.describe}" protected def getMemberImpl(name: Str): Opt[MemberInfo] = defn.body.members.get(name).map(_ -> Nil).orElse(ext.flatMap(_.getMember(name))) def toLoc: Opt[Loc] = defn.toLoc class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTermShape: + /** Instance lookup is shared by constructor calls and explicit `new`. + * Inherited members retain their marks before the caller adds its captures. */ + def getInstanceMember(name: Str): Opt[MemberInfo] = defn match + case cd: ClassDef => + cd.body.members.get(name).map(_ -> Nil).orElse(ext.flatMap(_.getMember(name))) + case _: TermDefinition => ext.flatMap(_.getMember(name)) + case _ => N override def extendsCls(cls: ClassLikeDef): Bool = - clsDef.contains(cls) || ext.exists(_.extendsCls(cls)) + clsDef.contains(cls) || ext.exists(_.applicationHead._1.extendsCls(cls)) lazy val clsDef = defn match case defn: ClassLikeDef => S(defn) case defn: TermDefinition => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 6d95c45df2..9a0fc77931 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -320,8 +320,11 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[TypeOrTermDef], val nameIsMe private var defnListeners: Buffer[() => Unit] = Buffer.empty def complete(): Unit = if defnListeners isnt null then - defnListeners.foreach(_()) + val listeners = defnListeners + // Completion callbacks may request this member again. Publish completion + // first so reentrant listeners run immediately instead of mutating the queue. defnListeners = null // free memory and prevent further listening + listeners.foreach(_()) /** Called when all the symbols covered by this BMS are present, with their definitions set. */ def onComplete(f: () => Unit): Unit = if defnListeners is null then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 7435672ca1..64305ed21e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -505,6 +505,13 @@ enum Term extends Statement, ShapePublisher: */ def resolvedSym: Opt[Symbol] = expanded match case res: Resolved => S(res.sym) + case SimpleRef(sym) => S(sym) + case Capture(base, _) => base.resolvedSym + case ref: UnresolvedRef if ref.resolvedMembers.distinct.sizeCompare(1) =/= 0 => N + case ref: NewResolvable => + if ref.isErroneous then N else ref.resolvedTargets.distinct match + case sym :: Nil => S(sym) + case _ => N case ref: Ref => ref.symbol case sel: Sel => sel.sym case sel: SynthSel => sel.sym diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala index cdc8dc7736..f962e04ce3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala @@ -602,6 +602,27 @@ class SplitCompiler(using tl: TL)(using State, Ctx, Raise, Config) extends TermS // Here we go! wrapPatternArgumentDefinitions(wrapUnapply(wrapDestruction(wrapExtractionArguments(consequent)))) + /** Validate recorded constructor candidates for both full and prefix matching. */ + private def resolvedConstructor(ctor: Constructor): Opt[Symbol] = + val target = ctor.target.withoutCaptures + val ambiguousReceiver = target match + case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.sizeCompare(1) > 0 + case _ => false + val erroneousTarget = target match + case ref: NewResolvable => ref.isErroneous + case Term.Error() => true + case _ => false + val targets = ctor.resolvedTargets.distinct + if ctor.isErroneous || erroneousTarget then N + else if ambiguousReceiver || targets.sizeCompare(1) > 0 then + error(msg"Constructor pattern is ambiguous." -> ctor.toLoc) + N + else targets match + case sym :: Nil => S(sym) + case _ => + error(msg"Cannot use this ${target.describe} as a pattern." -> ctor.toLoc) + N + /** Report errors when pattern parameters are used incorrectly. */ private def validatePatternParameter[A]( parameterTerm: Term, @@ -652,16 +673,16 @@ class SplitCompiler(using tl: TL)(using State, Ctx, Raise, Config) extends TermS def makeMatchSplit(scrutinee: Scrut, pattern: SP, outputNeeded: Bool): MakeSplit = pattern match case ctor @ Constructor(target, arguments) if newResolution => - ctor.resolvedSym match - case S(classSymbol: ClassSymbol) => - makeMatchClassSplit(scrutinee, target, classSymbol, arguments, outputNeeded) - case S(objectSymbol: ModuleOrObjectSymbol) => - makeMatchObjectSplit(pattern.toLoc, scrutinee, target, objectSymbol, arguments) - case S(patternSymbol: PatternSymbol) => - makeMatchPatternSplit(scrutinee, target, patternSymbol, arguments) - case _ => - error(msg"Cannot use this ${target.describe} as a pattern." -> target.toLoc) - RejectSplit + resolvedConstructor(ctor) match + case S(classSymbol: ClassSymbol) => + makeMatchClassSplit(scrutinee, target, classSymbol, arguments, outputNeeded) + case S(objectSymbol: ModuleOrObjectSymbol) => + makeMatchObjectSplit(pattern.toLoc, scrutinee, target, objectSymbol, arguments) + case S(patternSymbol: PatternSymbol) => + makeMatchPatternSplit(scrutinee, target, patternSymbol, arguments) + case S(symbol: VarSymbol) => + makeMatchPatternParameterSplit(scrutinee, target, symbol, arguments, pattern.toLoc) + case _ => RejectSplit case Constructor(target, arguments) => target.resolvedSym match case S(symbol: VarSymbol) => makeMatchPatternParameterSplit(scrutinee, target, symbol, arguments, pattern.toLoc) @@ -929,42 +950,43 @@ class SplitCompiler(using tl: TL)(using State, Ctx, Raise, Config) extends TermS scrutinee: Scrut, pattern: SP, )(using Raise): MakePrefixSplit = pattern match - case Constructor(target, arguments) => target.resolvedSym match - // The case when the target refers to a pattern parameter. - case S(symbol: VarSymbol) => - makeMatchPrefixPatternParameterSplit(scrutinee, target, symbol, arguments, pattern.toLoc) - case symbolOption => symbolOption.flatMap(_.asClsLike) match - case S(symbol: PatternSymbol) => - makeMatchPrefixPatternSplit(scrutinee, target, symbol, arguments) - // We accept the string class as a valid string pattern as it literally - // means all strings. - case S(symbol: ClassSymbol) if symbol is ctx.builtins.Str => - arguments match - case S(args) if args.nonEmpty => - error( - msg"`${symbol.name}` does not take any arguments." -> target.toLoc, - msg"But the pattern has ${"sub-pattern" countBy args.size}." -> Loc(args) - ) - RejectPrefixSplit - case _ => (makeConsequent, alternative) => - val nonEmptySymbol = TempSymbol(N, erasedType = S(ErasedType.Bool), "nonEmpty") - val nonEmptyTerm = app( - this.lt.safeRef, - tup(fld(int(0)), fld(sel(scrutinee(), "length"))), - "string is not empty" - ) - val outputSymbol = TempSymbol(N, erasedType = S(ErasedType.Str), "stringHead") - val outputTerm = callStringGet(scrutinee(), 0, "head") - val remainsSymbol = TempSymbol(N, erasedType = S(ErasedType.Str), "stringTail") - val remainsTerm = callStringDrop(scrutinee(), 1, "tail") - Split.Let(nonEmptySymbol, nonEmptyTerm, - Branch(nonEmptySymbol.safeRef, - Split.Let(outputSymbol, outputTerm, - Split.Let(remainsSymbol, remainsTerm, - makeConsequent(outputSymbol.toScrut, remainsSymbol.toScrut, SeqMap.empty))) - ) ~: alternative) - case S(_: ModuleOrObjectSymbol) | S(_: ClassSymbol) | N => - RejectPrefixSplit + case ctor @ Constructor(target, arguments) => + (if newResolution then resolvedConstructor(ctor) else target.resolvedSym) match + // The case when the target refers to a pattern parameter. + case S(symbol: VarSymbol) => + makeMatchPrefixPatternParameterSplit(scrutinee, target, symbol, arguments, pattern.toLoc) + case symbolOption => symbolOption.flatMap(_.asClsLike) match + case S(symbol: PatternSymbol) => + makeMatchPrefixPatternSplit(scrutinee, target, symbol, arguments) + // We accept the string class as a valid string pattern as it literally + // means all strings. + case S(symbol: ClassSymbol) if symbol is ctx.builtins.Str => + arguments match + case S(args) if args.nonEmpty => + error( + msg"`${symbol.name}` does not take any arguments." -> target.toLoc, + msg"But the pattern has ${"sub-pattern" countBy args.size}." -> Loc(args) + ) + RejectPrefixSplit + case _ => (makeConsequent, alternative) => + val nonEmptySymbol = TempSymbol(N, erasedType = S(ErasedType.Bool), "nonEmpty") + val nonEmptyTerm = app( + this.lt.safeRef, + tup(fld(int(0)), fld(sel(scrutinee(), "length"))), + "string is not empty" + ) + val outputSymbol = TempSymbol(N, erasedType = S(ErasedType.Str), "stringHead") + val outputTerm = callStringGet(scrutinee(), 0, "head") + val remainsSymbol = TempSymbol(N, erasedType = S(ErasedType.Str), "stringTail") + val remainsTerm = callStringDrop(scrutinee(), 1, "tail") + Split.Let(nonEmptySymbol, nonEmptyTerm, + Branch(nonEmptySymbol.safeRef, + Split.Let(outputSymbol, outputTerm, + Split.Let(remainsSymbol, remainsTerm, + makeConsequent(outputSymbol.toScrut, remainsSymbol.toScrut, SeqMap.empty))) + ) ~: alternative) + case S(_: ModuleOrObjectSymbol) | S(_: ClassSymbol) | N => + RejectPrefixSplit case Composition(true, left, right) => val makeLeft = makeStringPrefixMatchSplit(scrutinee, left) val makeRight = makeStringPrefixMatchSplit(scrutinee, right) diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls new file mode 100644 index 0000000000..5e599368f1 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls @@ -0,0 +1,225 @@ +:. + + +// Constructor lookup waits for forward definitions and ignores term overloads. +fun forward(x) = { + open Constructors + if x is Token then 1 else 0 +} +module Constructors { + class Token + val Token = 41 + class Box(val value) + object Singleton +} + +:expect [1, 0, 41] +[forward(new Constructors.Token), forward(0), Constructors.Token] +//│ = [1, 0, 41] + + +fun named(x) = { + open Constructors {Token} + if x is Token then 1 else 0 +} +fun selected(x) = if x is Constructors.Token then 1 else 0 +fun boxed(x) = { + open Constructors + if x is Box(value) then value else 0 +} +fun singleton(x) = { + open Constructors + if x is Singleton then 1 else 0 +} + +:expect [1, 0, 1, 0, 42, 0, 1, 0] +[named(new Constructors.Token), named(0), selected(new Constructors.Token), selected(0), + boxed(Constructors.Box(42)), boxed(0), singleton(Constructors.Singleton), singleton(0)] +//│ = [1, 0, 1, 0, 42, 0, 1, 0] + + +// Lowercase `as` names bind even when an open contains the same name. +// Uppercase `as` names match constructors, including deferred open candidates. +:expect [1, 0, 43] +fun chained(x) = { + open Constructors + if x is _ as Token then 1 else 0 +} +module Aliases { val saved = 99 } +fun alias(x) = { + open Aliases + if x is Constructors.Box(_) as saved then saved.value else 0 +} +[chained(new Constructors.Token), chained(0), alias(Constructors.Box(43))] +//│ = [1, 0, 43] + + +// A constructor declared later in the same scope is also visible in a pattern. +fun localForward(x) = if x is Later then 1 else 0 +class Later +fun Later() = new Later + +:expect [1, 0] +[localForward(Later()), localForward(0)] +//│ = [1, 0] + + +// An explicit binding wins over every wildcard candidate. +module Other { class Token } +fun explicit(x) = { + open Other + open Constructors {Token} + if x is Token then 1 else 0 +} + +:expect [1, 0] +[explicit(new Constructors.Token), explicit(new Other.Token)] +//│ = [1, 0] + + +:e +fun ambiguous(x) = { + open Constructors + open Other + if x is Token then 1 else 0 +} +ambiguous(new Constructors.Token) +//│ ╔══[COMPILATION ERROR] Constructor pattern is ambiguous. +//│ ║ l.84: if x is Token then 1 else 0 +//│ ╙── ^^^^^ +//│ = 0 + + +:e +fun ambiguousReversed(x) = { + open Other + open Constructors + if x is Token then 1 else 0 +} +ambiguousReversed(new Constructors.Token) +//│ ╔══[COMPILATION ERROR] Constructor pattern is ambiguous. +//│ ║ l.97: if x is Token then 1 else 0 +//│ ╙── ^^^^^ +//│ = 0 + + +// Unknown uppercase names in `as` never silently become bindings. +:e +fun unknown(x) = if x is _ as MissingConstructor then 1 else 0 +unknown(0) +//│ ╔══[COMPILATION ERROR] Name not found: MissingConstructor +//│ ║ l.108: fun unknown(x) = if x is _ as MissingConstructor then 1 else 0 +//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ = 0 + + +:expect [44, 45] +module Pairs { class (><) Pair(val first, val second) } +fun infix(x) = { + open Pairs + if x is a >< b then a else 0 +} +fun appliedSelection(x) = if x is Pairs.Pair(a, b) then b else 0 +[infix(Pairs.Pair(44, 45)), appliedSelection(Pairs.Pair(44, 45))] +//│ = [44, 45] + + +// Repeating one source is unambiguous. +:expect 1 +fun repeated(x) = { + open Constructors + open Constructors + if x is Token then 1 else 0 +} +repeated(new Constructors.Token) +//│ = 1 + + +// Distinct receivers can expose the same member symbol without denoting the +// same constructor value. Candidate identity includes the receiver. +fun factory() = { + module Local { class Token } + Local +} +val first = factory() +val second = factory() +//│ first = class Local { Token: class Token } +//│ second = class Local { Token: class Token } + +:e +fun sameMember(x) = { + open first + open second + if x is Token then 1 else 0 +} +sameMember(new first.Token) +//│ ╔══[COMPILATION ERROR] Constructor pattern is ambiguous. +//│ ║ l.153: if x is Token then 1 else 0 +//│ ╙── ^^^^^ +//│ = 0 + + +// A non-pattern value is diagnosed rather than treated as a constructor. +:e +val NotAPattern = 1 +fun invalid(x) = if x is NotAPattern then 1 else 0 +invalid(0) +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.165: fun invalid(x) = if x is NotAPattern then 1 else 0 +//│ ║ ^^^^^^^^^^^ +//│ ╙── Integer literal cannot be used as a constructor pattern. +//│ = 0 +//│ NotAPattern = 1 + + +// Pattern definitions use the same deferred references as direct matches. +:expect [1, 0] +module PatternNames { + open Constructors + pattern Named = Token +} +fun namedDefinition(x) = { + open PatternNames + if x is Named then 1 else 0 +} +[namedDefinition(new Constructors.Token), namedDefinition(0)] +//│ = [1, 0] + + +// Nominal filtering follows inheritance before propagating an alias's shape. +:expect [46, 0] +class Base { val field = 46 } +class Derived extends Base +fun inherited(x) = if x is Base as instance then instance.field else 0 +[inherited(new Derived), inherited(0)] +//│ = [46, 0] + + +:e +fun ambiguousNew() = { + open Constructors + open Other + new Token +} +ambiguousNew() +//│ ╔══[COMPILATION ERROR] The class instantiated here is ambiguous +//│ ║ l.202: new Token +//│ ║ ^^^^^ +//│ ╟── target: class 'Token' +//│ ║ l.10: class Token +//│ ║ ^^^^^ +//│ ╟── target: class 'Token' +//│ ║ l.68: module Other { class Token } +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +// Pattern parameters preserve the caller's selected constructor interpretation. +:expect [1, 0] +pattern Identity(pattern P) = P +fun parameterized(x) = { + open Constructors + if x is Identity(Token) then 1 else 0 +} +[parameterized(new Constructors.Token), parameterized(0)] +//│ = [1, 0] diff --git a/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls b/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls index adf49bce46..678c2c05f2 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls @@ -68,30 +68,20 @@ let b = new Bar(123) :e b.y -:fixme +:expect 124 b.x -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.72: b.x -//│ ║ ^^^ -//│ ╟── Instance of class 'Bar' does not contain member 'x' -//│ ║ l.64: let b = new Bar(123) -//│ ╙── ^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = 124 b.z //│ = 125 -:fixme +:expect [44, 43] class Baz extends Bar(42) { val a = [this.z, this.x] } -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.87: val a = [this.z, this.x] -//│ ║ ^^^^^^ -//│ ╟── Class definition 'Baz' does not contain member 'x' -//│ ║ l.87: val a = [this.z, this.x] -//│ ╙── ^^^^^^^^^^^^^^^^ +(new Baz).a +//│ = [44, 43] @@ -106,8 +96,8 @@ module Outer { :e object Derived extends Outer.Inner1 //│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported -//│ ║ l.107: object Derived extends Outer.Inner1 -//│ ╙── ^^^^^^^^^^^^ +//│ ║ l.97: object Derived extends Outer.Inner1 +//│ ╙── ^^^^^^^^^^^^ :sr :sir @@ -121,10 +111,10 @@ object Derived extends Outer.Inner1(11) :fixme Derived.x //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.122: Derived.x +//│ ║ l.112: Derived.x //│ ║ ^^^^^^^^^ //│ ╟── Object 'Derived' does not contain member 'x' -//│ ║ l.114: object Derived extends Outer.Inner1(11) +//│ ║ l.104: object Derived extends Outer.Inner1(11) //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -133,7 +123,7 @@ Derived."x" :e object Derived extends Outer.Inner2 //│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported -//│ ║ l.134: object Derived extends Outer.Inner2 +//│ ║ l.124: object Derived extends Outer.Inner2 //│ ╙── ^^^^^^^^^^^^ :sr diff --git a/hkmc2/shared/src/test/mlscript/newres/Opens.mls b/hkmc2/shared/src/test/mlscript/newres/Opens.mls index eefb8872e3..8c4a5f3b8c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Opens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Opens.mls @@ -334,8 +334,7 @@ callable() //│ = [22, 22] -// Stage 4: bare pattern names still require deferred constructor-vs-binding classification. -:fixme +// Bare pattern names resolve through both forms of open. :expect [1, 0, 1, 0] module BarePatterns { class Token } fun bareWildcard(x) = { @@ -348,11 +347,4 @@ fun bareNamed(x) = { } [bareWildcard(new BarePatterns.Token), bareWildcard(0), bareNamed(new BarePatterns.Token), bareNamed(0)] -//│ ╔══[COMPILATION ERROR] Pattern name not found: Token. -//│ ║ l.343: if x is Token then 1 else 0 -//│ ╙── ^^^^^ -//│ ╔══[COMPILATION ERROR] Pattern name not found: Token. -//│ ║ l.347: if x is Token then 1 else 0 -//│ ╙── ^^^^^ -//│ ═══[RUNTIME ERROR] Expected: '[1, 0, 1, 0]', got: '[1, 1, 1, 1]' -//│ = [1, 1, 1, 1] +//│ = [1, 0, 1, 0] diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls index 2e967d1b6b..5e022e7a58 100644 --- a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls @@ -12,7 +12,7 @@ OverloadedClassLibrary.fresh().answer() //│ import { "mlscript.class:_$_modulePrivate_$_Hidden" as Hidden1 } from "./../../mlscript-compile/OverloadedClassLibrary.mjs"; //│ let inlinedVal; //│ inlinedVal = globalThis.Object.freeze(new Hidden1()); -//│ return runtime.safeCall(inlinedVal.answer()) +//│ return inlinedVal.answer() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 51 @@ -55,3 +55,15 @@ OverloadedDefault :expect 54 (new OverloadedDefault).answer() //│ = 54 + + +// Imported overloads retain their class interpretation in bare patterns. +:expect [1, 0, 1, 0] +fun importedPattern(x) = { + open OverloadedClassLibrary + if x is Item then 1 else 0 +} +fun namedPattern(x) = if x is Item then 1 else 0 +[importedPattern(new OverloadedClassLibrary.Item), importedPattern(0), + namedPattern(new Item), namedPattern(0)] +//│ = [1, 0, 1, 0] diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls index 4cca07e0ca..6755e0d064 100644 --- a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls @@ -115,18 +115,10 @@ val Parent = false class Child extends Parent //│ Parent = false -// FIXME: NewShape does not yet look up inherited members. -:fixme +// Inherited members retain their class interpretation despite the term overload. :expect 47 (new Child).answer() -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.121: (new Child).answer() -//│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ╟── Instance of class 'Child' does not contain member 'answer' -//│ ║ l.121: (new Child).answer() -//│ ╙── ^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '47', got: 'undefined' +//│ = 47 class Factory { @@ -186,20 +178,20 @@ MutableMembers.Item class Parameterized() fun Parameterized() = 0 //│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Parameterized' -//│ ║ l.187: fun Parameterized() = 0 +//│ ║ l.179: fun Parameterized() = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── with class of the same name -//│ ║ l.186: class Parameterized() +//│ ║ l.178: class Parameterized() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ :e class ParameterizedValue(x) val ParameterizedValue = 0 //│ ╔══[COMPILATION ERROR] Illegal overloading of value 'ParameterizedValue' -//│ ║ l.197: val ParameterizedValue = 0 +//│ ║ l.189: val ParameterizedValue = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── with class of the same name -//│ ║ l.196: class ParameterizedValue(x) +//│ ║ l.188: class ParameterizedValue(x) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ParameterizedValue = 0 @@ -209,10 +201,10 @@ class Duplicate fun Duplicate() = 0 val Duplicate = 1 //│ ╔══[COMPILATION ERROR] Illegal overloading of value 'Duplicate' -//│ ║ l.210: val Duplicate = 1 +//│ ║ l.202: val Duplicate = 1 //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── with function of the same name -//│ ║ l.209: fun Duplicate() = 0 +//│ ║ l.201: fun Duplicate() = 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls index 8104c19a02..75faf0187c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls @@ -130,16 +130,16 @@ x.b //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -:todo +:re if x is Baz as y then y.b -//│ /!!!\ Uncaught error: scala.MatchError: Constructor(MemberRef(member:Baz),None) (of class hkmc2.semantics.Pattern$Constructor) +//│ ═══[RUNTIME ERROR] Error: match error -:todo +:expect 123 if x is Baz as y then y.b Bar as z then z.b -//│ /!!!\ Uncaught error: scala.MatchError: Constructor(MemberRef(member:Baz),None) (of class hkmc2.semantics.Pattern$Constructor) +//│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 40ecf8c819..9fa1021230 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -122,7 +122,7 @@ class Bar(a) with //│ constructor(a) { //│ this.#a = a; //│ this.#x = this.#a; -//│ runtime.safeCall(Predef.print(this.#x)); +//│ Predef.print(this.#x); //│ } //│ #a; //│ #x; From fc7eacac9205d52af5264b34ff5e36b4cc70a559 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 22 Sep 2026 14:50:44 +0800 Subject: [PATCH 020/228] Minor --- hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala | 7 ------- 1 file changed, 7 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index 6bed9c2e8a..cc4ca9b49c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -153,13 +153,6 @@ object Pattern: var isErroneous: Bool = false // val resSym: FlowSymbol = FlowSymbol.app var resolvedTargets: Ls[DefinitionSymbol[?] | VarSymbol] = Nil - - /** Get the resolved symbol of the target term. */ - def symbol: Opt[Symbol] = self.target.resolvedSym - - /** Expect the `symbol` to be set. */ - def symbol_! : Symbol = symbol.getOrElse: - lastWords(s"target term `${self.target}` does not resolve to a symbol") /** Add a mutable field to the `Alias` pattern to store the symbol for the * variable. Note that NOT every `Alias` pattern has a symbol. */ From fa178bc05a7f4be4c1e8e32e298ba9796f5ee683 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 22 Sep 2026 14:52:58 +0800 Subject: [PATCH 021/228] WIP --- .../newres/wasm/WasmMethodResolution.mls | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls new file mode 100644 index 0000000000..ad8384780c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls @@ -0,0 +1,24 @@ +:.. + + +:global +:wasm + + +:fixme +class A(val x) with + fun get() = x +let a = A(1) +a.A#get() + A(2).get() + a.get() +//│ ╔══[COMPILATION ERROR] Identifier `A` does not name a known class symbol. +//│ ║ l.12: a.A#get() + A(2).get() + a.get() +//│ ╙── ^^ +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'wasm' +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'wasm' +//│ ═══[RUNTIME ERROR] ReferenceError: wasm is not defined +//│ a = A(1) +//│ ╔══[COMPILATION ERROR] WatBuilder::result for field selection without a resolved symbol is not implemented (field `get`). Use `_.[_]` for index-based accesses. +//│ ║ l.12: a.A#get() + A(2).get() + a.get() +//│ ╙── ^^^ + + From 6c47ff062715d79e5a760e34813e8b651f0faaee Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 15:03:06 +0800 Subject: [PATCH 022/228] Isolate new-resolution WASM tests from inherited JS execution --- doc/new-resolution-wasm.md | 73 +++++++++++++++++++ .../test/mlscript/newres/wasm/WasmBasics.mls | 18 +++++ .../newres/wasm/WasmMethodResolution.mls | 9 +-- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 2 +- 4 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 doc/new-resolution-wasm.md create mode 100644 hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls diff --git a/doc/new-resolution-wasm.md b/doc/new-resolution-wasm.md new file mode 100644 index 0000000000..251702ed56 --- /dev/null +++ b/doc/new-resolution-wasm.md @@ -0,0 +1,73 @@ +# New-resolution WASM investigation + +## Findings and immediate fix + +WASM arithmetic and ordinary method calls work with new resolution. The +`WasmBasics.mls` regression produces 3 for arithmetic and 4 for three method +calls in the same block. + +The new-resolution tests inherit `:js` from the root test configuration. With +`:wasm`, lowering emits WASM-specific intrinsic references, but the harness also +tries to execute that same IR as JavaScript. This causes the missing `wasm` +binding and subsequent JavaScript ReferenceError; the WASM arithmetic itself +still succeeds. Make `:js` a flag supporting `:!js`, and use that override in +these WASM tests while retaining the inherited language configuration. + +The remaining failure in `WasmMethodResolution.mls` is explicit projection +`a.A#get()`. Its elaboration still uses `c.symbol.flatMap(_.asCls)` and syntax +member lookup, bypassing the new resolver. `Class::member` has the same problem. +This is shared frontend logic, not a WASM-specific class lookup failure. +The projection regression remains marked `:fixme` until the design below is +implemented; suppressing JavaScript execution does not fix that failure. + +Method calls across separate REPL blocks are being handled on another branch +and are outside this work. + +## Proposed projection resolution + +1. Give explicit projections a new-resolution representation that records the + class expression, instance receiver, member name, and resolved candidates. + Route both `instance.Class#member` and `Class::member` through one resolver + operation. The latter can keep its existing lambda elaboration, using the + same projection node in the body. +2. Elaborate the class expression with `Clss` and the instance with `Trm`. + Register shape listeners in the normal elaboration walk. On a symbol shape, + wait for definition completion, select its class interpretation independently + of any term overload, then use the completed class/extension shapes for + instance-member lookup. Share the relevant class-interpretation logic with + `resolveNew` rather than duplicating eager symbol queries. Preserve capture + marks through inherited member lookup and publish the member's shapes for + calls and subsequent selections. +3. Retain class and wildcard-receiver identity alongside concrete selected + members. Two candidate classes may expose the same inherited member, so + deduplicating only by the final member would hide an ambiguous class lookup. + Keep all candidates; callback ordering must not select a winner. Diagnose + invalid classes and missing members in resolution, and ambiguity in lowering, + consistently with other new-resolution references. +4. Lower reads, calls, and assignments from the recorded concrete targets only. + In particular, the current application case for bare `SelProj` emits + `Select(...)(N)`, relying on legacy resolution to wrap the node in `Resolved`. + The new path must carry the selected definition into the IR for all uses; + neither lowering nor the WASM backend should recover it by name. Preserve + single evaluation of the instance and the existing projection semantics. +5. Cover forward class definitions, class/term overloads, aliases, wildcard + ambiguity (including shared inherited members), inherited fields/methods, + captures, missing members, and projection writes. Run frontend/JS coverage + and same-block WASM coverage separately. Remove the existing `:fixme` once + its WASM result is 4. + +## Optional harness follow-up + +If tests need both JS and WASM execution, lower separately for each target and +keep their backend scopes and session state separate. Respect the MIR rule that +each definition symbol owns at most one current IR definition: use isolated +compilation state or refresh symbols rather than retaining two IRs that mutate +the same symbols' `irDefn` fields. Binding the WASM support module in the JS +scope is insufficient: that module supplies compilation/instantiation helpers, +not JavaScript implementations of all WASM intrinsics. + +## Validation + +- `ctest`: passed. +- `dtest newres/wasm/`: both tests passed; explicit projection remains `:fixme`. +- `hkmc2AllTests/test`: passed, including all 662 diff tests. diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls new file mode 100644 index 0000000000..bf031622ef --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls @@ -0,0 +1,18 @@ +:.. + +:global +:wasm +:!js + +// WASM lowering emits target-specific intrinsics; do not execute its IR as JS. +:expect 3 +1 + 2 +//│ Wasm result: +//│ = 3 + +:expect 4 +class A(val x) { fun get() = x } +let a = A(1) +a.get() + A(2).get() + a.get() +//│ Wasm result: +//│ = 4 diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls index ad8384780c..40c2ca4247 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls @@ -3,6 +3,7 @@ :global :wasm +:!js :fixme @@ -11,14 +12,10 @@ class A(val x) with let a = A(1) a.A#get() + A(2).get() + a.get() //│ ╔══[COMPILATION ERROR] Identifier `A` does not name a known class symbol. -//│ ║ l.12: a.A#get() + A(2).get() + a.get() +//│ ║ l.13: a.A#get() + A(2).get() + a.get() //│ ╙── ^^ -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'wasm' -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'wasm' -//│ ═══[RUNTIME ERROR] ReferenceError: wasm is not defined -//│ a = A(1) //│ ╔══[COMPILATION ERROR] WatBuilder::result for field selection without a resolved symbol is not implemented (field `get`). Use `_.[_]` for index-based accesses. -//│ ║ l.12: a.A#get() + A(2).get() + a.get() +//│ ║ l.13: a.A#get() + A(2).get() + a.get() //│ ╙── ^^^ diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index a100cce292..cc70b4a6a6 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -20,7 +20,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val debugLowering = NullaryCommand("dl") val noCodeGen = NullaryCommand("noCodeGen") - val js = NullaryCommand("js") + val js = FlagCommand(false, "js") val showSanitizedJS = NullaryCommand("ssjs") val showJS = NullaryCommand("sjs") val showRepl = NullaryCommand("showRepl") From 8aa21439b07274f2904c268e9490f05ae32b5cc4 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 15:06:38 +0800 Subject: [PATCH 023/228] Recognize disabled flags in imported test directives --- doc/new-resolution-wasm.md | 8 +++++--- hkmc2/shared/src/test/mlscript/newres/wasm/.mls | 2 ++ hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls | 3 +-- .../test/mlscript/newres/wasm/WasmMethodResolution.mls | 7 +++---- hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala | 4 ++-- 5 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/wasm/.mls diff --git a/doc/new-resolution-wasm.md b/doc/new-resolution-wasm.md index 251702ed56..b1592ed45d 100644 --- a/doc/new-resolution-wasm.md +++ b/doc/new-resolution-wasm.md @@ -11,7 +11,9 @@ The new-resolution tests inherit `:js` from the root test configuration. With tries to execute that same IR as JavaScript. This causes the missing `wasm` binding and subsequent JavaScript ReferenceError; the WASM arithmetic itself still succeeds. Make `:js` a flag supporting `:!js`, and use that override in -these WASM tests while retaining the inherited language configuration. +the WASM directory's `.mls` while retaining the inherited language configuration. +Imported directives recognize the lexer's `:!` token as well as `:`, so disabled +flags work in directory configuration files too. The remaining failure in `WasmMethodResolution.mls` is explicit projection `a.A#get()`. Its elaboration still uses `c.symbol.flatMap(_.asCls)` and syntax @@ -69,5 +71,5 @@ not JavaScript implementations of all WASM intrinsics. ## Validation - `ctest`: passed. -- `dtest newres/wasm/`: both tests passed; explicit projection remains `:fixme`. -- `hkmc2AllTests/test`: passed, including all 662 diff tests. +- `dtest newres/wasm/`: all three tests passed; explicit projection remains `:fixme`. +- `hkmc2AllTests/test`: passed, including all 663 diff tests. diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/.mls new file mode 100644 index 0000000000..d83e1a1f31 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/.mls @@ -0,0 +1,2 @@ +:.. +:!js diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls index bf031622ef..14e7b4d6a8 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls @@ -1,8 +1,7 @@ -:.. +:. :global :wasm -:!js // WASM lowering emits target-specific intrinsics; do not execute its IR as JS. :expect 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls index 40c2ca4247..1cd971fd04 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls @@ -1,9 +1,8 @@ -:.. +:. :global :wasm -:!js :fixme @@ -12,10 +11,10 @@ class A(val x) with let a = A(1) a.A#get() + A(2).get() + a.get() //│ ╔══[COMPILATION ERROR] Identifier `A` does not name a known class symbol. -//│ ║ l.13: a.A#get() + A(2).get() + a.get() +//│ ║ l.12: a.A#get() + A(2).get() + a.get() //│ ╙── ^^ //│ ╔══[COMPILATION ERROR] WatBuilder::result for field selection without a resolved symbol is not implemented (field `get`). Use `_.[_]` for index-based accesses. -//│ ║ l.13: a.A#get() + A(2).get() + a.get() +//│ ║ l.12: a.A#get() + A(2).get() + a.get() //│ ╙── ^^^ diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 96fcec3857..5ba7129745 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -362,7 +362,7 @@ abstract class MLsDiffMaker extends DiffMaker: case (syntax.IDENT(":..", true), _) :: rest => doImportUp(file.up) dropCrap(rest.dropWhile(_._1 isnt syntax.NEWLINE).drop(1)) - case (syntax.IDENT(":", true), _) :: (syntax.IDENT(nme, false), _) :: rest => + case (syntax.IDENT(prefix @ (":" | ":!"), true), _) :: (syntax.IDENT(nme, false), _) :: rest => if includeDirectives then val ln = rest.takeWhile(_._1 isnt syntax.NEWLINE) def render(ts: Ls[syntax.Stroken -> Loc]): Str = ts match @@ -373,7 +373,7 @@ abstract class MLsDiffMaker extends DiffMaker: case _ => TODO(st) str + render(rest) case Nil => "" - processLines(s":$nme ${render(ln)}" :: Nil, reprintCommands = false) + processLines(s"$prefix$nme ${render(ln)}" :: Nil, reprintCommands = false) dropCrap(rest.dropWhile(_._1 isnt syntax.NEWLINE).drop(1)) case _ => ts From f7acacd3241bc097c5050cde74ce7465e1b1b65d Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 22 Sep 2026 15:07:22 +0800 Subject: [PATCH 024/228] Minor --- .../src/test/mlscript/newres/wasm/WasmBasics.mls | 8 ++------ .../mlscript/newres/wasm/WasmMethodResolution.mls | 14 +++++++++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls index 14e7b4d6a8..721ca87d6e 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls @@ -3,15 +3,11 @@ :global :wasm + // WASM lowering emits target-specific intrinsics; do not execute its IR as JS. :expect 3 1 + 2 //│ Wasm result: //│ = 3 -:expect 4 -class A(val x) { fun get() = x } -let a = A(1) -a.get() + A(2).get() + a.get() -//│ Wasm result: -//│ = 4 + diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls index 1cd971fd04..eec1eefab6 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls @@ -5,16 +5,24 @@ :wasm +:expect 3 +class A(val x) { fun get() = x } +let a = A(1) +a.get() + A(2).get() +//│ Wasm result: +//│ = 3 + + :fixme class A(val x) with fun get() = x let a = A(1) -a.A#get() + A(2).get() + a.get() +a.A#get() //│ ╔══[COMPILATION ERROR] Identifier `A` does not name a known class symbol. -//│ ║ l.12: a.A#get() + A(2).get() + a.get() +//│ ║ l.20: a.A#get() //│ ╙── ^^ //│ ╔══[COMPILATION ERROR] WatBuilder::result for field selection without a resolved symbol is not implemented (field `get`). Use `_.[_]` for index-based accesses. -//│ ║ l.12: a.A#get() + A(2).get() + a.get() +//│ ║ l.20: a.A#get() //│ ╙── ^^^ From 20905f947fcbf2e55f07f33d636a97b67a6642fd Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 15:27:01 +0800 Subject: [PATCH 025/228] Resolve explicit class projections through deferred shapes --- doc/new-resolution-wasm.md | 110 +++++------ .../main/scala/hkmc2/codegen/Lowering.scala | 52 ++++-- .../scala/hkmc2/semantics/Elaborator.scala | 37 ++-- .../scala/hkmc2/semantics/NewResolver.scala | 151 +++++++++------ .../src/main/scala/hkmc2/semantics/Term.scala | 21 ++- .../test/mlscript/newres/CallSiteShapes.mls | 42 +++++ .../src/test/mlscript/newres/Functions.mls | 2 +- .../src/test/mlscript/newres/Projections.mls | 172 ++++++++++++++++++ .../mlscript/newres/wasm/BackendFlags.mls | 18 ++ .../newres/wasm/WasmMethodResolution.mls | 25 ++- .../src/test/scala/hkmc2/WasmDiffMaker.scala | 6 + 11 files changed, 481 insertions(+), 155 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/Projections.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/wasm/BackendFlags.mls diff --git a/doc/new-resolution-wasm.md b/doc/new-resolution-wasm.md index b1592ed45d..2f301e207d 100644 --- a/doc/new-resolution-wasm.md +++ b/doc/new-resolution-wasm.md @@ -1,63 +1,64 @@ # New-resolution WASM investigation -## Findings and immediate fix - -WASM arithmetic and ordinary method calls work with new resolution. The -`WasmBasics.mls` regression produces 3 for arithmetic and 4 for three method -calls in the same block. +## Implemented fixes The new-resolution tests inherit `:js` from the root test configuration. With `:wasm`, lowering emits WASM-specific intrinsic references, but the harness also -tries to execute that same IR as JavaScript. This causes the missing `wasm` -binding and subsequent JavaScript ReferenceError; the WASM arithmetic itself -still succeeds. Make `:js` a flag supporting `:!js`, and use that override in -the WASM directory's `.mls` while retaining the inherited language configuration. -Imported directives recognize the lexer's `:!` token as well as `:`, so disabled -flags work in directory configuration files too. - -The remaining failure in `WasmMethodResolution.mls` is explicit projection -`a.A#get()`. Its elaboration still uses `c.symbol.flatMap(_.asCls)` and syntax -member lookup, bypassing the new resolver. `Class::member` has the same problem. -This is shared frontend logic, not a WASM-specific class lookup failure. -The projection regression remains marked `:fixme` until the design below is -implemented; suppressing JavaScript execution does not fix that failure. +tries to execute that same IR as JavaScript. The WASM directory's `.mls` now +uses `:!js` while retaining the inherited language configuration. Imported +directives recognize the lexer's `:!` token as well as `:`. Compiling a nonempty +block with both backends enabled now emits a warning. + +Explicit `instance.Class#member` and `Class::member` now share `NewSel` with an +optional class expression. Elaborating a projection registers class-shape +listeners; symbolic class lookup waits for definition completion and chooses the +class interpretation independently of term overloads. Class aliases can supply +constructor shapes. Applied instances are rejected as projection classes. +Completed-symbol class interpretation is shared with explicit `new`, whose +accepted class-expression forms remain unchanged. + +Instance-member lookup uses completed class and extension shapes, retaining +capture marks for inherited members and publishing the selected member's shape. +Recorded projection candidates retain class identity and capture marks, and +ambiguity checks also account for distinct wildcard receivers. Different classes +inheriting the same member do not become an unambiguous projection. Repeated +notifications and repeated opens of the same receiver are deduplicated. + +Lowering validates recorded candidates and uses the ordinary selection paths +for reads, calls, and assignments. It neither queries syntax members nor +selects definitions from an overload set. Symbol queries also reject ambiguous +projection classes. The instance is evaluated once; assignment coverage checks +this with a receiver that increments a counter. + +`Class::member` keeps its lambda elaboration. Its forwarded argument tuple is +resolved through shape listeners, including when arguments arrive after the +method shape. Argument extraction retains the tuple's capture marks, and rest +parameters publish and cache their tuple shapes, including empty tuples. + +## Coverage and remaining limitations + +`Projections.mls` covers both syntaxes, forward class definitions, class/term +overloads, aliases, qualified names, inherited methods, fields and assignments, +captured classes, invalid classes, missing members, distinct wildcard receivers, +shared inherited members, repeated opens, and forward wildcard sources. + +`WasmMethodResolution.mls` now runs direct projections without `:fixme`, including +mixed projected and ordinary method calls. `BackendFlags.mls` checks the warning +and disabling JS through the directory configuration. + +Two independent limitations remain explicit regressions: + +- WASM cannot compile the anonymous function generated by `Class::member` yet. + The final block in `WasmMethodResolution.mls` retains `:fixme` for the backend's + anonymous-function/static-call diagnostics; member resolution succeeds. +- Call-site shape correlation is incomplete for nested forwarding calls. + `CallSiteShapes.mls` reproduces the ambiguity both with ordinary functions and + with a projected function. These cases remain `:fixme`; the resolver does not + choose an arbitrary member to suppress the ambiguity. Method calls across separate REPL blocks are being handled on another branch and are outside this work. -## Proposed projection resolution - -1. Give explicit projections a new-resolution representation that records the - class expression, instance receiver, member name, and resolved candidates. - Route both `instance.Class#member` and `Class::member` through one resolver - operation. The latter can keep its existing lambda elaboration, using the - same projection node in the body. -2. Elaborate the class expression with `Clss` and the instance with `Trm`. - Register shape listeners in the normal elaboration walk. On a symbol shape, - wait for definition completion, select its class interpretation independently - of any term overload, then use the completed class/extension shapes for - instance-member lookup. Share the relevant class-interpretation logic with - `resolveNew` rather than duplicating eager symbol queries. Preserve capture - marks through inherited member lookup and publish the member's shapes for - calls and subsequent selections. -3. Retain class and wildcard-receiver identity alongside concrete selected - members. Two candidate classes may expose the same inherited member, so - deduplicating only by the final member would hide an ambiguous class lookup. - Keep all candidates; callback ordering must not select a winner. Diagnose - invalid classes and missing members in resolution, and ambiguity in lowering, - consistently with other new-resolution references. -4. Lower reads, calls, and assignments from the recorded concrete targets only. - In particular, the current application case for bare `SelProj` emits - `Select(...)(N)`, relying on legacy resolution to wrap the node in `Resolved`. - The new path must carry the selected definition into the IR for all uses; - neither lowering nor the WASM backend should recover it by name. Preserve - single evaluation of the instance and the existing projection semantics. -5. Cover forward class definitions, class/term overloads, aliases, wildcard - ambiguity (including shared inherited members), inherited fields/methods, - captures, missing members, and projection writes. Run frontend/JS coverage - and same-block WASM coverage separately. Remove the existing `:fixme` once - its WASM result is 4. - ## Optional harness follow-up If tests need both JS and WASM execution, lower separately for each target and @@ -70,6 +71,7 @@ not JavaScript implementations of all WASM intrinsics. ## Validation -- `ctest`: passed. -- `dtest newres/wasm/`: all three tests passed; explicit projection remains `:fixme`. -- `hkmc2AllTests/test`: passed, including all 663 diff tests. +- `ctest`: all 45 tests passed. +- `dtest newres/`: all 35 tests passed, with the explicit limitations above marked `:fixme`. +- `hkmc2AllTests/test`: passed, including all 666 diff tests. +- Reviewed golden changes and checked the diff for whitespace errors. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index f55690ea5c..9e2cfebf2b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -669,6 +669,25 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): candidates.map((prefix, member) => msg"candidate from ${prefix.showDbg}: ${member.describe}" -> member.toLoc), source = Diagnostic.Source.Compilation) + /** A projection must identify its class as well as its member: different + * classes can inherit the same definition, and wildcard receivers can differ. */ + private def checkProjection(sel: NewSel): Bool = sel.cls match + case N => true + case S(cls) => + if sel.isErroneous then false + else if sel.hasAmbiguousClass then + raise: + ErrorReport(msg"The projection class is ambiguous" -> cls.toLoc :: + sel.resolvedClasses.map((sym, _) => msg"class: '${sym.nme}'" -> sym.toLoc), + source = Diagnostic.Source.Compilation) + false + else if sel.resolvedClasses.isEmpty then + raise: + ErrorReport(msg"Cannot resolve the projection class" -> cls.toLoc :: Nil, + source = Diagnostic.Source.Compilation) + false + else true + def selSymbol(sel: AnySelTerm): Opt[DefinitionSymbol[?]] = sel.validResolvedTargets match // sel.resolvedTargets match @@ -1146,20 +1165,21 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case _ => fail: ErrorReport(msg"Assignment requires a term member" -> ref.toLoc :: Nil, source = Diagnostic.Source.Compilation) - case sel @ NewSel(prefix, nme) => - // Term resolution selects the definition; lowering only checks that - // the result is an unambiguous term member and emits the field write. - val sym = sel.resolvedTargets.distinct match - case sym :: Nil => sym.asTrm - case _ => N - sym match - case S(sym) => - subTerm(prefix): p => - subTerm_nonTail(rhs): r => - AssignField(p, definitionIdent(nme, sym), castTo(r, sym.erasedType, sel.toLoc), k(unit))(S(sym)) - case N => fail: - ErrorReport(msg"Assignment requires an unambiguous term member" -> sel.toLoc :: Nil, - source = Diagnostic.Source.Compilation) + case sel @ NewSel(prefix, nme, _) => + if !checkProjection(sel) then compError else + // Term resolution selects the definition; lowering only checks that + // the result is an unambiguous term member and emits the field write. + val sym = sel.resolvedTargets.distinct match + case sym :: Nil => sym.asTrm + case _ => N + sym match + case S(sym) => + subTerm(prefix): p => + subTerm_nonTail(rhs): r => + AssignField(p, definitionIdent(nme, sym), castTo(r, sym.erasedType, sel.toLoc), k(unit))(S(sym)) + case N => fail: + ErrorReport(msg"Assignment requires an unambiguous term member" -> sel.toLoc :: Nil, + source = Diagnostic.Source.Compilation) case sel @ Sel(prefix, nme) => subTerm(prefix): p => subTerm_nonTail(rhs): r => @@ -1219,8 +1239,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case whltrm: st.SynthWhile => ucs.Normalization(this)(whltrm)(k) - case sel @ NewSel(prefix, id) => - if sel.isErroneous then compError else sel.resolvedTargets.distinct match + case sel @ NewSel(prefix, id, _) => + if !checkProjection(sel) || sel.isErroneous then compError else sel.resolvedTargets.distinct match case Nil => fail: ErrorReport(msg"This selection of member '${id.name}' has no resolved target" -> sel.toLoc :: Nil, source = Diagnostic.Source.Compilation) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 15d756a5dd..7c39c632f1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -466,7 +466,7 @@ object Elaborator: val prefix = base.ref(Ident(base.nme)) val name = new Ident(nme).withLocOf(id) if config.language.useNewResolution then - val res = new Term.NewSel(prefix, name)(FlowSymbol.synthSel(nme)).withLocOf(id) + val res = new Term.NewSel(prefix, name, N)(FlowSymbol.synthSel(nme)).withLocOf(id) summon[NewResolver].newSel(res) res else @@ -1244,6 +1244,11 @@ extends Importer: if newResolution then resolveNew(res) res + def elaborateProjection(prefix: Term, cls: Term, name: Ident): Term = + val res = new Term.NewSel(prefix, name, S(cls))(FlowSymbol.selProj(name.name)).withLocOf(tree) + newSel(res) + interpretRef(res, interpretation) + /** Fallback to a normal selection + application when label-specific handling does not apply. */ def mkNonLabelSelectionApp(tree: App, sel: Sel, args: Ls[Tree]): Term = val sym = FlowSymbol.app() @@ -1254,7 +1259,7 @@ extends Importer: def elaborateSelection(tree: Sel): Term = val preTrm = subterm(tree.prefix) if newResolution then - val res = new Term.NewSel(preTrm, tree.name)(FlowSymbol.sel(tree.name.name)).withLocOf(tree) + val res = new Term.NewSel(preTrm, tree.name, N)(FlowSymbol.sel(tree.name.name)).withLocOf(tree) // listenTerm(preTrm, shape => selShape2(shape, tree.name, res)) newSel(res) interpretRef(res, interpretation) @@ -1452,17 +1457,20 @@ extends Importer: ifLike(Keyword.`if`, IfLikeForm.ReturningIf, shorthandSplit(tree), tree.toLoc) case InfixApp(Sel(pre, idn: Ident), Keywrd(Keyword.`#`), idp: Ident) => val c = subterm(idn, Clss) - val f = c.symbol.flatMap(_.asCls) match - case S(cls: ClassSymbol) => - cls.tree.allSymbols.get(idp.name) match - case S(fld: MemberSymbol) => S(fld) + if newResolution then + elaborateProjection(subterm(pre), c, idp) + else + val f = c.symbol.flatMap(_.asCls) match + case S(cls: ClassSymbol) => + cls.tree.allSymbols.get(idp.name) match + case S(fld: MemberSymbol) => S(fld) + case _ => + raise(ErrorReport(msg"Class '${cls.nme}' does not contain member '${idp.name}'." -> idp.toLoc :: Nil)) + N case _ => - raise(ErrorReport(msg"Class '${cls.nme}' does not contain member '${idp.name}'." -> idp.toLoc :: Nil)) + raise(ErrorReport(msg"Identifier `${idn.name}` does not name a known class symbol." -> idn.toLoc :: Nil)) N - case _ => - raise(ErrorReport(msg"Identifier `${idn.name}` does not name a known class symbol." -> idn.toLoc :: Nil)) - N - Term.SelProj(subterm(pre), c, idp)(f, FlowSymbol.selProj(idp.name), N, S(summon)) + Term.SelProj(subterm(pre), c, idp)(f, FlowSymbol.selProj(idp.name), N, S(summon)) case InfixApp(lhs, op @ Keywrd(Keyword.`|`), rhs) => Term.CompType(subterm(lhs, Tpe), subterm(rhs, Tpe), true)//.withLocOf(tree) case InfixApp(lhs, op @ Keywrd(Keyword.`&`), rhs) => @@ -1577,7 +1585,7 @@ extends Importer: elaborateSelection(sel) case MemberProj(ct, nme) => val c = subterm(ct, Clss) - val f = c.symbol.flatMap(_.asCls) match + val f = if newResolution then N else c.symbol.flatMap(_.asCls) match case S(cls: ClassSymbol) => cls.tree.allSymbols.get(nme.name) match case S(fld: MemberSymbol) => S(fld) @@ -1601,7 +1609,10 @@ extends Importer: ) val rs = FlowSymbol.app() Term.Lam(ps, - app(Term.SelProj(self.ref(), c, nme)(f, FlowSymbol.selProj(nme.name), N, S(summon)), args.ref())( + app( + if newResolution then elaborateProjection(self.ref(), c, nme) + else Term.SelProj(self.ref(), c, nme)(f, FlowSymbol.selProj(nme.name), N, S(summon)), + args.ref())( App(nme, Tup(Nil)) // FIXME , N, rs) ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index e4f01a047c..7350bbfcfa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -55,9 +55,9 @@ class NewResolver: def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])]): Unit = raise: ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) - def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Ls[Elem], src: Term, funSh: TermShape): Unit = + def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Ls[Elem], src: Term, funSh: TermShape, argumentMarks: Ls[Marks]): Unit = (ps, args) match - case (Nil, Nil) => () + case (Nil, Nil) if r.isEmpty => () // case (Nil, Spd(k, t) :: args) => // zip(Nil, r, args) case (Nil, args) => @@ -68,8 +68,10 @@ class NewResolver: "argument".pluralized(ps.length)}, but got ${args.length}" -> funSh.toLoc :: Nil) case S(p) => val packaged = new Tup(args)(Tree.DummyTup).withLoc(Loc.mk(args.iterator.flatMap(_.toLoc))) - val sh = IntroShape(packaged) - p.sym.shapeListeners.foreach(_(sh)) + IntroShape(packaged).exit(argumentMarks).enter(mss) match + case NoShape => () + case sh: TermShape => + if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then p.sym.shapeListeners.foreach(_(sh)) case (p :: ps, Fld(fls, trm, asc) :: args) => // case ((p, mss) :: ps, Fld(fls, trm, asc) :: args) => listenTerm(trm): sh0 => @@ -77,14 +79,14 @@ class NewResolver: // log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg}") // if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then // p.sym.shapeListeners.foreach(listener => listener(sh)) - sh0.enter(mss) match + sh0.exit(argumentMarks).enter(mss) match case NoShape => case sh: TermShape => log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg} ${isOwnedSym(p.sym)}, ${p.sym.shapes.contains(sh)}") if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then // log(s"!!!! ${p.sym.shapeListeners}") p.sym.shapeListeners.foreach(listener => listener(sh)) - zipArgs(mss, ps, r, args, src, funSh) + zipArgs(mss, ps, r, args, src, funSh, argumentMarks) case _ => resolError(src, msg"${funSh.describe.capitalize} expected ${ps.length} ${ @@ -215,8 +217,15 @@ class NewResolver: args match case args: Tup => log(s"Zipping ${ps} (${mss.map(_.showDbg)}) with ${args.fields}") - zipArgs(mss, ps.params, ps.restParam, args.fields, res, lhs) - case _ => ??? + zipArgs(mss, ps.params, ps.restParam, args.fields, res, lhs, Nil) + case _ => + // Member projections forward their rest-argument tuple. Its shape can + // arrive after the method, and carries the caller's capture marks. + listenTerm(args): + case Marked(sh: IntroShape, marks) => sh.trm match + case tuple: Tup => zipArgs(mss, ps.params, ps.restParam, tuple.fields, res, lhs, marks :: Nil) + case _ => resolError(res, msg"Expected an argument tuple." -> args.toLoc :: Nil) + case _ => resolError(res, msg"Expected an argument tuple." -> args.toLoc :: Nil) log(s"appShape isSaturated? ${sh.isSaturated}; head? ${sh.applicationHead}") def register = if res.shapes.add(sh) then res.shapeListeners.foreach(listener => listener(sh)) @@ -283,67 +292,93 @@ class NewResolver: if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate publishMember(ref, member, ref.resSym, marks) + /** Inspect an overload set only once its definitions have all been published. */ + private def completedClass(shape: SymShape)(selected: ClassDef => Unit, absent: () => Unit): Unit = + shape.sym.onComplete: () => + shape.sym.asCls match + case S(cls) => selected(cls.defn.get) + case N => absent() + + /** Class interpretations wait for completed overload sets, independently of term + * companions. Aliases can supply constructor shapes; applied instances cannot. + * Capture marks are retained for subsequent instance-member lookup. */ + private def listenClass(trm: Term)(selected: (ClassDef, Ls[Marks]) => Unit, reject: Shape => Unit): Unit = + def select(cls: ClassDef, marks: Ls[Marks]): Unit = + trm.withoutCaptures match + case ref: NewResolvable => + if !ref.resolvedTargets.contains(cls.sym) then ref.resolvedTargets ::= cls.sym + case _ => () + selected(cls, marks) + def value(sh: TermShape): Unit = sh match + case Marked(ds: DefnShape, marks) => ds.defn match + case cls: ClassDef => select(cls, marks :: Nil) + case td: TermDefinition => td.tsym match + case ctor: ClassCtorSymbol => select(ctor.associatedCls.defn.get, marks :: Nil) + case _ => reject(sh) + case _ => reject(sh) + case _ => reject(sh) + trm match + case Capture(base, thru) => + listenClass(base)((cls, marks) => select(cls, marks ::: EntryMark(thru, N, NoMarks) :: Nil), reject) + case _ => listen(trm): + case sh: SymShape => + completedClass(sh)(cls => select(cls, sh.markss), + () => fromBMS(sh.sym, sh.resSym, sh.markss, value, trm, _ => ())) + case sh: TermShape => value(sh) + def newSel(sel: NewSel): Unit = log(s"newSel? sel = ${sel.showDbg}") - listenTerm(sel.prefix): shape => - log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") - shape.getMember(sel.id.name) match - case S((bms, mss)) => - log(s"newSel member: bms = ${bms.showDbg}, mss = ${mss.map(_.showDbg)}") - sel.resolvedMembers ::= bms - publishMember(sel, bms, sel.resSym, mss) - // symShapes.getOrElseUpdate((bms, sel.resSym), SymShape(bms, sel.resSym)) - // .exit(mss) match - // case NoShape => - // case sh: TermShape => - // if sel.shapes.add(sh) then - // sel.shapeListeners.foreach(listener => listener(sh)) - case N => + def member(info: Opt[MemberInfo], description: Message, loc: Opt[Loc]): Unit = info match + case S((bms, marks)) => + log(s"newSel member: bms = ${bms.showDbg}, mss = ${marks.map(_.showDbg)}") + if !sel.resolvedMembers.contains(bms) then sel.resolvedMembers ::= bms + publishMember(sel, bms, sel.resSym, marks) + case N => + sel.isErroneous = true + resolError(sel, msg"$description does not contain member '${sel.id.name}'" -> loc :: Nil) + sel.cls match + case N => listenTerm(sel.prefix): shape => + log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") + member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc) + case S(cls) => + listenClass(cls)((cd, marks) => + val candidate = cd.sym -> marks + if !sel.resolvedClasses.contains(candidate) then sel.resolvedClasses ::= candidate + listenExt(cd.ext, ext => + member(DefnShape(cd, ext).getInstanceMember(sel.id.name).map(_.mapSecond(_ ::: marks)), + msg"Class '${cd.sym.nme}'", cd.toLoc)) + , sh => sel.isErroneous = true - resolError(sel, msg"${shape.describe.capitalize} does not contain member '${sel.id.name}'" -> shape.toLoc :: Nil) + resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> cls.toLoc :: Nil) + ) def resolveNew(nw: Term.New): Unit = log(s"resolveNew? res = ${nw.showDbg}") - // listenTerm(nw.cls, shape => newShape(shape, nw.args, nw)) nw.cls.withoutCaptures match - case trm: NewResolvable => - listen(trm): shape => - log(s"resolveNew: res = ${nw.showDbg}, cls = ${trm.showDbg}, shape = ${shape.shwDbg}") - def reject = + case trm: NewResolvable => listen(trm): shape => + def reject(): Unit = nw.isErroneous = true - resolError(nw, - msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> trm.toLoc :: Nil) + resolError(nw, msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> trm.toLoc :: Nil) shape match - case ss: SymShape => - // TODO handle ss.resSym - val bms = ss.sym - bms.onComplete: () => - bms.asCls match - case S(cls) => - trm.resolvedTargets ::= cls - val cd = cls.defn.get - listenExt(cd.ext, extsh => { - val dsh = DefnShape(cd, extsh) - val sh = newShapes.getOrElseUpdate((cls, ss.markss, nw.resSym), { - dsh.unappliedParams.lazyZip(nw.args).foreach: - case ((ps, mss), args) => - args match - case args: Tup => - zipArgs(mss, ps.params, ps.restParam, args.fields, nw, dsh) + case ss: SymShape => completedClass(ss)(cd => + if !trm.resolvedTargets.contains(cd.sym) then trm.resolvedTargets ::= cd.sym + listenExt(cd.ext, extsh => + val dsh = DefnShape(cd, extsh) + val sh = newShapes.getOrElseUpdate((cd.sym, ss.markss, nw.resSym), { + dsh.unappliedParams.lazyZip(nw.args).foreach: + case ((ps, mss), args) => + args match + case args: Tup => zipArgs(mss, ps.params, ps.restParam, args.fields, nw, dsh, Nil) case _ => ??? - NewShape(dsh, cls, ss.markss, nw.args, nw) - }) - if nw.shapes.add(sh) then - nw.shapeListeners.foreach(listener => listener(sh)) + NewShape(dsh, cd.sym, ss.markss, nw.args, nw) }) - case N => - reject - case _ => - reject - case _ => - nw.isErroneous = true - resolError(nw, - msg"Invalid class expression: ${nw.cls.describe}" -> nw.cls.toLoc :: Nil) + if nw.shapes.add(sh) then nw.shapeListeners.foreach(_(sh)) + ) + , reject) + case _ => reject() + case _ => + nw.isErroneous = true + resolError(nw, msg"Invalid class expression: ${nw.cls.describe}" -> nw.cls.toLoc :: Nil) def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term): DefineVar = if newResolution then sym match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 64305ed21e..08cdf07361 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -361,6 +361,12 @@ sealed trait NewRefImpl extends AnyRefImpl: sealed trait NewSelImpl extends NewResolvableImpl: self: Term.NewSel => var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution + // Class identity and captures must survive even when candidates share an inherited member. + var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil + def hasAmbiguousClass: Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: + _.withoutCaptures match + case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.sizeCompare(1) > 0 + case _ => false sealed trait UnresolvedRefImpl extends NewResolvableImpl: self: Term.UnresolvedRef => @@ -379,7 +385,8 @@ enum Term extends Statement, ShapePublisher: case SimpleRef(sym: codegen.SimpleSymbol)(val tree: Tree.Ident) extends Term, NewRefImpl case SelfRef(sym: InnerSymbol)(val tree: Tree.Ident) extends Term, NewRefImpl case MemberRef(sym: MemberSymbol)(val tree: Tree.Ident, val resSym: FlowSymbol) extends Term, NewResolvableImpl, NewRefImpl - case NewSel(prefix: Term, id: Tree.Ident)(val resSym: FlowSymbol) extends Term, NewSelImpl, ShapeHost + /** An optional class fixes the lookup scope for an explicit member projection. */ + case NewSel(prefix: Term, id: Tree.Ident, cls: Opt[Term])(val resSym: FlowSymbol) extends Term, NewSelImpl, ShapeHost case UnresolvedRef(prefixes: Ls[Term], id: Tree.Ident)(val resSym: FlowSymbol) extends Term, UnresolvedRefImpl, ShapeHost case Capture(base: Term, thru: AnyDefinitionSymbol) extends Term // --- LEGACY --- @@ -508,6 +515,7 @@ enum Term extends Statement, ShapePublisher: case SimpleRef(sym) => S(sym) case Capture(base, _) => base.resolvedSym case ref: UnresolvedRef if ref.resolvedMembers.distinct.sizeCompare(1) =/= 0 => N + case sel: NewSel if sel.hasAmbiguousClass => N case ref: NewResolvable => if ref.isErroneous then N else ref.resolvedTargets.distinct match case sym :: Nil => S(sym) @@ -746,7 +754,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case UnresolvedRef(_, _) => "wildcard-open reference" case App(lhs, rhs) => "application" case TyApp(lhs, targs) => "type application" - case NewSel(pre, nme) => "selection" + case NewSel(pre, nme, _) => "selection" case Sel(pre, nme) => "selection" case SynthSel(pre, nme) => "selection" case DynSel(o, f, _) => "dynamic selection" @@ -821,7 +829,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case TyApp(pre, tarsg) => pre +: tarsg.toVector case Sel(pre, _) => Vector.single(pre) case SynthSel(pre, _) => Vector.single(pre) - case NewSel(pre, _) => Vector.single(pre) + case NewSel(pre, _, cls) => Vector.single(pre) ++ cls.toVector case UnresolvedRef(prefixes, _) => prefixes.toVector case DynSel(o, f, _) => Vector.double(o, f) case Tup(fields) => fields.flatMap(_.subTerms).toVector @@ -925,14 +933,15 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case _ => r.sym.showName case sel: NewSel => val str = sel.id.name + val pre = sel.cls.fold(doc"${sel.prefix.show}.")(cls => doc"${sel.prefix.show}.${cls.show}#") if summon[ShowCfg].showFlowSymbols - then doc"${sel.prefix.show}.${ + then doc"$pre${ sel.resolvedMembers match case Nil => doc"${str}ˀˀˀ" case t :: Nil => t.showName case ts => doc"$str‹" :: ts.map(_.showName).mkDocument(", ") :: doc"›" }" - else doc"${sel.prefix.show}.$str" + else doc"$pre$str" case sel: Sel => if summon[ShowCfg].showFlowSymbols then doc"${sel.prefix.show}.${sel.sym.fold(doc"${ @@ -1058,7 +1067,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case WildcardTy(in, out) => s"in ${in.map(_.toString).getOrElse("⊥")} out ${out.map(_.toString).getOrElse("⊤")}" case Sel(pre, nme) => s"${pre.showDbg}.${nme.name}" case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" - case NewSel(pre, nme) => s"${pre.showDbg}.${nme.name}" + case NewSel(pre, nme, cls) => s"${pre.showDbg}.${cls.fold("")(c => s"${c.showDbg}#")}${nme.name}" case UnresolvedRef(_, id) => s"${id.name}‹open›" case DynSel(pre, fld, _) => s"${pre.showDbg}[${fld.showDbg}]" case IfLike(kw, _, split) => s"${kw.name} { ${split.showDbg} }" diff --git a/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls new file mode 100644 index 0000000000..7bea6b5e20 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls @@ -0,0 +1,42 @@ +:. + +// Call-site shape correlation is incomplete even without member projections. +:fixme +:expect 40 +class A { fun get() = 17 } +class B { fun get() = 23 } +fun identity(value) = value +fun forwarded(value) = identity(value) +forwarded(new A).get() + forwarded(new B).get() +//│ ╔══[COMPILATION ERROR] This selection of member 'get' is ambiguous, as it has multiple resolved targets +//│ ║ l.10: forwarded(new A).get() + forwarded(new B).get() +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── target: function 'get' in class 'B' +//│ ║ l.7: class B { fun get() = 23 } +//│ ║ ^^^ +//│ ╟── target: function 'get' in class 'A' +//│ ║ l.6: class A { fun get() = 17 } +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '40', got: 'undefined' + +// Projected functions expose the same limitation after argument forwarding. +:fixme +:expect 40 +class Id { fun apply(value) = value } +let id = new Id +val apply = Id::apply +apply(id, new A).get() + apply(id, new B).get() +//│ ╔══[COMPILATION ERROR] This selection of member 'get' is ambiguous, as it has multiple resolved targets +//│ ║ l.29: apply(id, new A).get() + apply(id, new B).get() +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── target: function 'get' in class 'B' +//│ ║ l.7: class B { fun get() = 23 } +//│ ║ ^^^ +//│ ╟── target: function 'get' in class 'A' +//│ ║ l.6: class A { fun get() = 17 } +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '40', got: 'undefined' +//│ apply = fun +//│ id = Id diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index bb0a8e372c..f4c8531b42 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -162,7 +162,7 @@ callid1(Predef) //│ | listen: trm = x⁰ //│ | appShape isSaturated? true; head? (DefnShape(term definition 'callid1'),List(ExitMark(term:callid1,Some(callid1),NoMarks))) //│ | lhs false List(((p⁰),List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ))) -//│ | appShape: td.body = Some(App(NewSel(SimpleRef(p),Ident(id)),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))) +//│ | appShape: td.body = Some(App(NewSel(SimpleRef(p),Ident(id),None),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))) //│ | listenTerm: trm = p⁰.id(1) //│ | listen: trm = p⁰.id(1) //│ ~> { fun member:callid1(p⁰) = p⁰.id(1); member:callid1⁰(member:Predef⁰) } diff --git a/hkmc2/shared/src/test/mlscript/newres/Projections.mls b/hkmc2/shared/src/test/mlscript/newres/Projections.mls new file mode 100644 index 0000000000..1ad50af33c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Projections.mls @@ -0,0 +1,172 @@ +:. + +// Both projection forms wait for forward class definitions. +:expect 6 +fun read(a) = a.Later#get() +val project = Later::get +class Later(val x) { fun get() = x } +let a = Later(3) +read(a) + project(a) +//│ = 6 +//│ a = Later(3) +//│ project = fun + +// The class interpretation is independent of its term overload. +:expect 7 +class Overloaded { fun get() = 7 } +val Overloaded = 99 +(new Overloaded).Overloaded#get() +//│ = 7 +//│ Overloaded = 99 + +// Class aliases retain constructor shapes. +:expect 8 +class Original(val x) { fun get() = x } +val Alias = Original +let a = Original(8) +a.Alias#get() +//│ = 8 +//│ Alias = fun Original { class: class Original } +//│ a = Original(8) + +// Inherited lookup retains the defining member. +:expect 9 +class Parent { fun get() = 9 } +class Child extends Parent {} +(new Child).Child#get() +//│ = 9 + +// Fields and assignments share the same resolution path. +:expect 12 +class Cell { mut val value = 10 } +let cell = new Cell +set cell.Cell#value = 12 +cell.Cell#value +//│ = 12 +//│ cell = Cell + +// Missing members and non-class interpretations are resolution errors. +:e +fun missing(a) = a.Later#missing() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.50: fun missing(a) = a.Later#missing() +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╙── Class 'Later' does not contain member 'missing' + +:e +val NotClass = 1 +fun invalid(a) = a.NotClass#get() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.58: fun invalid(a) = a.NotClass#get() +//│ ║ ^^^^^^^^^^^^^^ +//│ ╙── Integer literal cannot be used as a projection class. +//│ NotClass = 1 + +// Different classes can expose the very same inherited member. +:e +module Left { class Item extends Parent {} } +module Right { class Item extends Parent {} } +open Left +open Right +fun ambiguous(a) = a.Item#get() +//│ ╔══[COMPILATION ERROR] The projection class is ambiguous +//│ ║ l.71: fun ambiguous(a) = a.Item#get() +//│ ║ ^^^^^ +//│ ╟── class: 'Item' +//│ ║ l.68: module Right { class Item extends Parent {} } +//│ ║ ^^^^ +//│ ╟── class: 'Item' +//│ ║ l.67: module Left { class Item extends Parent {} } +//│ ╙── ^^^^ + +// Forwarded arguments carry shapes through the projected method's result. +:expect 17 +class Identity { fun apply(x) = x } +class Answer { fun get() = 17 } +let identity = new Identity +val apply = Identity::apply +apply(identity, new Answer).get() +//│ = 17 +//│ apply = fun +//│ identity = Identity + +// The receiver of a projection is evaluated once, including writes. +:expect 1 +module Counter { mut val count = 0 } +fun receiver() = { + set Counter.count = Counter.count + 1 + cell +} +set receiver().Cell#value = 13 +Counter.count +//│ = 1 + +// Captured class overloads keep the class interpretation. +:expect 21 +fun captured() = { + class Local { fun get() = 21 } + val Local = 0 + fun read(value) = value.Local#get() + read(new Local) +} +captured() +//│ = 21 + +// Two instances exposing the same class symbol are still ambiguous. +:e +class Library { class Entry { fun get() = 1 } } +let first = new Library +let second = new Library +open first +open second +fun sameClass(value) = value.Entry#get() +//│ ╔══[COMPILATION ERROR] The projection class is ambiguous +//│ ║ l.122: fun sameClass(value) = value.Entry#get() +//│ ║ ^^^^^^ +//│ ╟── class: 'Entry' +//│ ║ l.117: class Library { class Entry { fun get() = 1 } } +//│ ╙── ^^^^^ +//│ first = Library { Entry: class Entry } +//│ second = Library { Entry: class Entry } + +:e +fun missingClass(value) = value.Unknown#get() +//│ ╔══[COMPILATION ERROR] Cannot resolve the projection class +//│ ║ l.133: fun missingClass(value) = value.Unknown#get() +//│ ╙── ^^^^^^^^ + +:e +val instance = new Parent +fun instanceAsClass(value) = value.instance#get() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.140: fun instanceAsClass(value) = value.instance#get() +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── Instance of class 'Parent' cannot be used as a projection class. +//│ instance = Parent + +// A forward wildcard source resolves through its eventual module shape. +:expect 31 +fun forward(value) = { + open Delayed + value.Target#get() +} +module Delayed { class Target { fun get() = 31 } } +forward(new Delayed.Target) +//│ = 31 + +// Repeating the same wildcard source does not introduce ambiguity. +:expect 31 +fun repeated(value) = { + open Delayed + open Delayed + value.Target#get() +} +repeated(new Delayed.Target) +//│ = 31 + +// Member projections also accept qualified class names. +:expect 31 +val qualified = Delayed.Target::get +qualified(new Delayed.Target) +//│ = 31 +//│ qualified = fun diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/BackendFlags.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/BackendFlags.mls new file mode 100644 index 0000000000..2a426531ae --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/BackendFlags.mls @@ -0,0 +1,18 @@ +:. + +// Empty blocks do not warn; each compiled block warns when both are enabled. +:global +:js +:wasm + +:w +1 +//│ ═══[WARNING] Enabling both :js and :wasm is not currently supported; disable one before compiling this block. +//│ = 1 +//│ Wasm result: +//│ = 1 + +:!js +2 +//│ Wasm result: +//│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls index eec1eefab6..61173bd1d9 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls @@ -13,16 +13,27 @@ a.get() + A(2).get() //│ = 3 -:fixme +:expect 1 class A(val x) with fun get() = x let a = A(1) a.A#get() -//│ ╔══[COMPILATION ERROR] Identifier `A` does not name a known class symbol. -//│ ║ l.20: a.A#get() -//│ ╙── ^^ -//│ ╔══[COMPILATION ERROR] WatBuilder::result for field selection without a resolved symbol is not implemented (field `get`). Use `_.[_]` for index-based accesses. -//│ ║ l.20: a.A#get() -//│ ╙── ^^^ +//│ Wasm result: +//│ = 1 +:expect 4 +class B(val x) { fun get() = x } +let b = B(1) +b.B#get() + B(2).B#get() + b.get() +//│ Wasm result: +//│ = 4 + +// Resolution succeeds; WASM does not yet support the generated anonymous function. +:fixme +:expect 6 +class Add(val x) { fun add(y) = x + y } +val add = Add::add +add(Add(2), 4) +//│ ═══[COMPILATION ERROR] WatBuilder::returningTerm for FunDefn(...) where `!sym.nameIsMeaningful` not implemented yet +//│ ═══[COMPILATION ERROR] Expected static function reference in Call(...) expression diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala index 38daf778f6..6779caa9cb 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala @@ -51,6 +51,12 @@ abstract class WasmDiffMaker extends InvalMLDiffMaker: lazy val prettifyBinaryenWat = (content: Str) => content.substring(2, content.length() - 2).replace("\\\\n", "\n").replace("\\\\\"", "\"") + override def processTrees(trees: Ls[syntax.Tree])(using Config, Raise): Unit = + if trees.nonEmpty && js.isSet && wasm.isSet then + raise(WarningReport( + msg"Enabling both :js and :wasm is not currently supported; disable one before compiling this block." -> N :: Nil)) + super.processTrees(trees) + override def processIRBlock( pgrm: Program, definedValues: ComputeDefinedValues, From 6bd8d901f87ef62df24259d04b952087da4ac18c Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 15:29:53 +0800 Subject: [PATCH 026/228] Point rejected shape diagnostics to their source origins --- .../scala/hkmc2/semantics/NewResolver.scala | 6 ++-- .../main/scala/hkmc2/semantics/Shape.scala | 4 ++- .../mlscript/newres/ConstructorResolution.mls | 6 ++-- .../shared/src/test/mlscript/newres/Opens.mls | 6 ++-- .../src/test/mlscript/newres/Projections.mls | 36 ++++++++++++++----- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 7350bbfcfa..fc0ff4a087 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -109,7 +109,7 @@ class NewResolver: true def reject(sh: TermShape): Unit = res.isErroneous = true - resolError(res, msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> N :: Nil) + resolError(res, msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> sh.toLoc :: Nil) def classPattern(cls: ClassLikeDef): Unit = if select(cls.sym) then val assoc = res.arguments match case N => Nil @@ -349,7 +349,7 @@ class NewResolver: msg"Class '${cd.sym.nme}'", cd.toLoc)) , sh => sel.isErroneous = true - resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> cls.toLoc :: Nil) + resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc :: Nil) ) def resolveNew(nw: Term.New): Unit = @@ -358,7 +358,7 @@ class NewResolver: case trm: NewResolvable => listen(trm): shape => def reject(): Unit = nw.isErroneous = true - resolError(nw, msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> trm.toLoc :: Nil) + resolError(nw, msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.toLoc :: Nil) shape match case ss: SymShape => completedClass(ss)(cd => if !trm.resolvedTargets.contains(cd.sym) then trm.resolvedTargets ::= cd.sym diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index f220d0592e..4289204565 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -10,6 +10,8 @@ import scala.collection.mutable sealed trait Shape extends ShapeLike: def describe: Str + /** Origin of the value or symbol described by this shape, independently of its use site. */ + def toLoc: Opt[Loc] def shwDbg(using DebugPrinter): Str = this match // case ds: DefnShape => s"DefnShape(${ds.defn.describe} ${ds.defn.sym.showDbg})" case ds: DefnShape => ds.defn.sym.showDbg @@ -210,7 +212,6 @@ sealed trait TermShape extends Shape: enter(mark).enter(rest) def isSaturated: Bool = unappliedParams.isEmpty - def toLoc: Opt[Loc] end TermShape @@ -256,6 +257,7 @@ class NewShape(val receiver: TermShape, val cls: ClassLikeSymbol, clsMarks: Ls[M class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: Ls[Marks]) extends Shape: def describe: Str = s"${sym.describe} symbol '${sym.nme}'" + def toLoc: Opt[Loc] = sym.toLoc override def toString: String = s"SymShape($sym)" def exit(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = ??? def exit(marks: Marks)(using TL): TermShape | NoShape = ??? diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls index 5e599368f1..c40d13263d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls @@ -167,7 +167,9 @@ invalid(0) //│ ╔══[COMPILATION ERROR] Resolution error in constructor //│ ║ l.165: fun invalid(x) = if x is NotAPattern then 1 else 0 //│ ║ ^^^^^^^^^^^ -//│ ╙── Integer literal cannot be used as a constructor pattern. +//│ ╟── Integer literal cannot be used as a constructor pattern. +//│ ║ l.165: fun invalid(x) = if x is NotAPattern then 1 else 0 +//│ ╙── ^ //│ = 0 //│ NotAPattern = 1 @@ -203,7 +205,7 @@ fun ambiguousNew() = { } ambiguousNew() //│ ╔══[COMPILATION ERROR] The class instantiated here is ambiguous -//│ ║ l.202: new Token +//│ ║ l.204: new Token //│ ║ ^^^^^ //│ ╟── target: class 'Token' //│ ║ l.10: class Token diff --git a/hkmc2/shared/src/test/mlscript/newres/Opens.mls b/hkmc2/shared/src/test/mlscript/newres/Opens.mls index 8c4a5f3b8c..7d31c39e28 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Opens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Opens.mls @@ -267,7 +267,9 @@ new Shadowed //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.266: new Shadowed //│ ║ ^^^^^^^^^^^^ -//│ ╙── Value symbol 'Shadowed' cannot be instantiated with keyword 'new'. +//│ ╟── Value symbol 'Shadowed' cannot be instantiated with keyword 'new'. +//│ ║ l.264: val Shadowed = 20 +//│ ╙── ^^^^^^^^^^^^^^^^^ //│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ Shadowed = 20 @@ -297,7 +299,7 @@ fun ambiguousReversed() = { } ambiguousReversed() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous -//│ ║ l.296: extra +//│ ║ l.298: extra //│ ║ ^^^^^ //│ ╟── candidate from member:Selected⁰^term:ambiguousReversed⁰: value //│ ║ l.38: module Selected { val extra = 6 } diff --git a/hkmc2/shared/src/test/mlscript/newres/Projections.mls b/hkmc2/shared/src/test/mlscript/newres/Projections.mls index 1ad50af33c..0b88dca567 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Projections.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Projections.mls @@ -59,7 +59,9 @@ fun invalid(a) = a.NotClass#get() //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.58: fun invalid(a) = a.NotClass#get() //│ ║ ^^^^^^^^^^^^^^ -//│ ╙── Integer literal cannot be used as a projection class. +//│ ╟── Integer literal cannot be used as a projection class. +//│ ║ l.57: val NotClass = 1 +//│ ╙── ^ //│ NotClass = 1 // Different classes can expose the very same inherited member. @@ -70,13 +72,13 @@ open Left open Right fun ambiguous(a) = a.Item#get() //│ ╔══[COMPILATION ERROR] The projection class is ambiguous -//│ ║ l.71: fun ambiguous(a) = a.Item#get() +//│ ║ l.73: fun ambiguous(a) = a.Item#get() //│ ║ ^^^^^ //│ ╟── class: 'Item' -//│ ║ l.68: module Right { class Item extends Parent {} } +//│ ║ l.70: module Right { class Item extends Parent {} } //│ ║ ^^^^ //│ ╟── class: 'Item' -//│ ║ l.67: module Left { class Item extends Parent {} } +//│ ║ l.69: module Left { class Item extends Parent {} } //│ ╙── ^^^^ // Forwarded arguments carry shapes through the projected method's result. @@ -121,10 +123,10 @@ open first open second fun sameClass(value) = value.Entry#get() //│ ╔══[COMPILATION ERROR] The projection class is ambiguous -//│ ║ l.122: fun sameClass(value) = value.Entry#get() +//│ ║ l.124: fun sameClass(value) = value.Entry#get() //│ ║ ^^^^^^ //│ ╟── class: 'Entry' -//│ ║ l.117: class Library { class Entry { fun get() = 1 } } +//│ ║ l.119: class Library { class Entry { fun get() = 1 } } //│ ╙── ^^^^^ //│ first = Library { Entry: class Entry } //│ second = Library { Entry: class Entry } @@ -132,16 +134,18 @@ fun sameClass(value) = value.Entry#get() :e fun missingClass(value) = value.Unknown#get() //│ ╔══[COMPILATION ERROR] Cannot resolve the projection class -//│ ║ l.133: fun missingClass(value) = value.Unknown#get() +//│ ║ l.135: fun missingClass(value) = value.Unknown#get() //│ ╙── ^^^^^^^^ :e val instance = new Parent fun instanceAsClass(value) = value.instance#get() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.140: fun instanceAsClass(value) = value.instance#get() +//│ ║ l.142: fun instanceAsClass(value) = value.instance#get() //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ╙── Instance of class 'Parent' cannot be used as a projection class. +//│ ╟── Instance of class 'Parent' cannot be used as a projection class. +//│ ║ l.141: val instance = new Parent +//│ ╙── ^^^^^^^^^^ //│ instance = Parent // A forward wildcard source resolves through its eventual module shape. @@ -170,3 +174,17 @@ val qualified = Delayed.Target::get qualified(new Delayed.Target) //│ = 31 //│ qualified = fun + +// Deferred aliases must retain the rejected value's origin, not just its use site. +:e +fun invalidAlias(value) = value.AliasToNumber#get() +val SourceNumber = 123 +val AliasToNumber = SourceNumber +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.180: fun invalidAlias(value) = value.AliasToNumber#get() +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Integer literal cannot be used as a projection class. +//│ ║ l.181: val SourceNumber = 123 +//│ ╙── ^^^ +//│ AliasToNumber = 123 +//│ SourceNumber = 123 From 4206411e1f9d8e1ddca6bdd2cebfb423b5af0fa8 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 22 Sep 2026 15:40:43 +0800 Subject: [PATCH 027/228] Cleanup --- doc/new-resolution-plan.md | 206 ------------------ doc/new-resolution-wasm.md | 77 ------- .../main/scala/hkmc2/codegen/Lowering.scala | 8 - .../scala/hkmc2/semantics/Elaborator.scala | 150 ++++--------- 4 files changed, 47 insertions(+), 394 deletions(-) delete mode 100644 doc/new-resolution-plan.md delete mode 100644 doc/new-resolution-wasm.md diff --git a/doc/new-resolution-plan.md b/doc/new-resolution-plan.md deleted file mode 100644 index 92a608fe4c..0000000000 --- a/doc/new-resolution-plan.md +++ /dev/null @@ -1,206 +0,0 @@ -# New resolution: overloads and deferred opens - -**Status: all five planned stages complete.** The final review and regression -coverage are recorded below. - -Resolution owns member discovery and definition selection. Lowering consumes those -decisions; it must not recover missing decisions by inspecting overload sets or -partially elaborated definitions. A member overload set, its selected definition, -and the shape of its value are separate pieces of information. - -## Intended wildcard-open semantics - -For `newResolution`, lookup first searches the complete chain of ordinary `Ctx` -environments. Explicit bindings, including member definitions and selective opens, -always take precedence over wildcard opens, including wildcard opens in an inner -scope. Only a name with no explicit binding is looked up through wildcard opens. - -`Ctx` carries the currently wildcard-opened members. Such a lookup creates an -`UnresolvedRef` and registers listeners on those opened members. As their shapes -arrive, matching members are propagated into the reference's `resolvedMembers`. -A pending shape is not evidence that a member is absent. Candidates retain their -receiver and capture context; module completion must not change lexical priority. - -## Stages - -1. **Record the contracts and update the language reference.** - Document explicit-binding precedence and deferred wildcard lookup. Keep this - plan current with implementation progress and validation results. -2. **Make term selection a resolver responsibility.** - Centralize selection after symbol completion and record the selected definition - separately from propagated value shapes. Register assignment term resolution, - including synthesized assignments, and remove assignment lowering's member - fallback. Preserve receiver evaluation order and avoid reading the assigned - value to determine its target. -3. **Implement deferred opens.** - Add wildcard-open context entries and `UnresolvedRef`. Search explicit bindings - before wildcard sources. Use normal shape resolution for selective opens too; - remove `moduleMembers` and eager syntax/definition-table lookup in new mode. - Preserve receivers and marks through captures, nesting, imports, and forward - references. Resolve competing distinct members without choosing by callback - arrival order. Lowering can report ambiguity from the accumulated candidates; - reporting ambiguity does not entail performing name resolution in lowering. -4. **Enforce the phase boundary for all uses.** - Ensure ordinary value uses request term resolution, while class and pattern - uses request their appropriate interpretation. Replace eager bare-pattern name - classification so opened constructors are recognized without syntax lookup. - Remove definition-selection - fallbacks from `MemberRef`/`NewSel` lowering and audit `SelElem` shape seeding. - Distinguish unresolved work from diagnosed errors and successful selections; - assert internal invariants instead of silently guessing. Audit backend class - storage against resolved identities and live IR definitions. -5. **Validate and commit.** - Cover forward definitions, both module-body forms, explicit-vs-wildcard - precedence across scopes and declaration orders, competing opens, captures, - imports, overloaded class/term uses, and ordinary/synthesized assignments. - Run `ctest` before focused tests, then `hkmc2AllTests/test`. Review diagnostics - and golden changes for semantic correctness and commit the resulting outputs - with the implementation under the agent's identity. - -## Progress - -- Stage 1 complete: plan recorded and wildcard lookup rules added to the language - reference. These are the intended rules; stage 3 implements them. -- Stage 2 implemented: `listenTerm` records the definition selected after symbol - completion, independently of its value shapes. Source and synthesized - assignments request term resolution. Assignment lowering no longer selects - from `resolvedMembers`. Declared values publish no implementation shape but - still supply a selected definition. -- Stage 2 regressions cover a forward module definition, overloaded field writes, - backtracking restoration, declared fields, receiver/RHS evaluation order, and - rejection of a class-only assignment target. -- Stage 2 validation: `ctest` passed (45 tests); `dtest newres/` passed (24 files); - `hkmc2AllTests/test` passed. Regression outputs reviewed for the stage checkpoint. -- Capture follow-up: module/object references no longer introduce their own exit - marks. Enclosing function and receiver-context marks remain intact. This fixes - the nested-module assertion in `newres/Assignments.mls`; its `:fixme` is removed. - `newres/ModuleCaptures.mls` covers nested and escaping modules, objects, getters, - methods, and separation of captures from distinct calls. Validation passed: - `ctest` (45 tests), `dtest newres/` (25 files), and `hkmc2AllTests/test`. -- Stage 3 implemented: contexts retain wildcard sources and search the full - explicit environment chain first. `UnresolvedRef` listens to the sources and - retains each candidate's receiver; lowering reports missing or ambiguous - candidates without doing resolution. Selective opens now use selection - listeners, and `moduleMembers` is removed. Import aliases propagate shapes. -- Primitive operators retain their existing implicit binding priority, ahead of - wildcard sources. The initial stage-3 post-elaboration traversal has been - replaced by the contextual elaboration described below. -- Stage 3 regressions cover forward and indented modules, both open forms, - declaration and open orders, outer explicit bindings, parameters, repeated and - competing sources, distinct receivers sharing one member symbol, chained opens, - imports, nested captures, class/value and class/function overloads, applied - constructor patterns, and temporary assignment restoration. -- Deferred uses exposed duplicate shape-listener registration and an unfinished - constructor-shape cache branch. Listeners are now registered once; cached - constructors are reused with assertions checking their definition and base. - An existing applied-pattern test now passes without its `:todo` marker. -- At the stage-3 checkpoint, bare constructor names still used eager classification; - `Opens.mls` recorded this with a `:fixme` regression for both open forms. The - final stage-4 work below removes that limitation and the marker. - -- Stage 3 validation: `ctest` passed (45 tests), `dtest newres/` passed - (26 files), and `hkmc2AllTests/test` passed with the final regression outputs. - Golden changes remove duplicate debug callbacks and replace the fixed - applied-pattern exception with its expected result. - -- Contextual interpretation is now threaded through `term` and `subterm`: - ordinary term uses request resolution during elaboration; class and pattern - targets retain their specialized interpretation, and type positions do not - request runtime values. `resolveOpenUses` and its second tree walk are removed. -- `term` requires an explicit interpretation; only `subterm` defaults to `Trm`. - This is a deliberate exception to the usual rule against defaults: ordinary - recursive operands stay concise, while symbolic positions and transparent - wrappers explicitly specify or forward the interpretation. The enum cases are - imported as `Trm`, `Clss`, `Ptrn`, and `Tpe`. Other helpers require it explicitly. - An implicit context was avoided because a target's interpretation must not leak - into its value arguments or selection prefixes. -- The resolver records direct definition references without demanding their value - shapes; overload sets still resolve through listeners. Failed term selection - marks the reference erroneous, avoiding repeated and secondary diagnostics. -- `Interpretations.mls` covers forward value uses, parenthesized and locally opened - class targets, constructor arguments, pattern guards, and type-only references - in signatures, aliases, type arguments, and ascriptions. Bare-pattern lookup and - lowering/lexical shortcuts were handled in the subsequent stage-4 checkpoints. -- Contextual-interpretation validation: `ctest` passed (45 tests), - `dtest newres/` passed (27 files), and the final `hkmc2AllTests/test` passed. - Existing runtime outputs are unchanged; reviewed golden updates record the - additional resolution requests made during elaboration. - -- Stage 4, receiver and lowering checkpoint: lexical `SelElem` references now - register ordinary selection listeners, just like selective opens. `SelfRef` - obtains its base shape through definition-completion listeners, handling both - pending and already available definitions and retaining inherited shapes. - The resolver caches these base shapes; no new mutable symbol state is needed. -- `MemberRef` and `NewSel` lowering now consume `resolvedTargets` exclusively. - Diagnosed references produce the error result; missing targets produce an - unresolved-target diagnostic; strict ambiguous selections report all selected - definitions. Existing non-strict dynamic selection behavior is preserved. -- `ReceiverResolution.mls` covers forward lexical members, overloaded class/value - names, nested receiver capture, later selective opens, and inherited selection. - Ambiguity diagnostics retain owner context and now point at selected definition - names rather than the entire overload declaration. -- Reviewed the existing independent class storage: name reservation records live - IR class symbols, and class and term slots remain separate. No storage change - is needed for this checkpoint. -- The receiver/lowering checkpoint left constructor-pattern resolution for a - separate regression review. That work is discharged below. The agreed alias - rule for new resolution uses capitalization, replacing lookup-dependent binding. -- Receiver/lowering validation: `ctest` passed (45 tests), the focused receiver - regression passed, and final `hkmc2AllTests/test` passed (including 659 diff - tests). Reviewed the four changed ambiguity snapshots and the new regression's - runtime outputs. - -## Final stage-4 review - -- Bare, applied, selected, and infix constructor patterns now use one resolver - entry point. It waits for member completion, selects the pattern/class/object - interpretation independently of term overloads, and accumulates candidates. - Full and string-prefix pattern lowering share candidate validation. Lowering - diagnoses multiple targets and distinct wildcard receivers, including - receivers exposing the same member symbol. Missing targets and already reported - errors are handled separately; no candidate is chosen by callback order. -- Bare uppercase names use ordinary contextual lookup. As agreed, `p as name` - binds a lowercase name and `p as Name` matches an uppercase constructor; an - unknown uppercase name is an error. The language reference documents this rule - and applies wildcard precedence to pattern interpretations too. -- Constructor shape propagation handles aliases, chains, and nominal inheritance, - so bindings receive matching scrutinee shapes. Guards and literal tests may - conservatively retain shapes. This does not attempt to implement all unfinished - shape inference for other pattern forms, such as tuple bindings or conjunction - outputs. Constructor calls and explicit `new` now share inherited member lookup, - preserving inherited capture marks; this also discharges three existing - inheritance `:fixme` cases. -- Reentrant member-completion callbacks now see the completed state immediately, - rather than appending to the listener buffer currently being traversed. -- Symbol queries consume recorded new-resolution targets, including captures. - Synthesized runtime constructors carry explicit resolved class symbols, which - lowering now accepts directly. Ambiguous `new` targets produce diagnostics. -- The `selfShapes` cache is keyed only by inner symbol, with assertions that - repeated notifications agree on the definition and extension. -- Backend review: class storage continues to use live IR identities and separate - slots for class/term overloads. The new pattern path retains the selected class - identity through lowering; imported overload patterns exercise the exported - class slots. No backend storage workaround was added. - -| Planned contract | Regression coverage | -| --- | --- | -| Explicit bindings beat wildcard opens across scopes and declaration orders | `Opens.mls` | -| Deferred forward definitions, both module-body forms, captures and imports | `Opens.mls`, `ModuleCaptures.mls`, `ReceiverResolution.mls`, `OverloadedClassImports.mls` | -| Class/function and class/value overloads, storage and live class identities | `OverloadedClasses.mls`, `OverloadedClassValues.mls`, `OverloadedClassImports.mls` | -| Ordinary and synthesized assignments, evaluation order and restoration | `Assignments.mls`, `Opens.mls` | -| Contextual term/class/pattern/type interpretation | `Interpretations.mls`, `ConstructorResolution.mls` | -| Bare/applied/selected/infix patterns, objects, aliases, named patterns and pattern parameters | `ConstructorResolution.mls`, `Opens.mls`, `PatMat.mls` | -| Ambiguity independent of open order, repeated sources and distinct receivers | `ConstructorResolution.mls`, `Opens.mls` | - -## Final validation - -- `ctest`: 45 tests passed. -- `dtest newres/`: 29 tests passed; the final added pattern-parameter regression - also passed its focused run. -- Final `hkmc2AllTests/test`: all suites passed, including 660 diff tests. -- Reviewed the golden changes: bare opens and constructor aliases now succeed; - inherited member regressions now return their expected values; ambiguity and - invalid-pattern cases produce diagnostics. Two JS snapshots omit `safeCall` - now that symbol queries expose the already recorded function targets. -- Legacy-resolution diagnostic spans are unchanged. Implementation, language - reference, regression inputs, and final golden outputs are committed together. diff --git a/doc/new-resolution-wasm.md b/doc/new-resolution-wasm.md deleted file mode 100644 index 2f301e207d..0000000000 --- a/doc/new-resolution-wasm.md +++ /dev/null @@ -1,77 +0,0 @@ -# New-resolution WASM investigation - -## Implemented fixes - -The new-resolution tests inherit `:js` from the root test configuration. With -`:wasm`, lowering emits WASM-specific intrinsic references, but the harness also -tries to execute that same IR as JavaScript. The WASM directory's `.mls` now -uses `:!js` while retaining the inherited language configuration. Imported -directives recognize the lexer's `:!` token as well as `:`. Compiling a nonempty -block with both backends enabled now emits a warning. - -Explicit `instance.Class#member` and `Class::member` now share `NewSel` with an -optional class expression. Elaborating a projection registers class-shape -listeners; symbolic class lookup waits for definition completion and chooses the -class interpretation independently of term overloads. Class aliases can supply -constructor shapes. Applied instances are rejected as projection classes. -Completed-symbol class interpretation is shared with explicit `new`, whose -accepted class-expression forms remain unchanged. - -Instance-member lookup uses completed class and extension shapes, retaining -capture marks for inherited members and publishing the selected member's shape. -Recorded projection candidates retain class identity and capture marks, and -ambiguity checks also account for distinct wildcard receivers. Different classes -inheriting the same member do not become an unambiguous projection. Repeated -notifications and repeated opens of the same receiver are deduplicated. - -Lowering validates recorded candidates and uses the ordinary selection paths -for reads, calls, and assignments. It neither queries syntax members nor -selects definitions from an overload set. Symbol queries also reject ambiguous -projection classes. The instance is evaluated once; assignment coverage checks -this with a receiver that increments a counter. - -`Class::member` keeps its lambda elaboration. Its forwarded argument tuple is -resolved through shape listeners, including when arguments arrive after the -method shape. Argument extraction retains the tuple's capture marks, and rest -parameters publish and cache their tuple shapes, including empty tuples. - -## Coverage and remaining limitations - -`Projections.mls` covers both syntaxes, forward class definitions, class/term -overloads, aliases, qualified names, inherited methods, fields and assignments, -captured classes, invalid classes, missing members, distinct wildcard receivers, -shared inherited members, repeated opens, and forward wildcard sources. - -`WasmMethodResolution.mls` now runs direct projections without `:fixme`, including -mixed projected and ordinary method calls. `BackendFlags.mls` checks the warning -and disabling JS through the directory configuration. - -Two independent limitations remain explicit regressions: - -- WASM cannot compile the anonymous function generated by `Class::member` yet. - The final block in `WasmMethodResolution.mls` retains `:fixme` for the backend's - anonymous-function/static-call diagnostics; member resolution succeeds. -- Call-site shape correlation is incomplete for nested forwarding calls. - `CallSiteShapes.mls` reproduces the ambiguity both with ordinary functions and - with a projected function. These cases remain `:fixme`; the resolver does not - choose an arbitrary member to suppress the ambiguity. - -Method calls across separate REPL blocks are being handled on another branch -and are outside this work. - -## Optional harness follow-up - -If tests need both JS and WASM execution, lower separately for each target and -keep their backend scopes and session state separate. Respect the MIR rule that -each definition symbol owns at most one current IR definition: use isolated -compilation state or refresh symbols rather than retaining two IRs that mutate -the same symbols' `irDefn` fields. Binding the WASM support module in the JS -scope is insufficient: that module supplies compilation/instantiation helpers, -not JavaScript implementations of all WASM intrinsics. - -## Validation - -- `ctest`: all 45 tests passed. -- `dtest newres/`: all 35 tests passed, with the explicit limitations above marked `:fixme`. -- `hkmc2AllTests/test`: passed, including all 666 diff tests. -- Reviewed golden changes and checked the diff for whitespace errors. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 9e2cfebf2b..a08a3ac2f8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -433,12 +433,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case cls :: Nil => cls.defn match case S(clsDef: ClassLikeDef) => - // resl match - // case ref: MemberRef => - // subTerm(ref)(k) - // case sel: NewSel => - // subTerm(sel.prefix): pre => - // k(Select(pre, memberIdent(sel.id, S(clsDef.bsym)))(S(clsDef.sym))(false)) subTerm(resl)(k) case _ => softAssert(resl.isErroneous, s"Unexpected `new` target: ${cls.showDbg}") @@ -1264,8 +1258,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): setupSelection(prefix, nme, S(sym))(k) case sel @ SynthSel(prefix, nme) => - // System.out.println(sel) - // System.out.println(sel.sym) // * Not using `setupSelection` as these selections are not meant to be sanity-checked // * Unlike source `Sel`s, compiler-synthesized selections may carry a known // * member symbol directly in `sel.sym` without being wrapped in `Resolved`. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 7c39c632f1..7977c49b77 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -142,40 +142,6 @@ object Elaborator: def nestLocal(nameHint: Str): Ctx = nest(OuterCtx.LocalScope(nameHint)) def nestInner(inner: InnerSymbol): Ctx = nest(OuterCtx.InnerScope(inner)) - /* - def get(name: Str)(using config: Config): Opt[Ctx.Elem] = - env.get(name).orElse: - val nr = config.language.useNewResolution - if nr then parent match - case S(p @ Ctx(outer = OuterCtx.InnerScope(inner))) if nr => - p.get(name).map(Ctx.CaptElem(_, inner.asDefnSym)) - case S(p @ Ctx(outer = OuterCtx.Function(_, _, symo))) if nr => - symo match - case S(inner) => - p.get(name).map(Ctx.CaptElem(_, inner)) - case N => - // TODO: also capture across lambdas. - ??? - case _ => parent.flatMap(_.get(name)) - else parent.flatMap(_.get(name)) - */ - /* - def get(name: Str)(using config: Config): Opt[Ctx.Elem] = - env.get(name).orElse: - val nr = config.language.useNewResolution - if nr then outer match - case OuterCtx.InnerScope(inner) => - parent.flatMap(_.get(name)).map(Ctx.CaptElem(_, inner.asDefnSym)) - case OuterCtx.Function(_, _, symo) => - symo match - case S(inner) => - parent.flatMap(_.get(name)).map(Ctx.CaptElem(_, inner)) - case N => - // TODO: also capture across lambdas. - ??? - case _ => parent.flatMap(_.get(name)) - else parent.flatMap(_.get(name)) - */ /** Explicit bindings in any enclosing scope take precedence over every wildcard open. */ def get(name: Str)(using config: Config): Opt[Ctx.Elem] = getExplicit(name).orElse: @@ -450,7 +416,6 @@ object Elaborator: case sym: InnerSymbol => Term.SelfRef(sym)(id) case sym: MemberSymbol => - // Term.MemberRef(sym)(id, FlowSymbol.memSym(sym, id.name)) Term.MemberRef(sym)(id, FlowSymbol.memSym(sym)) // case sym: TermSymbol => // FIXME: should never happen... (currently happens for ref to ctor let) // Term.MemberRef(sym)(id) @@ -1172,38 +1137,17 @@ extends Importer: // Callers specify the interpretation at each elaboration entry point. Keeping // it explicit (rather than implicit) prevents a target's interpretation from // leaking into its value operands. - def term(tree: Tree, interpretation: Interpretation): Ctxl[Term] = + def term(tree: Tree, interp: Interpretation): Ctxl[Term] = trace[Term](s"Elab term ${tree.showDbg}", r => s"~> ${r.showDbg}"): val unders = mutable.ArrayBuffer.empty[VarSymbol] given UnderCtx = new UnderCtx(S(unders)) - val st = subterm(tree, interpretation) + val st = subterm(tree, interp) val params = unders.iterator.map: sym => Param(FldFlags.empty, sym, N, Modulefulness.none) .toList if params.isEmpty then st else Term.Lam(PlainParamList(params), st) - /* - private def termDefinition( - k: TermDefKind, // * The only reason we store it here in addition to tsym.k is for refining patmats - sym: BlockMemberSymbol, - tsym: TermSymbol, - params: Ls[ParamList], - tparams: Opt[Ls[Param]], - sign: Opt[Term], - body: Opt[Term], - flags: TermDefFlags, - modulefulness: Modulefulness, - annotations: Ls[Annot], - companion: Opt[CompanionSymbol], - ): TermDefinition = - val res = new TermDefinition( - k, sym, tsym, params, tparams, sign, body, flags, modulefulness, annotations, companion) - body.foreach: body => - listenTerm(body, shape => termDefShape(shape, res)) - res - */ - private def app(lt: Term, rt: Term)(tree: Tree.App, typ: Opt[typing.Type], resSym: FlowSymbol): Term = val res = new Term.App(lt, rt)(tree, typ, resSym) if newResolution then listenTerm(lt)(shape => appShape(shape, rt, res)) @@ -1228,13 +1172,13 @@ extends Importer: /** Request the reference's term interpretation in expression positions. * Listeners can wait for forward definitions to complete. */ - private def interpretRef(ref: Term, interpretation: Interpretation): Term = - if newResolution && (interpretation is Trm) then requireTerm(ref) + private def interpretRef(ref: Term, interp: Interpretation): Term = + if newResolution && (interp is Trm) then requireTerm(ref) ref // Most recursive operands are terms. Transparent wrappers must explicitly // forward their interpretation; class, pattern, and type operands specify theirs. - def subterm(tree: Tree, interpretation: Interpretation = Trm): Ctxl[UnderCtx ?=> Term] = + def subterm(tree: Tree, interp: Interpretation = Trm): Ctxl[UnderCtx ?=> Term] = trace[Term](s"Elab subterm ${tree.showDbg}", r => s"~> ${r.showDbg}"): def error = Term.Error().withLocOf(tree) @@ -1247,7 +1191,7 @@ extends Importer: def elaborateProjection(prefix: Term, cls: Term, name: Ident): Term = val res = new Term.NewSel(prefix, name, S(cls))(FlowSymbol.selProj(name.name)).withLocOf(tree) newSel(res) - interpretRef(res, interpretation) + interpretRef(res, interp) /** Fallback to a normal selection + application when label-specific handling does not apply. */ def mkNonLabelSelectionApp(tree: App, sel: Sel, args: Ls[Tree]): Term = @@ -1262,7 +1206,7 @@ extends Importer: val res = new Term.NewSel(preTrm, tree.name, N)(FlowSymbol.sel(tree.name.name)).withLocOf(tree) // listenTerm(preTrm, shape => selShape2(shape, tree.name, res)) newSel(res) - interpretRef(res, interpretation) + interpretRef(res, interp) else val sym = if newResolution then N else resolveField(tree.name, preTrm.symbol, tree.name) @@ -1279,7 +1223,7 @@ extends Importer: Term.Sel(preTrm, tree.name)(sym, FlowSymbol.sel(tree.name.name), N, S(summon)) tree.desugared match - case Tree.Trm(term) => interpretRef(term, interpretation) + case Tree.Trm(term) => interpretRef(term, interp) case unt @ Unt() => unit.withLocOf(unt) case Bra(k, e) => k match @@ -1287,18 +1231,18 @@ extends Importer: case Curly => case _ => raise(ErrorReport(msg"Unsupported ${k.name} in this position" -> tree.toLoc :: Nil)) - term(e, interpretation) // * not `subterm` as `e` could be a lambda shorthand + term(e, interp) // * not `subterm` as `e` could be a lambda shorthand case b: Block => ctx.nestLocal("‹block›").givenIn: - block(b, hasResult = true, resultInterpretation = interpretation)._1 match + block(b, hasResult = true, resultInterp = interp)._1 match case Term.Blk(Nil, res) => res case res => res case lit: Literal => Term.Lit(lit) case d: Def => - subterm(Block(d :: Unt() :: Nil), interpretation) + subterm(Block(d :: Unt() :: Nil), interp) case LetLike(kw @ Keywrd(`let`), lhs, rhso, S(bod)) => - subterm(Block(LetLike(kw, lhs, rhso, N) :: bod :: Nil), interpretation) + subterm(Block(LetLike(kw, lhs, rhso, N) :: bod :: Nil), interp) case LetLike(kw @ Keywrd(`let`), lhs, rhso, N) => raise(ErrorReport( msg"Expected a body for let bindings in expression position" -> @@ -1324,14 +1268,14 @@ extends Importer: Blk( LetDecl(sym, Nil) :: defineVar(sym, lt) :: Nil, Term.Try(Blk( assignment(lt, subterm(rhs)) :: Nil, - subterm(bod, interpretation), + subterm(bod, interp), ), assignment(lt, sym.ref()))) case LetLike(Keywrd(Keyword.`set`), _, N, S(_)) => raise: ErrorReport(msg"Expected a right-hand side for this assignment" -> tree.toLoc :: Nil) error case TryFinally(tryBody, finallyBody) => - Term.Try(subterm(tryBody, interpretation), subterm(finallyBody)) + Term.Try(subterm(tryBody, interp), subterm(finallyBody)) case (hd @ Hndl(id: Ident, c, Block(sts_), S(bod))) => ctx.nest(OuterCtx.LambdaOrHandlerBlock).givenIn: val sym = VarSymbol(id, erasedType = N) @@ -1374,7 +1318,7 @@ extends Importer: (subterm(c, Clss), Nil) (ctx + (id.name -> sym)).givenIn: - Term.Handle(sym, cp, p, derivedClsSym, tds, subterm(bod, interpretation)) + Term.Handle(sym, cp, p, derivedClsSym, tds, subterm(bod, interp)) case h: Hndl => raise(ErrorReport( msg"Unsupported handle binding shape" -> @@ -1387,11 +1331,11 @@ extends Importer: raise: ErrorReport(msg"Cannot use 'this' outside of an object scope" -> tree.toLoc :: Nil) error - case id @ Ident(name) => ident(id).map(interpretRef(_, interpretation)).getOrElse: + case id @ Ident(name) => ident(id).map(interpretRef(_, interp)).getOrElse: raise(ErrorReport(msg"Name not found: $name" -> id.toLoc :: Nil)) error case TyApp(lhs, targs) => - Term.TyApp(subterm(lhs, interpretation), targs.map { + Term.TyApp(subterm(lhs, interp), targs.map { case Modified(Keywrd(Keyword.`in`), arg) => Term.WildcardTy(S(subterm(arg, Tpe)), N) case Modified(Keywrd(Keyword.`out`), arg) => Term.WildcardTy(N, S(subterm(arg, Tpe))) case Tup(Modified(Keywrd(Keyword.`in`), arg1) :: Modified(Keywrd(Keyword.`out`), arg2) :: Nil) => @@ -1443,13 +1387,13 @@ extends Importer: Term.Lam(syms, term(rhs, Trm)(using nestCtx)) case TyTup(tys) => val constraints = tys.flatMap(maybeConstraint) - val body = term(rhs, interpretation) + val body = term(rhs, interp) Term.Constrained(constraints, body) case _ => lastWords(s"Unexpected lambda parameter shape: $lhs") case InfixApp(lhs, Keywrd(Keyword.`as`), rhs) => - Term.Asc(subterm(lhs, interpretation), subterm(rhs, Tpe)) + Term.Asc(subterm(lhs, interp), subterm(rhs, Tpe)) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => - block(Block(tree :: Nil), hasResult = false, resultInterpretation = interpretation)._1 + block(Block(tree :: Nil), hasResult = false, resultInterp = interp)._1 case PrefixApp(kw @ Keywrd(Keyword.`not`), rhs) => app(State.builtinOpsMap("!").ref(new Ident("not").withLocOf(kw)), Term.Tup( PlainFld(subterm(rhs)) :: Nil)(DummyTup))(DummyApp, N, FlowSymbol("not-app")) @@ -1486,12 +1430,12 @@ extends Importer: case App(Ident("~"), Tup(rhs :: Nil)) => Term.Neg(subterm(rhs, Tpe)) case PrefixApp(Keywrd(Keyword.`|` | Keyword.`&`), rhs) => - subterm(rhs, interpretation) + subterm(rhs, interp) case tree @ OpSplit(lhs, rhss) => val tree = rhss.foldLeft(lhs): case (acc, rhs) => rhs.splitOn(acc) - subterm(tree, interpretation) + subterm(tree, interp) case tree @ App(sel @ Sel(labelId @ Ident(labelName), nme @ Ident("break")), Tup(args)) => val value = args match case Nil => N @@ -1617,11 +1561,11 @@ extends Importer: , N, rs) ) case tree @ Tup(TermDef(Ins, f, N) :: fs) => - Term.CtxTup((f :: fs).map(fld(_, interpretation)))(tree) + Term.CtxTup((f :: fs).map(fld(_, interp)))(tree) case Modified(kw @ Keywrd(Keyword.`mut`), tree @ Tup(fields)) => - Term.Mut(Term.Tup(fields.map(fld(_, interpretation)))(tree)).mkLocWith(kw) + Term.Mut(Term.Tup(fields.map(fld(_, interp)))(tree)).mkLocWith(kw) case tree @ Tup(fields) => - Term.Tup(fields.map(fld(_, interpretation)))(tree) + Term.Tup(fields.map(fld(_, interp)))(tree) case DynamicNew(Apps(c, args)) => val (mut, c2) = c match @@ -1639,7 +1583,7 @@ extends Importer: clsSym -> // TODO integrate context inherited from cls // TODO make context with var symbols for class parameters - ObjBody(block(rft, hasResult = false, resultInterpretation = Trm)._1) + ObjBody(block(rft, hasResult = false, resultInterp = Trm)._1) body match case S(Apps(c, args)) => val (mut, c2) = c match @@ -1737,7 +1681,7 @@ extends Importer: raise(ErrorReport(msg"Illegal outer binding." -> tree.toLoc :: Nil)) error case Outer(N) => ctx.get("outer") match - case S(sym) => interpretRef(sym.ref(Ident("outer")), interpretation) + case S(sym) => interpretRef(sym.ref(Ident("outer")), interp) case N => raise(ErrorReport(msg"Illegal outer reference." -> tree.toLoc :: Nil)) error @@ -1753,7 +1697,7 @@ extends Importer: raise(ErrorReport(msg"Illegal type declaration in term position." -> tree.toLoc :: Nil)) error case Modified(Keywrd(Keyword.`mut`), body: Block) => - blockOrRcd(body, hasResult = true, resultInterpretation = interpretation) match + blockOrRcd(body, hasResult = true, resultInterp = interp) match case (Blk(Nil, Term.UnitVal()), ctx) => Rcd(mut = true, Nil).withLocOf(body) case (blk: Blk, ctx) => @@ -1796,7 +1740,7 @@ extends Importer: raise(ErrorReport(msg"Illegal position for 'open' statement." -> tree.toLoc :: Nil)) error case OpenIn(op, body) => - subterm(Block(Open(op) :: body :: Nil), interpretation) + subterm(Block(Open(op) :: body :: Nil), interp) case DynAccess(obj, rhs) => rhs match case Bra(bk @ (Round | Square), fld) => Term.DynSel(subterm(obj), subterm(fld), bk is Square) @@ -1819,8 +1763,8 @@ extends Importer: unds += sym sym.ref() case Annotated(lhs, rhs) => - annot(lhs).fold(subterm(rhs, interpretation))(ann => - Term.Annotated(ann, subterm(rhs, interpretation))) + annot(lhs).fold(subterm(rhs, interp))(ann => + Term.Annotated(ann, subterm(rhs, interp))) case Keywrd(kw) => raise(ErrorReport(msg"Unexpected keyword '${kw.name}' in this position." -> tree.toLoc :: Nil)) error @@ -1836,20 +1780,20 @@ extends Importer: raise(ErrorReport(msg"Unsupported term in this position (${tree.describe})." -> tree.toLoc :: Nil)) error - def arg(tree: Tree, interpretation: Interpretation)(using UnderCtx): Ctxl[Term] = tree match - case u: Under => subterm(tree, interpretation) // Note: currently `f(a, _, c)` is treated the same as `f of a, _, c` - case _ => term(tree, interpretation) - def fld(tree: Tree, interpretation: Interpretation)(using UnderCtx): Ctxl[Elem] = tree match + def arg(tree: Tree, interp: Interpretation)(using UnderCtx): Ctxl[Term] = tree match + case u: Under => subterm(tree, interp) // Note: currently `f(a, _, c)` is treated the same as `f of a, _, c` + case _ => term(tree, interp) + def fld(tree: Tree, interp: Interpretation)(using UnderCtx): Ctxl[Elem] = tree match case InfixApp(id: Ident, Keywrd(Keyword.`:`), rhs) => Fld(FldFlags.empty, Term.Lit(StrLit(id.name).withLocOf(id)), S(arg(rhs, Tpe))) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => - Fld(FldFlags.empty, term(lhs, interpretation), S(arg(rhs, Tpe))) + Fld(FldFlags.empty, term(lhs, interp), S(arg(rhs, Tpe))) case Spread(Keywrd(Keyword.`..`), S(trm)) => - Spd(SpreadKind.Lazy, arg(trm, interpretation)) + Spd(SpreadKind.Lazy, arg(trm, interp)) case Spread(Keywrd(Keyword.`...`), S(trm)) => - Spd(SpreadKind.Eager, arg(trm, interpretation)) + Spd(SpreadKind.Eager, arg(trm, interp)) case _ => - val t = arg(tree, interpretation) + val t = arg(tree, interp) var flags = FldFlags.empty Fld(flags, t, N) @@ -1860,8 +1804,8 @@ extends Importer: def block(sts: Ls[Tree], hasResult: Bool)(using UnderCtx): Ctxl[(Blk, Ctx)] = block(new Block(sts), hasResult, Trm) - def block(blk: Block, hasResult: Bool, resultInterpretation: Interpretation)(using UnderCtx): Ctxl[(Blk, Ctx)] = - blockOrRcd(blk, hasResult, resultInterpretation) match + def block(blk: Block, hasResult: Bool, resultInterp: Interpretation)(using UnderCtx): Ctxl[(Blk, Ctx)] = + blockOrRcd(blk, hasResult, resultInterp) match case (blk: Blk, ctx) => (blk, ctx) case (rcd: Rcd, ctx) => (Blk(Nil, rcd), ctx) @@ -1883,14 +1827,14 @@ extends Importer: // * for these, elaborate with `hasResult = false`, which uses `undefined` as the result // * when there is no other result available. This is fine since the value is never used. // * These useless trailing `undefined`s are then removed by `Lowering`. - def blockOrRcd(blk: Block, hasResult: Bool, resultInterpretation: Interpretation)(using UnderCtx) + def blockOrRcd(blk: Block, hasResult: Bool, resultInterp: Interpretation)(using UnderCtx) : Ctxl[(Blk | Rcd, Ctx)] = trace[(Blk | Rcd, Ctx)]( pre = s"Elab block ${blk.desugStmts.toString.truncate(100, "[...]")} ${ctx.outer}", r => s"~> ${r._1.showDbg}" ): val members = blk.definedSymbols.toMap - val fieldInterpretation = resultInterpretation match + val fieldInterpretation = resultInterp match case Tpe => Tpe case _ => Trm val newSignatureTrees = mutable.Map.empty[Str, Tree] // * Store trees of signatures @@ -2476,7 +2420,7 @@ extends Importer: def mkBody(extraParams: Ls[ParamList])(using Ctx) = withFields(extraParams): body match case N | S(Error()) => (new Blk(Nil, Term.Lit(UnitLit(false))), ctx) - case S(b: Block) => block(b, hasResult = false, resultInterpretation = Trm) + case S(b: Block) => block(b, hasResult = false, resultInterp = Trm) case S(t) => raise(ErrorReport( msg"Illegal body of ${k.desc} definition (should be a block; found ${t.describe})." -> t.toLoc :: Nil)) @@ -2646,7 +2590,7 @@ extends Importer: go(sts, annotations, acc) case (st: Tree) :: sts => // TODO reject plain term statements? Currently, `(1, 2)` is allowed to elaborate (tho it should be rejected in type checking later) - val interpretation = if sts.isEmpty then resultInterpretation else Trm + val interpretation = if sts.isEmpty then resultInterp else Trm val res = annotations.foldLeft(term(st, interpretation)): case (acc, ann) => Term.Annotated(ann, acc) sts match @@ -3022,13 +2966,13 @@ extends Importer: def importFrom(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) - val (res, newCtx) = block(sts, hasResult = false, resultInterpretation = Trm) + val (res, newCtx) = block(sts, hasResult = false, resultInterp = Trm) // TODO handle name clashes (res, newCtx) def topLevel(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) - val (res, ctx) = block(sts, hasResult = false, resultInterpretation = Trm) + val (res, ctx) = block(sts, hasResult = false, resultInterp = Trm) computeVariances(res) (res, ctx) From 8e66dbefd0a6be2b98c1236ac561158f40b1544c Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 15:56:33 +0800 Subject: [PATCH 028/228] Migrate initial WASM tests to new resolution --- doc/new-resolution-wasm.md | 41 +++++++++++++++++++ hkmc2/shared/src/test/mlscript/wasm/.mls | 6 +++ .../test/mlscript/wasm/ClassInheritance.mls | 12 +++--- .../src/test/mlscript/wasm/Exceptions.mls | 5 ++- .../src/test/mlscript/wasm/MainFunctions.mls | 3 +- .../src/test/mlscript/wasm/Matching.mls | 11 +++-- .../src/test/mlscript/wasm/ScopedLocals.mls | 3 +- .../src/test/mlscript/wasm/SingletonUnit.mls | 3 +- .../src/test/mlscript/wasm/Singletons.mls | 3 +- .../shared/src/test/mlscript/wasm/Strings.mls | 3 +- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 4 +- .../src/test/scala/hkmc2/WasmDiffMaker.scala | 4 +- 12 files changed, 74 insertions(+), 24 deletions(-) create mode 100644 doc/new-resolution-wasm.md create mode 100644 hkmc2/shared/src/test/mlscript/wasm/.mls diff --git a/doc/new-resolution-wasm.md b/doc/new-resolution-wasm.md new file mode 100644 index 0000000000..3212326f8a --- /dev/null +++ b/doc/new-resolution-wasm.md @@ -0,0 +1,41 @@ + +## Existing WASM suite migration (partial) + +The shared `mlscript/wasm/.mls` enables new resolution, strict resolution, and +WASM execution while disabling JS execution. Migrated tests start with `:.`, +before any blank line, so these commands apply throughout the file. No +`:global` is needed. The synthetic Predef import now explicitly targets JS, +and WASM emission checks the compilation target as well as `:wasm`; this avoids +sending the host-side bootstrap import to WASM when the option appears at the +very top of a file. `newres/wasm/FileWideFlags.mls` covers that placement across +two blocks. `Binaryen.mls` is unchanged. + +Eight existing files migrate without compiler design changes: `ClassInheritance`, +`Exceptions`, `MainFunctions`, `Matching`, `ScopedLocals`, `SingletonUnit`, +`Singletons`, and `Strings`. + +The remaining eleven files were tried with the shared configuration and failed. +Their original configurations and snapshots are retained, rather than marking +the migration failures as expected. The main remaining work is deferred type interpretation: + +- `NumericTypes` and `BuiltinOperators`: annotations such as `Int64` and `Int32` + are elaborated as new references, but `ErasedType.eraseSign` still queries + legacy `sign.symbol` eagerly. Declared erased types become unknown, causing + primitive/reference representation conversion errors. Type interpretation must + resolve before erased signatures are consumed, including forward definitions, + aliases, and wildcard names; replacing this query with another eager lookup + would not establish that contract. +- `TailRecOptCasts`: the separately compiled declaration + `fun useBox(b: Box): Int = b.v` has no resolved target for `v` before any call. + Declared parameter types should publish instance shapes through the existing + listener-based resolution infrastructure, independently of calls. + +The trial also exposed smaller adaptation gaps: assignment lowering does not +accept `SimpleRef` or captured selection l-values; mutable tuple shape handling +throws an unimplemented-case exception; some old expected-error cases now +succeed. These were not changed after reaching the design boundary. + +Blocked files: `Basics`, `BuiltinOperators`, `Casts`, `ClassMethods`, `ControlFlow`, +`NumericIntrinsicReview`, `NumericTypes`, `ReplImports`, `TailRecOptCasts`, `Tuples`, +and `VirtualMethods`. Cross-block method handling remains owned by the other +branch. diff --git a/hkmc2/shared/src/test/mlscript/wasm/.mls b/hkmc2/shared/src/test/mlscript/wasm/.mls new file mode 100644 index 0000000000..ba01bcfae7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/wasm/.mls @@ -0,0 +1,6 @@ +:.. +:!js +:wasm +#lang(strictResolution: true) +//│ Wasm result: +//│ = {} diff --git a/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls b/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls index a326fbc0b8..be964a5f2b 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm class A(val x) @@ -100,6 +99,7 @@ c.Parent#x + (c is Parent) //│ (return //│ (local.get $this)))) //│ (func $entry (export "entry") (type $entry) (result (ref null any)) +//│ (local $scrut (ref null any)) //│ (local $matchRes (ref null any)) //│ (local $currentTypeInfo (ref null any)) //│ (local $targetTypeInfo (ref null any)) @@ -109,19 +109,21 @@ c.Parent#x + (c is Parent) //│ (call $Child_ctor //│ (ref.i31 //│ (i32.const 2)))) +//│ (local.set $scrut +//│ (global.get $c)) //│ (block $match //│ (local.set $matchRes //│ (ref.null any)) //│ (if //│ (ref.test (ref $Object) -//│ (global.get $c)) +//│ (local.get $scrut)) //│ (then //│ (if //│ (block (result i32) //│ (local.set $currentTypeInfo //│ (struct.get $Object $$typeinfo //│ (ref.cast (ref $Object) -//│ (global.get $c)))) +//│ (local.get $scrut)))) //│ (local.set $targetTypeInfo //│ (global.get $Parent_typeinfo)) //│ (local.set $typeInfoMatch @@ -267,7 +269,7 @@ class B class A extends B class B extends A //│ ╔══[COMPILATION ERROR] Inheritance cycles are not supported. -//│ ║ l.267: class A extends B +//│ ║ l.269: class A extends B //│ ╙── ^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/wasm/Exceptions.mls b/hkmc2/shared/src/test/mlscript/wasm/Exceptions.mls index 2bdfda52c3..faacff2902 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Exceptions.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Exceptions.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm :wat @@ -35,4 +34,6 @@ throw false :ge let x = 1 set x += 1 in x +//│ ═══[COMPILATION ERROR] Unexpected left-hand side in assignment (reference) +//│ ═══[COMPILATION ERROR] Unexpected left-hand side in assignment (reference) //│ ═══[COMPILATION ERROR] WatBuilder::returningTerm for TryBlock(...) not implemented yet diff --git a/hkmc2/shared/src/test/mlscript/wasm/MainFunctions.mls b/hkmc2/shared/src/test/mlscript/wasm/MainFunctions.mls index ac631f4d3d..5b5c95fa58 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/MainFunctions.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/MainFunctions.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm let x = 0 diff --git a/hkmc2/shared/src/test/mlscript/wasm/Matching.mls b/hkmc2/shared/src/test/mlscript/wasm/Matching.mls index a9ec477d25..c35167dcad 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Matching.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Matching.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm if true then 0 else 2 //│ Wasm result: @@ -281,7 +280,9 @@ fun isBool(v) = if v is Bool then 1 else 0 isInt(1) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define isInt⁰ as fun isInt¹(v: ?): ? { -//│ match v +//│ let scrut: ?; +//│ set scrut = v; +//│ match scrut //│ Int⁰ => //│ return 1 //│ else @@ -289,7 +290,9 @@ isInt(1) //│ end //│ }; //│ define isBool⁰ as fun isBool¹(v: ?): ? { -//│ match v +//│ let scrut: ?; +//│ set scrut = v; +//│ match scrut //│ Bool⁰ => //│ return 1 //│ else diff --git a/hkmc2/shared/src/test/mlscript/wasm/ScopedLocals.mls b/hkmc2/shared/src/test/mlscript/wasm/ScopedLocals.mls index de2a1845c5..5c7ab591a7 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ScopedLocals.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ScopedLocals.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm :wat diff --git a/hkmc2/shared/src/test/mlscript/wasm/SingletonUnit.mls b/hkmc2/shared/src/test/mlscript/wasm/SingletonUnit.mls index 31c795cc25..c0b6cfcc90 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/SingletonUnit.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/SingletonUnit.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm :wat () diff --git a/hkmc2/shared/src/test/mlscript/wasm/Singletons.mls b/hkmc2/shared/src/test/mlscript/wasm/Singletons.mls index 8a3faeb224..3664c2ae24 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Singletons.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Singletons.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm :wat diff --git a/hkmc2/shared/src/test/mlscript/wasm/Strings.mls b/hkmc2/shared/src/test/mlscript/wasm/Strings.mls index 9b4df994de..5aac241761 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Strings.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Strings.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm "Hello World!" //│ Wasm result: diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 5ba7129745..a338952ed2 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -323,7 +323,9 @@ abstract class MLsDiffMaker extends DiffMaker: () if file != preludeFile then val cfg = mkConfig - given Config = cfg.copy(optimizer = cfg.optimizer.copy( + // The synthetic Predef import bootstraps the host environment; it is not + // a WASM test block, even when :wasm is enabled at the top of the file. + given Config = cfg.copy(target = CompilationTarget.JS, optimizer = cfg.optimizer.copy( deforest = cfg.deforest.map: d => d.copy(config = d.config.copy( debug = false, diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala index 6779caa9cb..7b33282f05 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala @@ -52,7 +52,7 @@ abstract class WasmDiffMaker extends InvalMLDiffMaker: content.substring(2, content.length() - 2).replace("\\\\n", "\n").replace("\\\\\"", "\"") override def processTrees(trees: Ls[syntax.Tree])(using Config, Raise): Unit = - if trees.nonEmpty && js.isSet && wasm.isSet then + if trees.nonEmpty && js.isSet && wasm.isSet && config.target == CompilationTarget.Wasm then raise(WarningReport( msg"Enabling both :js and :wasm is not currently supported; disable one before compiling this block." -> N :: Nil)) super.processTrees(trees) @@ -67,7 +67,7 @@ abstract class WasmDiffMaker extends InvalMLDiffMaker: val outerRaise: Raise = summon - if wasm.isSet then + if wasm.isSet && config.target == CompilationTarget.Wasm then val reportedMessages = mutable.Set.empty[Str] From c265858bec1ce43be28353e882cb31cd32fc85a1 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 16:14:38 +0800 Subject: [PATCH 029/228] Restrict resolved symbol queries to lowering and publish IR class headers --- .../src/main/scala/hkmc2/CompilerCtx.scala | 5 +++++ .../src/main/scala/hkmc2/codegen/Block.scala | 18 +++++++++++++++++ .../main/scala/hkmc2/codegen/ErasedType.scala | 14 ++----------- .../main/scala/hkmc2/codegen/Lowering.scala | 17 ++++++++++++++++ .../main/scala/hkmc2/invalml/InvalML.scala | 6 +++++- .../main/scala/hkmc2/semantics/Resolver.scala | 20 +++++++++---------- .../scala/hkmc2/semantics/SimpleSplit.scala | 4 ++-- .../main/scala/hkmc2/semantics/Split.scala | 2 +- .../main/scala/hkmc2/semantics/Symbol.scala | 7 +++++++ .../src/main/scala/hkmc2/semantics/Term.scala | 20 ++++++++++++++----- .../hkmc2/semantics/flow/FlowAnalysis.scala | 4 ++-- .../hkmc2/semantics/ucs/Normalization.scala | 12 +++-------- .../semantics/ups/FixedPointCompiler.scala | 2 +- .../hkmc2/semantics/ups/Instantiator.scala | 2 +- .../hkmc2/semantics/ups/SplitCompiler.scala | 2 +- .../test/mlscript/newres/IRClassHeaders.mls | 19 ++++++++++++++++++ .../test/scala/hkmc2/InvalmlDiffMaker.scala | 5 ++++- 17 files changed, 113 insertions(+), 46 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/IRClassHeaders.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala index 2c4782ad9a..b4b756b3e4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala @@ -218,6 +218,11 @@ class CompilerCtx( val elab = Elaborator(tl, file.up, Ctx.empty) val initCtx = State.init.nestLocal("prelude") val (blk, ctx) = elab.importFrom(parse.resultBlk)(using initCtx) + // Prelude declarations have no executable program, but their nominal hierarchy + // is needed by erased-type operations in every compilation unit. + given Ctx = ctx + given SymbolPrinter = new SymbolPrinter(Scope.empty(Scope.Cfg.default)) + new codegen.Lowering().classHeaders(blk) PreludeArtifact(parse.resultBlk, blk, ctx, state, rootConfig, lastMod), ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 90a9a6372b..53e0282500 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -29,6 +29,10 @@ This means that if a program fragment containing definitions is rewritten at all then the rewritten version must be kept as the "currently valid" version, and the old one must no longer be reused, as a given symbol can never correspond to more than one IR definition. +Class-like symbols also retain a nominal `ClassHeader`: lowering publishes these before executable bodies, +including for external declarations, and rewritten IR class definitions refresh them. This lets inheritance +queries operate solely on IR even when a parent has no executable definition or has not been lowered yet. + Moreover, we generally assume that in a given program, no symbol is every bound more than once by any binding form, such as Scoped, Label, Define. (This is currently not completely enforced, though.) This means that processes that duplicate binding forms within a program (eg, inlining) @@ -845,6 +849,11 @@ object ValDefn: ValDefn(tsym = TermSymbol(k, owner, Tree.Ident(sym.nme), erasedType = rhs.erasedValueType), sym, rhs)(configOverride, annotations) +/** The nominal hierarchy of a class, available independently of its executable body. + * An absent header means an unknown parent; a header with no parent denotes a root. + */ +final case class ClassHeader(parent: Opt[TypeSymbol]) + /* The following explains the difference between paramsOpt, auxParams, privateFields and publicFields. @@ -900,6 +909,12 @@ final case class ClsLikeDefn( val annotations: Ls[Annot], ) extends Defn: require(k isnt syntax.Mod) + isym match + case sym: ClassLikeSymbol => + sym.irClassHeader = parentPath match + case N => S(ClassHeader(N)) + case S(parent) => parent.targetSymbol.flatMap(_.asTpe).map(p => ClassHeader(S(p))) + case _ => () methods.foreach: m => m.dSym.irDefn = S(m) companion.foreach: c => @@ -917,6 +932,9 @@ final case class ClsLikeBody( ctor: Block, annotations: Ls[Annot], ): + isym match + case sym: ModuleOrObjectSymbol => sym.irClassHeader = S(ClassHeader(N)) + case _ => () def isStaged: Bool = annotations.exists: case Annot.Modifier(Keyword.`staged`) => true case _ => false diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala index 15dbef9a55..cdcff9d8da 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala @@ -157,18 +157,8 @@ object ErasedType: * - `S(N)` for a root class with no parent. * - `N` for a class whose parent chain is not available in the IR (e.g. an unlinked import). */ - private def parentOf(sym: BaseTypeSymbol)(using Ctx, State): Opt[Opt[TypeSymbol]] = - sym.asClsOrMod.flatMap: sym => - sym.irClsLikeDefn.flatMap: defn => - defn.parentPath match - case S(parent) => parent.targetSymbol.collect { case s: TypeSymbol => s }.map(S(_)) - case N => S(N) - .orElse: - // FIXME: remove this fallback once imported classes have their `irClsLikeDefn` properly linked - sym.defn.flatMap: defn => - defn.ext match - case S(parent) => parent.cls.resolvedSym.flatMap(_.asClsOrMod).map(S(_)) - case N => S(N) + private def parentOf(sym: BaseTypeSymbol): Opt[Opt[TypeSymbol]] = + sym.asClsOrMod.flatMap(_.irClassHeader.map(_.parent)) /** A symbol's ancestors, nearest first, starting with the symbol itself and following its single parent chain. * diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index a08a3ac2f8..07c53c0a94 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -86,6 +86,7 @@ object Lowering: import Lowering.* class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): + private given Lowering = this val newResolution: Bool = config.language.useNewResolution val strictResolution: Bool = config.language.strictResolution @@ -1649,7 +1650,23 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): ps.params.map(_.sym.erasedType) + /** Publish nominal parent links before lowering bodies can ask erased-type questions. + * This also handles declarations, whose executable definitions are deliberately omitted. + * An unresolved or ambiguous parent leaves the header absent, rather than inventing a root. + */ + def classHeaders(statement: Statement): Unit = + statement match + case cls: ClassLikeDef => cls.sym match + case sym: ClassLikeSymbol if sym.irClassHeader.isEmpty => + sym.irClassHeader = cls.ext match + case N => S(ClassHeader(N)) + case S(parent) => parent.cls.resolvedSym.flatMap(_.asClsOrMod).map(p => ClassHeader(S(p))) + case _ => () + case _ => () + statement.subStatements.foreach(classHeaders) + def program(main: st.Blk, symbolsToPreserve: Set[BoundSymbol]): Program = + classHeaders(main) val (imps, funs, rest) = splitBlock(main.stats, Nil, Nil, Nil) diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala index 732ec23e1c..b6244a6443 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala @@ -69,8 +69,12 @@ object InvalCtx: end InvalCtx -class InvalTyper(using elState: Elaborator.State, tl: TL)(using Ctx, Config): +class InvalTyper(using codegen.Lowering)(using elState: Elaborator.State, tl: TL)(using Ctx, Config): import tl.{trace, log} + + // InvalML expands patterns while typing. Its early use of lowering is valid only + // for legacy resolution, whose terms do not carry deferred candidate sets. + require(!config.language.useNewResolution, "InvalML requires legacy resolution") private val infVarState = new InfVarUid.State() private val solver = new ConstraintSolver(infVarState, elState, tl) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index b1852bace5..6697ca77d5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -525,7 +525,7 @@ class Resolver(tl: TraceLogger) def resolve(t: Resolvable, prefer: Expect, inAppPrefix: Bool, inCtxPrefix: Bool, inTyPrefix: Bool)(using ICtx): (Opt[CallableDefinition], ICtx) = trace[(Opt[CallableDefinition], ICtx)]( s"Resolving resolvable term: ${t}, (prefer = ${prefer}, inAppPrefix = ${inAppPrefix}, inCtxPrefix = ${inCtxPrefix}, inTyPrefix = ${inTyPrefix})", - _ => s"~> ${t.expanded} (sym = ${t.resolvedSym}, typ = ${t.resolvedTyp})" + _ => s"~> ${t.expanded} (sym = ${t.legacyResolvedSym}, typ = ${t.resolvedTyp})" ): // Resolve the sub-resolvable-terms of the term. val (defn, newICtx1) = @@ -595,7 +595,7 @@ class Resolver(tl: TraceLogger) (N, ictx) t.expandedResolvableIn: t => - log(s"Resolving resolvable term ${t} with sym = ${t.resolvedSym}, typ = ${t.resolvedTyp}") + log(s"Resolving resolvable term ${t} with sym = ${t.legacyResolvedSym}, typ = ${t.resolvedTyp}") // Fill the context with possibly the type arguments information. val newICtx2 = newICtx1.givenIn: @@ -853,7 +853,7 @@ class Resolver(tl: TraceLogger) def resolveSymbol(t: Resolvable, prefer: Expect, sign: Bool)(using ictx: ICtx): Unit = trace[Unit]( s"Resolving symbol for term: ${t} (prefer = ${prefer})", - _ => s"-> (sym = ${t.resolvedSym}, typ = ${t.resolvedTyp})" + _ => s"-> (sym = ${t.legacyResolvedSym}, typ = ${t.resolvedTyp})" ): // If the term has an expansion already, it is likely that there is // an internal error because otherwise we should resolve the symbol @@ -884,7 +884,7 @@ class Resolver(tl: TraceLogger) log(s"Resolving symbol for selection ${t}, defn = ${lhs.defn}") val clsDefn = cls.flatMap: sym => - sym.resolvedSym.flatMap(_.asCls).map: clsSym => + sym.legacyResolvedSym.flatMap(_.asCls).map: clsSym => clsSym.defn.getOrElse(die) // Singleton Definitions @@ -939,7 +939,7 @@ class Resolver(tl: TraceLogger) case t @ Term.New(cls, _, N) => // cls is already resolved, so the symbol should be present; // otherwise, there is already an error raised. - cls.resolvedSym match + cls.legacyResolvedSym match case S(clsSym: ClassSymbol) => t.expand(S(t.duplicate.resolved(clsSym))) case S(sym) => @@ -958,7 +958,7 @@ class Resolver(tl: TraceLogger) */ def resolveType(t: Resolvable, prefer: Expect)(using ictx: ICtx): Unit = t.expandedResolvableIn: t => trace[Unit]( - s"Resolving the type for term: ${t} (prefer = ${prefer}, sym = ${t.resolvedSym})", + s"Resolving the type for term: ${t} (prefer = ${prefer}, sym = ${t.legacyResolvedSym})", _ => s"-> typ = ${t.resolvedTyp}" ): def disambSym(bms: BlockMemberSymbol): Opt[DefinitionSymbol[?]] = prefer match @@ -969,7 +969,7 @@ class Resolver(tl: TraceLogger) case _: PatternConstructor => TODO("disambSym for PatternConstructor") t match - case Term.New(cls, _, N) => cls.resolvedSym match + case Term.New(cls, _, N) => cls.legacyResolvedSym match case S(clsSym: ClassSymbol) => val ty = Type.Ref(clsSym, Nil) t.expand(S(t.withTyp(ty))) @@ -978,7 +978,7 @@ class Resolver(tl: TraceLogger) // otherwise, there is already an error raised. () case t @ Apps(base: Resolvable, ass) => - val decl = base.resolvedSym match + val decl = base.legacyResolvedSym match case S(bms: BlockMemberSymbol) => val disambBms = disambSym(bms) log(s"Disambiguate ${bms} (${bms.asTrm}) into ${disambBms} (defn = ${disambBms.map(_.defn)}), preferring ${prefer}") @@ -1179,7 +1179,7 @@ class Resolver(tl: TraceLogger) else Type.NotImplemented // TODO: Support complex types // Otherwise, resolve the term directly. - case _ => t.resolvedSym match + case _ => t.legacyResolvedSym match // A VarSymbol is probably a type parameter. case S(sym: VarSymbol) if ModuleChecker.isTypeParam(sym) => if expect.module @@ -1252,6 +1252,6 @@ object ModuleChecker: case N => false case _ => false - def isStaticClass(t: Term): Bool = t.resolvedSym.exists(_.asCls.isDefined) + def isStaticClass(t: Term): Bool = t.legacyResolvedSym.exists(_.asCls.isDefined) end ModuleChecker diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala index df4327fc98..459c6de930 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala @@ -90,8 +90,8 @@ enum SimpleSplit extends AutoLocated with ProductWithTail: /** This field is designed to be compatible with bbML. */ private var _expandedSplit: Opt[Split] = N - /** */ - def getExpandedSplit(using TL, Ctx, State, Raise, Config): Split = _expandedSplit.getOrElse: + /** Expand patterns after resolution, with symbol queries authorized by lowering. */ + def getExpandedSplit(using codegen.Lowering, TL, Ctx, State, Raise, Config): Split = _expandedSplit.getOrElse: val split = Split.from(this) _expandedSplit = S(split) split diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index 72f1059a80..faeea86f6a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -175,7 +175,7 @@ object Split: import Term.Ref /** Desugar a `SimpleSplit` into a `Split`. */ - def from(rootSplit: SS)(using tl: TL)(using Ctx, Raise, State, Config): Split = + def from(rootSplit: SS)(using codegen.Lowering)(using tl: TL)(using Ctx, Raise, State, Config): Split = val compiler = new SplitCompiler() val scrutCache = MutMap.empty[Ref, Scrut] def go(split: SS): Split = split match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 9a0fc77931..11578c681d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -471,6 +471,13 @@ sealed trait ClassLikeSymbol extends IdentifiedSymbol, HasErasedType: val tree: Tree.TypeDef def subst(using sub: SymbolSubst): ClassLikeSymbol + /** Published by lowering before class bodies are lowered, including for external declarations. + * Full IR class definitions refresh this header when rewritten, just as they own `irDefn`. + * Keeping the header separately is necessary for forward parents and declarations without + * executable bodies; consumers must not reconstruct it from semantic terms. + */ + var irClassHeader: Opt[codegen.ClassHeader] = N + type AnyDefinitionSymbol = DefinitionSymbol[?] diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 08cdf07361..f75c5ad83d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -238,7 +238,7 @@ sealed trait ResolvableImpl extends ShapeHost: def hasExpansion = expansion.isDefined - def defn: Opt[Definition] = resolvedSym match + def defn: Opt[Definition] = legacyResolvedSym match case S(sym: BlockMemberSymbol) => N case S(sym: AnyDefinitionSymbol) => sym.defn case _ => N @@ -508,9 +508,10 @@ enum Term extends Statement, ShapePublisher: /** * The symbol representing the evaluation result of the term. This - * symbol is resolved during the resolution stage. + * symbol is resolved during the resolution stage. Reading its final value requires + * lowering; elaboration must listen for shapes instead. */ - def resolvedSym: Opt[Symbol] = expanded match + def resolvedSym(using codegen.Lowering): Opt[Symbol] = expanded match case res: Resolved => S(res.sym) case SimpleRef(sym) => S(sym) case Capture(base, _) => base.resolvedSym @@ -527,6 +528,15 @@ enum Term extends Statement, ShapePublisher: case app: TyApp => app.lhs.resolvedSym case _ => N + /** The old resolver may inspect its own expansions while resolving legacy terms. + * This deliberately rejects new references: their candidate sets are not final yet. + */ + private[semantics] def legacyResolvedSym: Opt[Symbol] = expanded match + case _: NewResolvable | _: NewRefImpl | _: Capture => + lastWords("Legacy symbol query on a new-resolution term") + case app: TyApp => app.lhs.legacyResolvedSym + case term => term.symbol + def resolvedTyp: Opt[Type] = expanded match case res: Resolved => res.typ case ref: Ref => ref.typ @@ -808,8 +818,8 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: def extraInfo(using DebugPrinter): Str = this match case s: AnySel if s.resolvedTargets.nonEmpty => s"targets=${s.resolvedTargets.map(_.showAsPlain).mkString("[", ",", "]")}" - case r: Resolvable if r.resolvedSym.isDefined || r.resolvedTyp.isDefined => ( - r.resolvedSym.map(s => s"sym=${s.showAsPlain}") :: + case r: Resolvable if r.legacyResolvedSym.isDefined || r.resolvedTyp.isDefined => ( + r.legacyResolvedSym.map(s => s"sym=${s.showAsPlain}") :: r.resolvedTyp.map(s => s"typ=${s.showDbg}") :: Nil ).flatten.mkString(",") case r: SelProj => r.symbol.mkString diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala index ded6faa872..28fb864d11 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -182,7 +182,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case sel @ AnySel(pre, nme, cls) => log(s"Selection ${sel.showDbg} ${sel.typ}") checkLDS(pre): pre_t => - sel.resolvedSym match + sel.legacyResolvedSym match case S(sym: BlockMemberSymbol) => log(s"RES ${sym.nme} in ${sel.showDbg}") getFlowSymOrType(sym) @@ -199,7 +199,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case nw @ New(cls, args, rft) => rft match case N => - cls.resolvedSym.flatMap(_.asCls) match + cls.legacyResolvedSym.flatMap(_.asCls) match case N => log(s"Unresolved or invalid class symbol in ${cls.showDbg}") P.Unknown(nw) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index 0916726691..740bb40b0f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -10,6 +10,8 @@ import codegen.Lowering class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, Config) extends TermSynthesizer: + private given Lowering = lowering + import Normalization.*, Mode.* import tl.* @@ -639,14 +641,6 @@ object Normalization: !compareCasePattern(lhs, rhs) && !compareCasePattern(rhs, lhs) case _ => false - /** Get the parent class-like symbol from the extends clause of a class or module. */ - private def getParentClassLikeSymbol(sym: ClassSymbol | ModuleOrObjectSymbol) - : Opt[ClassSymbol | ModuleOrObjectSymbol] = - val ext: Opt[Term.New] = sym match - case cls: ClassSymbol => cls.defn.flatMap(_.ext) - case mod: ModuleOrObjectSymbol => mod.defn.flatMap(_.ext) - ext.flatMap(nw => nw.cls.resolvedSym.flatMap(_.asClsOrMod)) - /** Check if `child` is a subclass of `parent` by traversing the class hierarchy. * Uses a visited set to avoid infinite loops in case of cyclic inheritance. * TODO: Cache the subclasses set!! */ @@ -656,7 +650,7 @@ object Normalization: ): Bool = def go(sym: ClassSymbol | ModuleOrObjectSymbol, visited: Set[ClassSymbol | ModuleOrObjectSymbol]): Bool = - !visited.contains(sym) && (getParentClassLikeSymbol(sym) match + !visited.contains(sym) && (sym.irClassHeader.flatMap(_.parent).flatMap(_.asClsOrMod) match case S(parentSym) => (parentSym is parent) || go(parentSym, visited + sym) case N => false) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala index 077ad86c69..69d11c327e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala @@ -154,7 +154,7 @@ object FixedPointCompiler: * machine compilation does not support get a warning and fall back to the * naive backtracking translation. */ -class FixedPointCompiler(using tl: TL)(using State, Ctx, Raise) extends TermSynthesizer: +class FixedPointCompiler(using codegen.Lowering)(using tl: TL)(using State, Ctx, Raise) extends TermSynthesizer: import FixedPointCompiler.*, tl.* /** Try to compile the given `@compile`-annotated pattern into a fixed-point diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala index 750c89553f..96fad6cb49 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala @@ -14,7 +14,7 @@ import scala.collection.immutable.SeqMap object Instantiator: type Subst = Map[VarSymbol, Pat] -class Instantiator(using tl: TL)(using Ctx, State, Raise): +class Instantiator(using codegen.Lowering)(using tl: TL)(using Ctx, State, Raise): import tl.* /** The map contains all instantiated patterns. The key is a pattern symbol diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala index f962e04ce3..c8faa7768d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala @@ -119,7 +119,7 @@ object SplitCompiler: import SplitCompiler.* /** This class compiles a pattern to a split that matches the pattern. */ -class SplitCompiler(using tl: TL)(using State, Ctx, Raise, Config) extends TermSynthesizer: +class SplitCompiler(using codegen.Lowering)(using tl: TL)(using State, Ctx, Raise, Config) extends TermSynthesizer: import tl.*, SP.* val newResolution: Bool = config.language.useNewResolution diff --git a/hkmc2/shared/src/test/mlscript/newres/IRClassHeaders.mls b/hkmc2/shared/src/test/mlscript/newres/IRClassHeaders.mls new file mode 100644 index 0000000000..3c1b986655 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/IRClassHeaders.mls @@ -0,0 +1,19 @@ +:. + +// Functions are lowered before class bodies. Their pattern specialization must +// already know the inheritance chain, including nested class declarations. +:expect 1 +fun classify(x) = + if x is Classes.Child and x is Classes.Parent then 1 else 2 +module Classes with + class Parent + class Child extends Parent +classify(new Classes.Child) +//│ = 1 + +// Prelude classes have no executable definitions to supply their parent links. +:expect 1 +fun classifyNumber(x) = + if x is Int and x is Num then 1 else 2 +classifyNumber(42) +//│ = 1 diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala index 5c1f705e54..e253af323a 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala @@ -37,8 +37,11 @@ abstract class InvalMLDiffMaker extends JSBackendDiffMaker: super.processTerm(trm, inImport) if invalmlOpt.isSet then given Scope = Scope.empty(Scope.Cfg.default) + given Elaborator.Ctx = curCtx + given codegen.Lowering = new codegen.Lowering() + // Typing may lower patterns even when executable code generation is disabled. + summon[codegen.Lowering].classHeaders(trm) if invalmlTyper.isEmpty then - given Elaborator.Ctx = curCtx invalmlTyper = S(InvalTyper()) given hkmc2.invalml.InvalCtx = invalCtx.copy(raise = summon) val typer = invalmlTyper.get From d4917fae5cd1d7c0d13f8ca626e2857ada706a7d Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 18:03:42 +0800 Subject: [PATCH 030/228] Migrate initial core test suites to new resolution Add deferred type interpretation and erasure, migrate 184 core and application worksheets and ten additional WASM files, and retain blocked tests on their previous configurations. Fix named-field interpretation, synthetic assertions, free-variable tracking, type-application shape propagation, and arity diagnostics. Include updated golden outputs and the remaining migration plan. WASM Basics remains unmigrated pending the annotated-receiver capture fix. Validation: hkmc2AllTests/test passes. --- doc/new-resolution-suite-migration.md | 467 ++++++++++++++++++ doc/new-resolution-wasm.md | 29 +- .../src/main/scala/hkmc2/CompilerCtx.scala | 2 +- .../main/scala/hkmc2/codegen/ErasedType.scala | 9 + .../main/scala/hkmc2/codegen/Lowering.scala | 19 +- .../scala/hkmc2/semantics/Elaborator.scala | 22 +- .../scala/hkmc2/semantics/NewResolver.scala | 150 ++++-- .../src/main/scala/hkmc2/semantics/Term.scala | 15 +- .../scala/hkmc2/semantics/TypeShape.scala | 77 +++ hkmc2/shared/src/test/mlscript/apps/.mls | 3 + .../src/test/mlscript/apps/parsing/.mls | 1 + .../mlscript/apps/parsing/CamlLightTest.mls | 1 + .../mlscript/apps/parsing/LoopExpressions.mls | 1 + .../mlscript/apps/parsing/ParserErrorTest.mls | 1 + .../test/mlscript/apps/parsing/ParserTest.mls | 1 + .../test/mlscript/apps/parsing/TypeTest.mls | 1 + hkmc2/shared/src/test/mlscript/basics/.mls | 3 + .../shared/src/test/mlscript/basics/ADTs.mls | 5 +- .../shared/src/test/mlscript/basics/AppOp.mls | 3 +- .../src/test/mlscript/basics/Assert.mls | 11 +- .../src/test/mlscript/basics/BadClasses.mls | 17 +- .../test/mlscript/basics/BadConstructor.mls | 11 +- .../src/test/mlscript/basics/BadDefs.mls | 11 +- .../mlscript/basics/BadMemberProjections.mls | 61 ++- .../src/test/mlscript/basics/BadModules.mls | 3 +- .../src/test/mlscript/basics/BadMut.mls | 7 +- .../src/test/mlscript/basics/BadParams.mls | 21 +- .../src/test/mlscript/basics/BlockArgs.mls | 11 +- .../mlscript/basics/ByNameTailPosition.mls | 1 + .../src/test/mlscript/basics/CallSyntaxes.mls | 5 +- .../mlscript/basics/CompanionFunctions.mls | 16 +- .../basics/CompanionModules_Patterns.mls | 5 +- .../basics/CompanionModules_Types.mls | 1 + .../src/test/mlscript/basics/CyclicValues.mls | 1 + .../src/test/mlscript/basics/DataClass.mls | 9 +- .../src/test/mlscript/basics/Declare.mls | 35 +- .../mlscript/basics/DisruptiveComments.mls | 1 + .../shared/src/test/mlscript/basics/Drop.mls | 1 + .../mlscript/basics/ExplicitSelections.mls | 1 + .../test/mlscript/basics/FunnyRecordKeys.mls | 1 + .../src/test/mlscript/basics/Getters.mls | 3 +- .../src/test/mlscript/basics/IdentEscape.mls | 7 +- .../test/mlscript/basics/IfThenNewline.mls | 1 + .../src/test/mlscript/basics/Indentation.mls | 17 +- .../mlscript/basics/LabeledIfWhileBodies.mls | 1 + .../src/test/mlscript/basics/LazySpreads.mls | 15 +- .../src/test/mlscript/basics/LetBindings.mls | 1 + .../src/test/mlscript/basics/Literals.mls | 21 +- .../src/test/mlscript/basics/LocalVal.mls | 1 + .../mlscript/basics/MemberProjections.mls | 79 +-- .../src/test/mlscript/basics/Modules.mls | 14 +- .../test/mlscript/basics/MultiParamLists.mls | 1 + .../mlscript/basics/MultilineExpressions.mls | 15 +- .../src/test/mlscript/basics/MutCls.mls | 1 + .../src/test/mlscript/basics/NamedArgs.mls | 7 +- .../src/test/mlscript/basics/NestedBlocks.mls | 40 +- .../mlscript/basics/NewlineOperatorEnd.mls | 1 + .../src/test/mlscript/basics/OfLambdaArgs.mls | 19 +- .../src/test/mlscript/basics/OpBlocks.mls | 27 +- .../test/mlscript/basics/OpDivPrecedence.mls | 1 + .../src/test/mlscript/basics/OutParams.mls | 11 +- .../src/test/mlscript/basics/PartialApps.mls | 51 +- .../src/test/mlscript/basics/PostfixOps.mls | 17 +- .../mlscript/basics/PureTermStatements.mls | 29 +- .../src/test/mlscript/basics/Return.mls | 23 +- .../src/test/mlscript/basics/SetIndented.mls | 13 +- .../mlscript/basics/ShortcircuitingOps.mls | 1 + .../mlscript/basics/SuspensionComments.mls | 1 + .../basics/SymbolicSuffixIdentifiers.mls | 7 +- .../src/test/mlscript/basics/ThenRecord.mls | 1 + .../src/test/mlscript/basics/TryFinally.mls | 11 +- .../src/test/mlscript/basics/WeirdCalls.mls | 31 +- hkmc2/shared/src/test/mlscript/codegen/.mls | 3 + .../mlscript/codegen/BadConfigDirective.mls | 9 +- .../test/mlscript/codegen/BadFunctions.mls | 5 +- .../src/test/mlscript/codegen/BadImport.mls | 3 +- .../src/test/mlscript/codegen/BadInit.mls | 1 + .../src/test/mlscript/codegen/BadSpreads.mls | 22 +- .../src/test/mlscript/codegen/BadValInit.mls | 1 + .../src/test/mlscript/codegen/BadWhile.mls | 7 +- .../src/test/mlscript/codegen/Bindings.mls | 13 +- .../src/test/mlscript/codegen/BuiltinOps.mls | 7 +- .../test/mlscript/codegen/BuiltinSymbols.mls | 13 +- .../src/test/mlscript/codegen/CaseOfCase.mls | 1 + .../test/mlscript/codegen/CaseShorthand.mls | 7 +- .../test/mlscript/codegen/CheckedCasts.mls | 1 + .../test/mlscript/codegen/ClassInClass.mls | 3 +- .../src/test/mlscript/codegen/ClassInFun.mls | 14 +- .../src/test/mlscript/codegen/Classes.mls | 1 + .../test/mlscript/codegen/CodegenScratch.mls | 1 + .../src/test/mlscript/codegen/Comma.mls | 1 + .../mlscript/codegen/CurriedFunctions.mls | 1 + .../test/mlscript/codegen/DelayedLetInit.mls | 27 +- .../src/test/mlscript/codegen/EarlyReturn.mls | 1 + .../src/test/mlscript/codegen/ElseLess.mls | 1 + .../test/mlscript/codegen/FieldSymbols.mls | 23 +- .../src/test/mlscript/codegen/Formatting.mls | 1 + .../src/test/mlscript/codegen/FunInClass.mls | 1 + .../src/test/mlscript/codegen/Functions.mls | 5 +- .../test/mlscript/codegen/FunctionsThis.mls | 1 + .../src/test/mlscript/codegen/FunnyOpen.mls | 19 +- .../src/test/mlscript/codegen/GlobalThis.mls | 1 + .../src/test/mlscript/codegen/IfThenElse.mls | 7 +- .../codegen/ImperativeConditionals.mls | 15 +- .../test/mlscript/codegen/ImportConflicts.mls | 5 +- .../test/mlscript/codegen/InlineLambdas.mls | 1 + .../mlscript/codegen/InlineMultiArgLists.mls | 1 + .../mlscript/codegen/InnerNameHygiene.mls | 1 + .../test/mlscript/codegen/Juxtapositions.mls | 62 ++- .../src/test/mlscript/codegen/Lambdas.mls | 5 +- .../shared/src/test/mlscript/codegen/Lazy.mls | 1 + .../src/test/mlscript/codegen/LetPatterns.mls | 11 +- .../test/mlscript/codegen/MergeMatchArms.mls | 107 ++-- .../test/mlscript/codegen/ModuleMatching.mls | 1 + .../src/test/mlscript/codegen/NestedLoops.mls | 1 + .../src/test/mlscript/codegen/NestedTypes.mls | 1 + .../src/test/mlscript/codegen/ObjectInit.mls | 1 + .../src/test/mlscript/codegen/OpenJS.mls | 1 + .../src/test/mlscript/codegen/OptMatch.mls | 1 + .../src/test/mlscript/codegen/Primes.mls | 45 +- .../codegen/PrivateMemberExtrusion.mls | 1 + .../test/mlscript/codegen/RecordSpreads.mls | 3 +- .../shared/src/test/mlscript/codegen/Repl.mls | 23 +- .../src/test/mlscript/codegen/SetIn.mls | 1 + .../mlscript/codegen/ShortCircuitReturn.mls | 1 + .../test/mlscript/codegen/SimplePatMat.mls | 3 +- .../test/mlscript/codegen/SingletonInit.mls | 5 +- .../test/mlscript/codegen/SpecialJSNames.mls | 1 + .../mlscript/codegen/SwitchSpecialization.mls | 1 + .../mlscript/codegen/TailRecFormerFailure.mls | 1 + .../test/mlscript/codegen/TraceLogIndent.mls | 22 +- .../mlscript/codegen/TypedIRShortCircuit.mls | 5 +- .../test/mlscript/codegen/WhileDefaults.mls | 1 + .../shared/src/test/mlscript/codegen/i382.mls | 1 + .../src/test/mlscript/newres/Functions.mls | 2 +- .../src/test/mlscript/newres/PatMat.mls | 8 +- .../test/mlscript/newres/TypeApplications.mls | 15 + .../test/mlscript/newres/TypeResolution.mls | 59 +++ hkmc2/shared/src/test/mlscript/ucs/.mls | 3 + .../src/test/mlscript/ucs/AndNewline.mls | 1 + .../src/test/mlscript/ucs/examples/.mls | 1 + .../mlscript/ucs/examples/Permutations.mls | 1 + .../test/mlscript/ucs/examples/SimpleTree.mls | 1 + .../shared/src/test/mlscript/ucs/future/.mls | 1 + .../test/mlscript/ucs/future/AppSplits.mls | 17 +- .../src/test/mlscript/ucs/future/Or.mls | 5 +- .../mlscript/ucs/future/SymbolicClass.mls | 35 +- .../shared/src/test/mlscript/ucs/general/.mls | 1 + .../test/mlscript/ucs/general/CardSuits.mls | 1 + .../mlscript/ucs/general/CrossModules.mls | 1 + .../test/mlscript/ucs/general/DualOptions.mls | 1 + .../mlscript/ucs/general/InterleavedLet.mls | 1 + .../test/mlscript/ucs/general/JoinPoints.mls | 1 + .../src/test/mlscript/ucs/general/List.mls | 1 + .../ucs/general/LogicalConnectives.mls | 7 +- .../src/test/mlscript/ucs/general/Seqs.mls | 1 + .../src/test/mlscript/ucs/general/Simple.mls | 1 + .../shared/src/test/mlscript/ucs/hygiene/.mls | 1 + .../ucs/hygiene/CrossBranchCapture.mls | 19 +- .../src/test/mlscript/ucs/hygiene/Hygiene.mls | 1 + .../src/test/mlscript/ucs/hygiene/PatVars.mls | 1 + .../src/test/mlscript/ucs/normalization/.mls | 1 + .../ucs/normalization/DeduplicationWhile.mls | 43 +- .../normalization/ExcessiveDeduplication.mls | 56 ++- .../ucs/normalization/SimplePairMatches.mls | 1 + .../normalization/SpecializedSplitSharing.mls | 48 +- .../ucs/normalization/UnifySubScrutinees.mls | 1 + .../ucs/normalization/UnifyTupleElements.mls | 1 + .../shared/src/test/mlscript/ucs/papers/.mls | 1 + .../mlscript/ucs/papers/OperatorSplit.mls | 1 + .../src/test/mlscript/ucs/patterns/.mls | 1 + .../mlscript/ucs/patterns/AliasPattern.mls | 1 + .../mlscript/ucs/patterns/Compilation.mls | 31 +- .../ucs/patterns/GuardedPatternBindings.mls | 17 +- .../test/mlscript/ucs/patterns/Literals.mls | 3 +- .../mlscript/ucs/patterns/NamePattern.mls | 1 + .../test/mlscript/ucs/patterns/Parameters.mls | 58 ++- .../test/mlscript/ucs/patterns/Refinement.mls | 56 +-- .../test/mlscript/ucs/patterns/RestTuple.mls | 1 + .../mlscript/ucs/patterns/SimpleTuple.mls | 16 +- .../ucs/patterns/SpreadTrailingIndex.mls | 1 + .../shared/src/test/mlscript/ucs/syntax/.mls | 1 + .../src/test/mlscript/ucs/syntax/And.mls | 5 +- .../test/mlscript/ucs/syntax/BadUCSSyntax.mls | 15 +- .../src/test/mlscript/ucs/syntax/Case.mls | 5 +- .../mlscript/ucs/syntax/ConjunctMatches.mls | 1 + .../src/test/mlscript/ucs/syntax/Do.mls | 9 +- .../test/mlscript/ucs/syntax/DoProblems.mls | 11 +- .../src/test/mlscript/ucs/syntax/Empty.mls | 3 +- .../test/mlscript/ucs/syntax/IfOpSplit.mls | 19 +- .../src/test/mlscript/ucs/syntax/Is.mls | 1 + .../mlscript/ucs/syntax/NestedOpSplits.mls | 7 +- .../src/test/mlscript/ucs/syntax/Of.mls | 1 + .../mlscript/ucs/syntax/PlainConditionals.mls | 9 +- .../src/test/mlscript/ucs/syntax/Split.mls | 1 + .../test/mlscript/ucs/syntax/TupleRest.mls | 1 + .../test/mlscript/ucs/syntax/WithBraces.mls | 1 + hkmc2/shared/src/test/mlscript/ups/.mls | 3 + .../src/test/mlscript/ups/BadPatterns.mls | 11 +- .../src/test/mlscript/ups/JoinPatterns.mls | 1 + .../src/test/mlscript/ups/RangePatterns.mls | 16 +- .../src/test/mlscript/ups/examples/.mls | 1 + .../ups/examples/BadRecStackParse.mls | 1 + hkmc2/shared/src/test/mlscript/ups/regex/.mls | 1 + .../test/mlscript/ups/regex/EmptyString.mls | 1 + .../src/test/mlscript/ups/regex/Number.mls | 1 + .../mlscript/ups/regex/Simplification.mls | 1 + .../shared/src/test/mlscript/ups/syntax/.mls | 1 + .../mlscript/ups/syntax/CrossCompilation.mls | 1 + .../test/mlscript/ups/syntax/Declaration.mls | 9 +- .../test/mlscript/ups/syntax/Precedence.mls | 1 + .../mlscript/ups/syntax/WrongArguments.mls | 13 +- .../test/mlscript/ups/syntax/WrongArity.mls | 15 +- .../src/test/mlscript/ups/transformation/.mls | 1 + .../transformation/BasicTransformations.mls | 1 + .../test/mlscript/wasm/BuiltinOperators.mls | 29 +- hkmc2/shared/src/test/mlscript/wasm/Casts.mls | 13 +- .../src/test/mlscript/wasm/ClassMethods.mls | 16 +- .../src/test/mlscript/wasm/ControlFlow.mls | 3 +- .../src/test/mlscript/wasm/Exceptions.mls | 2 - .../mlscript/wasm/NumericIntrinsicReview.mls | 11 +- .../src/test/mlscript/wasm/NumericTypes.mls | 21 +- .../src/test/mlscript/wasm/ReplImports.mls | 3 +- .../test/mlscript/wasm/TailRecOptCasts.mls | 19 +- .../shared/src/test/mlscript/wasm/Tuples.mls | 3 +- .../src/test/mlscript/wasm/TypeResolution.mls | 10 + .../src/test/mlscript/wasm/VirtualMethods.mls | 9 +- .../test/scala/hkmc2/InvalmlDiffMaker.scala | 2 +- 228 files changed, 1973 insertions(+), 901 deletions(-) create mode 100644 doc/new-resolution-suite-migration.md create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala create mode 100644 hkmc2/shared/src/test/mlscript/apps/.mls create mode 100644 hkmc2/shared/src/test/mlscript/apps/parsing/.mls create mode 100644 hkmc2/shared/src/test/mlscript/basics/.mls create mode 100644 hkmc2/shared/src/test/mlscript/codegen/.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/TypeApplications.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls create mode 100644 hkmc2/shared/src/test/mlscript/ucs/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ucs/examples/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ucs/future/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ucs/general/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ucs/hygiene/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ucs/normalization/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ucs/papers/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ucs/patterns/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ucs/syntax/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ups/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ups/examples/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ups/regex/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ups/syntax/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ups/transformation/.mls create mode 100644 hkmc2/shared/src/test/mlscript/wasm/TypeResolution.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md new file mode 100644 index 0000000000..e12ecacbe8 --- /dev/null +++ b/doc/new-resolution-suite-migration.md @@ -0,0 +1,467 @@ +# New-resolution suite migration + +## Scope and retained milestone + +The first pass tried all 372 active `.mls` files in `basics`, `codegen`, `ucs`, +`ups`, and `apps` with new resolution and strict resolution. The 51 files under +`ucs/staging` are excluded by the test runner and remain unchanged. Counts below +exclude the new shared `.mls` configuration files and new regression tests. + +| Suite | Migrated | Retained on existing configuration | Active files | +| --- | ---: | ---: | ---: | +| basics | 55 | 35 | 90 | +| codegen | 61 | 64 | 125 | +| ucs | 50 | 20 | 70 | +| ups | 13 | 58 | 71 | +| apps | 5 | 11 | 16 | +| Total | 184 | 188 | 372 | + +Migrated files start with `:.`. Each suite's `.mls` loads the common language +configuration and enables strict resolution; nested directories inherit through +`:..`. The suite defaults disable JS execution so each file retains its original +execution flags. A parser-only test must not acquire runtime execution merely +because its resolution configuration changes. + +Blocked files retain their complete original sources and golden outputs. No new +`:todo`, `:fixme`, `:ignore`, or loose-resolution exception hides a migration +failure. Existing expected failures remain visible. The inventory below records +trial observations, not newly accepted failures. + +The five migrated application worksheets still import compilation fixtures in +their existing mode. This is caller-side migration, not a claim that all imported +application implementations use new resolution. The 20 application compilation +fixtures and four `mlscript-compile/ups` fixtures need a subsequent dependency-ordered +migration. Their shared consumers make a blanket flag change inappropriate for +this first milestone. + +The unfinished WASM/type-erasure changes predate this migration and are retained. +In particular, the typed-constructor-field capture assertion is still outstanding. +Method calls across REPL blocks remain assigned to the other branch. + +## Straightforward fixes applied + +1. Named record/tuple fields elaborate their values with the enclosing + interpretation. `value: expression` inside a term tuple is not a type + annotation. Existing `basics/NamedArgs.mls` exercises this distinction. +2. Compiler-generated `runtime.assertFail` calls use a synthetic selection, like + other runtime helpers. This is not a fallback for unresolved source selections. + `basics/Assert.mls` and `DisruptiveComments.mls` retain runtime assertion tests. +3. Free-variable collection recognizes the new direct reference forms. Pattern + guards no longer produce spurious unused-binding warnings; see + `ucs/patterns/GuardedPatternBindings.mls`. +4. Type applications propagate the underlying shape, preserving the requested + interpretation and capture handling. Class validation and lowering look + through erased type arguments. `newres/TypeApplications.mls` checks generic + construction, inheritance, and function-result member lookup. This does not + implement generic substitution or missing type-argument validation. +5. Pattern compilation annotations forward binding-shape propagation to the + annotated pattern. Remaining pattern forms and matcher synthesis are separate + work, described below. +6. Argument matching retains the original parameter and argument counts for + diagnostics. A two-argument call with one supplied argument no longer reports + the counts of the unmatched tails. +7. The assertion test explicitly constructs `new Error(...)`. An invalid + selection from a declared class now expects its compilation error in + `basics/Declare.mls`. + +One intended semantic change is visible in `codegen/ImportConflicts.mls`: an +explicitly bound `Some5` wins over a wildcard open, yielding `"555"` rather than +`5`. That follows the already-agreed wildcard-open rule. + +## Design questions and proposed decisions + +### 1. Structural members and opaque values + +Evidence: `basics/LiteralSelection.mls` fails on `arr.1` and `obj.a` because +`IntroShape.getMemberImpl` has unimplemented tuple and record cases. The current +`MemberInfo` requires a `BlockMemberSymbol`, which a structural field need not +have. `codegen/ImportJSModule.mls`, `Pwd.mls`, and many app worksheets instead +have opaque external results with no member shapes at all. + +**Question:** should ordinary member selection on an opaque external value be +accepted dynamically, or require a declaration/explicit dynamic selection? +This is different from a known record's structural member. + +**Proposal:** preserve strict resolution for ordinary source selections. Add a +member-target representation distinguishing nominal definitions from structural +fields, and an explicit opaque/dynamic category where the language authorizes +it. Structural targets carry their field/index path and value-shape publisher; +they must not invent nominal symbols or discard the selected value's provenance. +Resolve record overwrites and spreads in source order, independently of listener +arrival order. Lowering consumes those targets without doing lookup. Publish +external signatures through the same shape infrastructure; keep explicitly +dynamic operations on a dedicated path. + +Acceptance cases: duplicate record fields, spreads arriving late, tuple indices, +field values used as receivers, field assignment, missing fields, and JS imports. +Decide whether wildcard opens accept structural records as part of this work; +do not silently treat an unsupported structural open as an empty module. + +### 2. Annotations that affect elaboration + +Evidence: `codegen/NoInline.mls` warns that `@noInline` has no effect and then +inlines `bar`. `Elaborator.annot` still uses eager `trm.symbol`. The trial of +`codegen/Generators.mls` exceeded the 25-second test limit; lost generator +recognition is a concrete suspect, not yet a proven explanation of that timeout. + +**Question:** may elaboration-affecting annotations be obtained through forward +aliases and wildcard opens, just like other symbolic references? + +**Proposal:** yes. Resolve annotation identity through completed symbolic +candidates and schedule dependent elaboration after that prerequisite is known. +Separate annotations needed to elaborate a body (`generator`, `async`) from +metadata only consumed later (`noInline`, `tailrec`). Diagnose ambiguity or a +dependency cycle explicitly. Do not recognize annotations by spelling, inspect +incomplete definitions, or call `resolvedSym` during elaboration. + +Acceptance cases: direct, qualified, explicit-open, wildcard-open, shadowed, +forward, ambiguous, and cyclic annotation references. Check emitted function +kind and optimization behavior, not only final values. This should be addressed +first because it can change valid program behavior without an ordinary error. + +### 3. Pattern transfer and synthesized references + +Evidence: new shape propagation lacks record, conjunction, negation, string +concatenation, and transformation cases. Examples include +`ucs/general/BooleanPatterns.mls`, `patterns/String.mls`, and +`normalization/RecordImpliedByClass.mls`. Tuple bindings currently publish no +shapes. `CompiledQualifiedConstructors.mls` reaches `Term.mkClone` with a +`NewSel`; `CompiledClassPatterns.mls` reaches it with a `MemberRef`. Many UPS +failures combine these gaps with synthesized matcher selections. + +**Question:** what resolution state should synthesized matcher references share +with source references, and what precision should recursive/transformed pattern +bindings promise? + +**Proposal:** retain the source reference's completed target and receiver path +when lowering synthesizes matcher code; use the existing `Lowering` capability +to obtain that result. Do not clone listener hosts with empty candidate sets or +re-run name lookup from the generated tree. Add explicit pattern transfer rules +that distinguish matched input shapes, bound-field shapes, and transformed +output shapes. Compose alternatives by union and conjunctions by successive +filtering; negation must not invent positive bindings. Reuse a dependency graph +with cycle-aware subscriptions for recursive patterns. Establish convergence +before trying to fix recursive cases by adding recursion limits. + +Acceptance cases: bound members after tuple/record extraction, nested constructors, +aliases, guards, transformed results, repeated references, qualified imported +constructors, and recursive UPS matchers. Compare results and generated matcher +structure with the existing tests. `ups/examples/HindleyMilner.mls` also timed +out during the trial and needs an isolated reproducer before assigning its cause. + +### 4. Declared interfaces versus inferred values + +Evidence: the prior WASM `Basics` failure mixes a nominal annotated receiver with +an untyped constructor field carrying a different capture context. +`codegen/CurriedClassInheritance.mls` and `ParamClasses.mls` also expose capture +or receiver-shape problems; they are not all necessarily the same bug. + +**Question:** does `b: Base` restrict member lookup to Base's declared interface, +or may concrete call arguments expose subclass-only members? This remains the +unanswered question from the preceding phase. + +**Proposal:** use the declared interface for member availability, preserving +virtual dispatch. Keep value-flow provenance separate from that interface so +reading a field cannot feed constructor entry marks into an unrelated function +exit. Publish declared parameter, result, and field types consistently, including +functions with no observed calls. Never fix the assertion by dropping marks. + +Acceptance cases: an unused annotated function, an unannotated constructor field, +subclass overrides, inherited fields, curried constructors, nested captures, and +generic aliases. Coordinate REPL method cases with the other branch. + +### 5. Dynamic construction and foreign callable classes + +Evidence: `basics/DynamicInstantiation.mls` and `codegen/ImportJSClass.mls` reach +static class-lowering assumptions for explicitly dynamic constructions. +`PredefUsage.mls` calls JavaScript's `String` as a function, while its current +declaration supplies only a module interpretation. Bare MLscript classes also +reject old empty-call syntax, as intended by the earlier migration. + +**Question:** how should foreign declarations expose both call and constructor +capabilities, without making every module or bare class implicitly callable? + +**Proposal:** give foreign declarations explicit callable/constructible +capabilities using the existing overload interpretation mechanism. Lower +explicitly dynamic construction directly as dynamic IR, distinct from static +class construction. Keep static `new` on the listener-resolved class path. +Change test syntax only where that preserves the test's intent; tests explicitly +about callable JS constructors must not be rewritten to avoid that feature. + +Acceptance cases: imported JS classes, callable-and-constructible externals, +plain MLscript classes, companion values, dynamic constructor expressions, and +errors when a selected interpretation lacks the required capability. + +## Remaining implementation work, not new semantic decisions + +- Port the old modulefulness, generic-arity, and implicit-argument checks to + completed shape/signature subscriptions. `basics/BadModuleUses.mls`, + `GenericClasses.mls`, `BadTypeClasses.mls`, and `codegen/NoModuleCheck.mls` + currently lose expected errors. Do not remove their expectations to turn the + migration green. +- Implement deferred validation for calls, spread distribution, curried + constructors, and returned function shapes. Distinguish an unknown-length + spread from a proven arity mismatch; the remaining spread diagnostics still + expose this gap. +- Audit assignments to member symbols and definition initializers. The trial + reaches unimplemented direct member-reference shape cases and missing + assignment lowering in several mutation tests. +- Add transfer rules for result-bearing control flow, handlers, and quotation + where the resolver currently has no shape rule. Preserve the established + capture model and the boundary between elaboration and lowering. +- Diagnose cyclic wildcard/module forwarding without recursive listener + registration overflowing the stack; `basics/CyclicModuleForwarders.mls` is + a small existing reproducer. +- Preserve source origins in synthesized references and diagnostics. Some + migrated error cases now report a definition rather than a use site, or lose + a location. These need attention without changing the chosen symbol. + +## Execution order and completion gates + +1. **Retain this measured batch.** Run compilation fixtures, main difftests, + application difftests, and the aggregate suite. Review output changes; keep + nonmigrated files intact. WASM `Basics` retains its previous configuration + pending the capture fix, keeping this partial migration green. +2. **Fix annotation prerequisites and cycle handling.** Minimize both timeout + cases. Re-enable generator/annotation tests only after checking emitted IR/JS. +3. **Implement structural targets and external signatures.** Agree on opaque + selection policy, then migrate records, tuples, mutation, and JS interop in + small batches. Re-run affected negative tests as well as successful programs. +4. **Complete pattern transfers and reference preservation.** Start with UCS + conjunction/record/tuple cases, then compiled class patterns, then recursive + and transforming UPS cases. Keep fixed-point behavior tested independently + from matcher code generation. +5. **Complete type/interface and call validation.** Resolve the pending interface + question, finish declared result/field shapes, port module/generic checks, and + finish the WASM migration. Keep cross-block method work coordinated externally. +6. **Migrate shared compilation fixtures from leaves upward.** Begin with the + four UPS fixtures; then application parsing data types, lexer, parser helpers, + and entry points. Rebuild `.mjs` dependencies before each worksheet batch. + Verify cache/import behavior across old and new consumers during transition. +7. **Remove the remaining legacy suite configurations.** Require no new failure + suppressions, no lost negative diagnostics, reviewed goldens, and a successful + `hkmc2AllTests/test`. Commit code and golden outputs together under the agent's + identity once that gate is met. + +## Validation + +- `ctest`: 45 compilation tests pass. +- `catest`: 20 application compilation tests pass in their existing mode. +- Main `dtest` after retaining the migrated batch: 681 tests pass. +- `adtest` after restoring deferred files: all 18 tests pass (16 worksheets and + two shared configuration files). +- The added `newres/TypeApplications.mls` regression passes separately. +- `hkmc2AllTests/test` passes after restoring WASM `Basics.mls` to its previous + configuration. All 22 WASM tests pass; the final main test run passes 685 tests. + The capture assertion remains a migration blocker, not an accepted test failure. +- This checkpoint includes the latest golden outputs and records the outstanding + migration blocker. No trial-generated application report changes remain. + +## Deferred-file inventory + +The following are the first observed trial failures, which may be symptoms rather +than root causes. Each path is relative to `hkmc2/shared/src/test/mlscript/`. +The files themselves retain the pre-trial configuration and output. + +### basics + +- `basics/BadAssignments.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: SelfRef(globalThis:globalThis) (of class SelfRef) +- `basics/BadModuleUses.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] This selection of member 'mtd' has no resolved target +- `basics/BadOverloading.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Not yet supported: overloading of function 'Foo' +- `basics/BadTypeClasses.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Resolution error in member reference; Value symbol 'someInt' cannot be used as a type +- `basics/Classes.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'id' has no resolved target +- `basics/CompanionModules_Classes.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'C' cannot be called like a function. +- `basics/CompanionModules_Functions.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'foo' cannot be called like a function. +- `basics/CyclicModuleForwarders.mls`: Unexpected exception; /!!!\ Uncaught error: java.lang.StackOverflowError +- `basics/DynamicFields.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class constructor 'DynCtor' does not contain member 'dynField' +- `basics/DynamicInstantiation.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape +- `basics/DynamicSelection.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `basics/ExplicitLabels.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Integer literal does not contain member 'break' +- `basics/FunDefs.mls`: Unexpected exception; /!!!\ Uncaught error: java.lang.StackOverflowError +- `basics/GenericClasses.mls`: Unexpected lack of compilation or type error; +- `basics/Inheritance.mls`: Unexpected lack of error to fix; [COMPILATION ERROR] This selection of member 'x' has no resolved target +- `basics/LiteralSelection.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `basics/MiscArrayTests.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `basics/ModuleMethods.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'module M' +- `basics/MultiParamListClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Instance of class constructor 'Foo' cannot receive more argument lists. +- `basics/MutArr.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `basics/MutRcd.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `basics/MutVal.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `basics/New.mls`: Unexpected lack of warnings; [COMPILATION ERROR] Resolution error in application; Class 'Foo' cannot receive more argument lists. +- `basics/NewMut.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class 'Foo' does not contain member 'y' +- `basics/NewlineOps.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'length' has no resolved target +- `basics/NewlineSels.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `basics/ObjectExtensions.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape +- `basics/OpenIn.mls`: Unexpected compilation error; [COMPILATION ERROR] Builtin '~' is not a binary operator +- `basics/Overloading.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'Foo' cannot be called like a function. +- `basics/PrefixOps.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; +- `basics/Puns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(x))))) (of class Record) +- `basics/Records.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `basics/StrTest.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Unexpected term form in expression position (negation type) +- `basics/Underscores.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'f' has no resolved target +- `basics/ValMemberSymbols.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Integer literal cannot receive more argument lists. + +### codegen + +- `codegen/Arrays.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/AuxiliaryConstructors.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/BadGenerators.mls`: Unexpected warning; [WARNING] This annotation has no effect. +- `codegen/BadNew.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape +- `codegen/BadOpen.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Module 'Foo' does not contain member 'y' +- `codegen/BadThis.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'clearInterval' has no resolved target +- `codegen/BasicTerms.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Integer literal cannot be called like a function. +- `codegen/BlockPrinter.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/ClassMatching.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/ConfigDirective.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'call' has no resolved target +- `codegen/ConsoleLog.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'log' has no resolved target +- `codegen/CurriedClassInheritance.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Instance of class constructor 'Bar' cannot receive more argument lists. +- `codegen/CurriedClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in object instantiation; Class 'A' expected 1 argument, but got 1 +- `codegen/Do.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'hello' has no resolved target +- `codegen/ErasedTypes.mls`: Unexpected warning; [WARNING] This annotation has no effect. +- `codegen/FirstClassFunctionTransform.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/Generators.mls`: New-resolution trial exceeded the 25-second runner timeout. +- `codegen/Getters.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'whoops' has no resolved target +- `codegen/Hygiene.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'foo' +- `codegen/ImportAlias.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'inc' has no resolved target +- `codegen/ImportExample.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/ImportJSClass.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape +- `codegen/ImportJSModule.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'greet' has no resolved target +- `codegen/ImportMLs.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Module 'Option' does not contain member 'oops' +- `codegen/ImportMLsJS.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'isDefined' has no resolved target +- `codegen/ImportedOps.mls`: Unexpected compilation error; [COMPILATION ERROR] Builtin '~' is not a binary operator +- `codegen/Inliner.mls`: Unexpected warning; [WARNING] This annotation has no effect. +- `codegen/InterleavedRecords.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/Misc.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/ModuleMethods.mls`: Unexpected lack of compilation or type error; +- `codegen/Modules.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'None' cannot be called like a function. +- `codegen/NestedClasses.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape +- `codegen/NestedScoped.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/NoFreeze.mls`: Unexpected compilation error; [COMPILATION ERROR] Assignment requires an unambiguous term member +- `codegen/NoInline.mls`: Unexpected warning; [WARNING] This annotation has no effect. +- `codegen/NoModuleCheck.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Resolution error in object instantiation; +- `codegen/ObjectMethodDebinding.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'foo' has no resolved target +- `codegen/Open.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'length' +- `codegen/OpenWildcard.mls`: Unexpected compilation error; [COMPILATION ERROR] Wildcard-open reference 'None' is ambiguous +- `codegen/ParamClasses.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/PartialApps.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Tuple literal cannot receive more argument lists. +- `codegen/PlainClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class 'Foo' cannot receive more argument lists. +- `codegen/PredefUsage.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot be called like a function. +- `codegen/PrivateMembers.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'y' has no resolved target +- `codegen/Pwd.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'pop' has no resolved target +- `codegen/QQImport.mls`: Unexpected runtime error; [RUNTIME ERROR] ReferenceError: Term is not defined +- `codegen/Quasiquotes.mls`: Unexpected runtime error; [RUNTIME ERROR] ReferenceError: Term is not defined +- `codegen/RandomStuff.mls`: Unexpected exception; /!!!\ Uncaught error: java.lang.StackOverflowError +- `codegen/ReboundLet.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/RuntimeUsage.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Class 'Str' does not contain member 'leave' +- `codegen/SanityChecks.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function literal expected 1 argument, but got 0 +- `codegen/ScopedBlocks.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'Foo' cannot receive more argument lists. +- `codegen/ScopedBlocksAndHandlers.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Handle(h,Capture(MemberRef(member:Effect),term:f),List(),class:Handler$h$,List(HandlerTermDefinition(k,TermDefinition(Fun,member:perform,term:Handler$h$/perform,List(... +- `codegen/Scoping.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/SelfReferences.mls`: Unexpected lack of compilation or type error; +- `codegen/SetStmt.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/Spreads.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function 'foo' expected 4 arguments, but got 1 +- `codegen/This.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'a' has no resolved target +- `codegen/ThisCallVariations.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'call' has no resolved target +- `codegen/ThisCalls.mls`: Unexpected lack of compilation or type error; +- `codegen/Throw.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class 'Error' cannot receive more argument lists. +- `codegen/TraceLog.mls`: Unexpected exception; /!!!\ Uncaught error: java.lang.StackOverflowError +- `codegen/UnitValue.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'log' has no resolved target +- `codegen/While.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Integer literal cannot be called like a function. + +### ucs + +- `ucs/examples/BinarySearchTree.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. +- `ucs/examples/EitherOrBoth.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection of type None; Object 'None' cannot be used as a type +- `ucs/examples/LeftistTree.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. +- `ucs/examples/ListFold.mls`: Unexpected compilation error; [COMPILATION ERROR] Builtin '~' is not a binary operator +- `ucs/examples/ULC.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. +- `ucs/general/BooleanPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Composition(false,Negation(Literal(IntLit(2))),Negation(Literal(IntLit(3)))) (of class Composition) +- `ucs/hygiene/HygienicBindings.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class 'Error' cannot receive more argument lists. +- `ucs/normalization/Deduplication.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'length' has no resolved target +- `ucs/normalization/InheritanceNormalization.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:B),None)) (of class Composition) +- `ucs/normalization/OverlapOfPrimitives.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. +- `ucs/normalization/RecordImpliedByClass.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(av))), (Ident(b),Alias(Wildcard(),Ident(bv))))) (of class Record) +- `ucs/patterns/BooleansOps.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Composition(false,Alias(Wildcard(),Ident(a)),Alias(Wildcard(),Ident(b))) (of class Composition) +- `ucs/patterns/CompiledClassPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Add) (of class hkmc2.semantics.Term$MemberRef) +- `ucs/patterns/CompiledQualifiedConstructors.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: NewSel(SimpleRef(Inner),Ident(Wrapped),None) (of class hkmc2.semantics.Term$NewSel) +- `ucs/patterns/ConjunctionPattern.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Composition(false,Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:A),None)),Constructor(MemberRef(member:B),None)) (of class Comp... +- `ucs/patterns/RecordPattern.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Record(List((Ident(x),Alias(Wildcard(),Ident(a))), (Ident(y),Alias(Wildcard(),Ident(b))))) (of class Record) +- `ucs/patterns/String.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Concatenation(Literal(StrLit(0x)),Alias(Wildcard(),Ident(body))) (of class Concatenation) +- `ucs/patterns/where.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ucs/syntax/Else.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class 'Set' does not contain member 'has' +- `ucs/syntax/SimpleUCS.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'get' has no resolved target + +### ups + +- `ups/BasicStackPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack)),Ident(x)) (of class hkmc2.semantics.Term$UnresolvedRef) +- `ups/EmptyJunctions.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Range(IntLit(5),IntLit(3),true) (of class Range) +- `ups/Future.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Pattern symbol 'Test' cannot be used as a type +- `ups/LocalPatterns.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; +- `ups/MatchResult.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; +- `ups/RecursiveTransformations.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(b) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/SimpleConjunction.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:observeLeft) (of class hkmc2.semantics.Term$MemberRef) +- `ups/SimpleTransform.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(a) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/TransformFree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/UpsBugsBacklog.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(c) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/examples/BasicSeqStackParse.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `ups/examples/BasicStackParse.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) +- `ups/examples/Computation.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(f) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/examples/DnfCnf.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Or) (of class hkmc2.semantics.Term$MemberRef) +- `ups/examples/DoubleOrSum.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:addWithPrint) (of class hkmc2.semantics.Term$MemberRef) +- `ups/examples/DoubleTripleList.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack), MemberRef(member:annotations)),Ident(Nil)) (of class hkmc2.semantics.Term$UnresolvedRef) +- `ups/examples/EvaluationContext.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `ups/examples/EvaluationContext2.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `ups/examples/Extraction.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: NewSel(MemberRef(member:Option),Ident(Some),None) (of class hkmc2.semantics.Term$NewSel) +- `ups/examples/Flatten.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack), MemberRef(member:annotations)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) +- `ups/examples/HindleyMilner.mls`: New-resolution trial exceeded the 25-second runner timeout. +- `ups/examples/ListPredicates.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(list) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/examples/Negation.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/examples/PrecedenceClimbStackParse.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) +- `ups/examples/Record.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(weight) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/examples/TupleSpread.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/fixpoint/Diagnostics.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/fixpoint/FixedPointPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/fixpoint/IndirectRecursion.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/fixpoint/ListFusion.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target +- `ups/fixpoint/MoreAlternatives.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/fixpoint/NonCatchAll.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/fixpoint/RecursionAlternatives.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/fixpoint/SimpleExample.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(a) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/fixpoint/UnsupportedShapes.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/nondeterminism/BitArithmetic.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:And) (of class hkmc2.semantics.Term$MemberRef) +- `ups/nondeterminism/EvenOddTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:B) (of class hkmc2.semantics.Term$MemberRef) +- `ups/nondeterminism/LaRbTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:A) (of class hkmc2.semantics.Term$MemberRef) +- `ups/parametric/EtaConversion.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Zero) (of class hkmc2.semantics.Term$MemberRef) +- `ups/parametric/HigherOrderPattern.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Int) (of class hkmc2.semantics.Term$MemberRef) +- `ups/parametric/ListLike.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/parametric/Nullable.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/recursion/BitSeq.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Pair) (of class hkmc2.semantics.Term$MemberRef) +- `ups/recursion/BitTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Pair) (of class hkmc2.semantics.Term$MemberRef) +- `ups/recursion/LeafEvenOddTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:B) (of class hkmc2.semantics.Term$MemberRef) +- `ups/recursion/NatBox.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Box) (of class hkmc2.semantics.Term$MemberRef) +- `ups/recursion/NullTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Pair) (of class hkmc2.semantics.Term$MemberRef) +- `ups/recursion/SignBox.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Box) (of class hkmc2.semantics.Term$MemberRef) +- `ups/regex/EmailAddress.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:UserNameLetter) (of class hkmc2.semantics.Term$MemberRef) +- `ups/regex/Identifier.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'forEach' has no resolved target +- `ups/regex/Separation.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(t) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/regex/TailRepetition.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; +- `ups/specialization/SimpleList.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Box) (of class hkmc2.semantics.Term$MemberRef) +- `ups/specialization/SimpleLiterals.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:observeZero) (of class hkmc2.semantics.Term$MemberRef) +- `ups/syntax/InterestingPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/syntax/MixedParameters.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Bit) (of class hkmc2.semantics.Term$MemberRef) +- `ups/syntax/PatternBody.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) +- `ups/transformation/BindingLess.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) + +### apps + +- `apps/AccountingTest.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `apps/CSVTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member '1' has no resolved target +- `apps/IterTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'reverse' has no resolved target +- `apps/parsing-web-demo/ExamplesTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'sort' has no resolved target +- `apps/parsing/DirectiveTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'display' has no resolved target +- `apps/parsing/LeftRecursion.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'display' has no resolved target +- `apps/parsing/LexerTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'join' has no resolved target +- `apps/parsing/ParseRuleVisualizerTest.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class 'Error' cannot receive more argument lists. +- `apps/parsing/PrattParsingTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target +- `apps/parsing/RecursiveDescentTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target +- `apps/parsing/RulesTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'display' has no resolved target diff --git a/doc/new-resolution-wasm.md b/doc/new-resolution-wasm.md index 3212326f8a..61df50a66a 100644 --- a/doc/new-resolution-wasm.md +++ b/doc/new-resolution-wasm.md @@ -1,5 +1,5 @@ -## Existing WASM suite migration (partial) +## Initial WASM migration milestone (`8e66dbefd`) The shared `mlscript/wasm/.mls` enables new resolution, strict resolution, and WASM execution while disabling JS execution. Migrated tests start with `:.`, @@ -39,3 +39,30 @@ Blocked files: `Basics`, `BuiltinOperators`, `Casts`, `ClassMethods`, `ControlFl `NumericIntrinsicReview`, `NumericTypes`, `ReplImports`, `TailRecOptCasts`, `Tuples`, and `VirtualMethods`. Cross-block method handling remains owned by the other branch. + +## Type interpretation migration in progress + +Ten of the eleven remaining files now use the shared configuration. `Basics` +retains its previous configuration pending the capture fix below. Deferred type +interpretations preserve forward aliases and unboxed signatures, and annotations +publish nominal instance shapes before a function is called. Assignment lowering +accepts the new reference forms; mutable tuples forward their value shapes. +The old empty-call syntax for bare classes and paramless getters has been updated. + +The retained WASM suite passes all 22 files, including the new forward-alias +regression. Trying `Basics` with new resolution exposes a capture-context assertion for: + +```mlscript +class Foo(val x) +fun getX(f: Foo) = f.x +getX(Foo(42)) +``` + +The nominal shape supplied by the annotation knows the field symbol but lacks the +construction context carried by its inferred value. Returning that value tries +to exit `getX` while its pending entry mark belongs to the constructor `Foo`. +This must be addressed in the relationship between declared interfaces and +call-derived value shapes, without bypassing capture invariants. The outstanding +semantic question is whether annotations fix the available member interface or +permit refinement from concrete arguments. This is an intermediate checkpoint; the migration is not complete until that +question and the assertion are resolved. diff --git a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala index b4b756b3e4..a4c830f151 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala @@ -222,7 +222,7 @@ class CompilerCtx( // is needed by erased-type operations in every compilation unit. given Ctx = ctx given SymbolPrinter = new SymbolPrinter(Scope.empty(Scope.Cfg.default)) - new codegen.Lowering().classHeaders(blk) + new codegen.Lowering().prepareTypes(blk) PreludeArtifact(parse.resultBlk, blk, ctx, state, rootConfig, lastMod), ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala index cdcff9d8da..ad75b7d1cd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala @@ -55,6 +55,15 @@ object ErasedType: // Ensures `toString` returns a stable string override def toString: Str = "ValueLike(?)" + /** Erasure of a type whose interpretation is still being published during elaboration. + * The thunk reads an already-resolved type graph when the backend needs its representation. + */ + final class Deferred(compute: (Ctx, State) ?=> ErasedValueType) extends ErasedValueType: + override type Canonical = CanonicalErasedValueType + override def sym(using Ctx, State): TypeSymbol | NoSymbol = canonicalize.sym + override protected def computeCanonicalize(using Ctx, State): CanonicalErasedValueType = compute.canonicalize + override def toString: Str = "Deferred(?)" + /** A reference to a function of a possibly-known shape. * * - `rsc` is true if this reference is a resource function. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 07c53c0a94..4b98550472 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -425,7 +425,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): def classOf(trm: Term, nw: Resolvable)(k: Path => Block)(using LoweringCtx): Block = if newResolution then - trm.withoutCaptures match + trm.classHead match case resolved @ Resolved(_, _: ClassSymbol) => // Synthesized runtime constructors already carry an explicit class target. subTerm(resolved)(k) @@ -1143,10 +1143,13 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): // * need the selected prefix/name/symbol in order to emit `AssignField`. // * Still, resolver expansions matter: `Resolved(lhs, sym)` carries the // * disambiguated member symbol needed for private fields and overloads. - val (target, resolvedSelectionSymbol) = lhs.instantiated match + val (target, resolvedSelectionSymbol) = lhs.withoutCaptures.instantiated match case Resolved(inner, sym) => inner -> S(sym) case target => target -> N target match + case SimpleRef(sym) => + subTerm(rhs): r => + assignSymbol(sym, sym, r, k(unit), trm.toLoc) case Ref(sym) => subTerm(rhs): r => assignSymbol(resolvedSelectionSymbol.getOrElse(sym), sym, r, k(unit), trm.toLoc) @@ -1650,11 +1653,15 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): ps.params.map(_.sym.erasedType) - /** Publish nominal parent links before lowering bodies can ask erased-type questions. + /** Validate elaborated type interpretations and publish nominal parent links before + * lowering bodies can ask erased-type questions. * This also handles declarations, whose executable definitions are deliberately omitted. * An unresolved or ambiguous parent leaves the header absent, rather than inventing a root. */ - def classHeaders(statement: Statement): Unit = + def prepareTypes(statement: Statement): Unit = + statement match + case term: Term => term.typeInterpretation.foreach(_.validate(Set.empty)) + case _ => () statement match case cls: ClassLikeDef => cls.sym match case sym: ClassLikeSymbol if sym.irClassHeader.isEmpty => @@ -1663,10 +1670,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case S(parent) => parent.cls.resolvedSym.flatMap(_.asClsOrMod).map(p => ClassHeader(S(p))) case _ => () case _ => () - statement.subStatements.foreach(classHeaders) + statement.subStatements.foreach(prepareTypes) def program(main: st.Blk, symbolsToPreserve: Set[BoundSymbol]): Program = - classHeaders(main) + prepareTypes(main) val (imps, funs, rest) = splitBlock(main.stats, Nil, Nil, Nil) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 7977c49b77..7e5f474f59 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1173,7 +1173,10 @@ extends Importer: * Listeners can wait for forward definitions to complete. */ private def interpretRef(ref: Term, interp: Interpretation): Term = - if newResolution && (interp is Trm) then requireTerm(ref) + if newResolution then interp match + case Trm => requireTerm(ref) + case Tpe => typeResolution(ref) + case _ => () ref // Most recursive operands are terms. Transparent wrappers must explicitly @@ -1611,7 +1614,7 @@ extends Importer: (org.startLineNum + org.fph.getLineColAt(loc.spanStart)._1).toString) case N => ("‹unknown›", "‹unknown›") val elsPart = els.fold(PrefixApp(Keywrd(Keyword.`else`), Tree.Trm( - State.runtimeSymbol.ref().selNoSym("assertFail") + State.runtimeSymbol.ref().selNoSym("assertFail", synth = true) .app(Term.Lit(StrLit(fl)), Term.Lit(StrLit(ln))) )))(PrefixApp.apply.tupled) subterm: @@ -1785,9 +1788,9 @@ extends Importer: case _ => term(tree, interp) def fld(tree: Tree, interp: Interpretation)(using UnderCtx): Ctxl[Elem] = tree match case InfixApp(id: Ident, Keywrd(Keyword.`:`), rhs) => - Fld(FldFlags.empty, Term.Lit(StrLit(id.name).withLocOf(id)), S(arg(rhs, Tpe))) + Fld(FldFlags.empty, Term.Lit(StrLit(id.name).withLocOf(id)), S(arg(rhs, interp))) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => - Fld(FldFlags.empty, term(lhs, interp), S(arg(rhs, Tpe))) + Fld(FldFlags.empty, term(lhs, interp), S(arg(rhs, interp))) case Spread(Keywrd(Keyword.`..`), S(trm)) => Spd(SpreadKind.Lazy, arg(trm, interp)) case Spread(Keywrd(Keyword.`...`), S(trm)) => @@ -2176,10 +2179,10 @@ extends Importer: val noParams: Opt[Ls[Opt[ErasedValueType]]] = S(Nil) fields.foldRight(noParams): (fld, acc) => (fld, acc) match - case (Fld(_, t, _), S(rest)) => S(ErasedType.eraseSign(t) :: rest) + case (Fld(_, t, _), S(rest)) => S(eraseSignature(t) :: rest) case _ => N // * An unparenthesized type is a single parameter. - case single => S(ErasedType.eraseSign(single) :: Nil) + case single => S(eraseSignature(single) :: Nil) sign match case Term.Forall(_, _, body) => splitSignature(body) case Term.FunTy(lhs, rhs, _) => paramsOf(lhs).map: ps => @@ -2214,7 +2217,7 @@ extends Importer: if (k is syntax.Fun) && td.annotatedResultType.isEmpty then stripSignatureParams(s, pss.length) else sigShape.map(_._2).getOrElse(s) - ErasedType.eraseSign(resultSign) + eraseSignature(resultSign) val erasedTpe = k match case syntax.Fun => // * A `declare`d function's parameter lists are derived from its signature when it writes none. @@ -2669,11 +2672,14 @@ extends Importer: // * `eraseSign` would miss by resolving the name through `asTpe`. val erasedTpe = mfn.msym match case S(msym) => S(ErasedType.ValueLike(rsc = S(false), msym)) - case N => sig.flatMap(ErasedType.eraseSign) + case N => sig.flatMap(eraseSignature) val sym = VarSymbol(canonicalId, erasedType = erasedTpe) sym.sourceAliases = aliases val p = Param(flg, sym, sig, mfn) sym.decl = S(p) + if newResolution then sig.foreach: sign => + listenTypeValues(sign): shape => + if sym.shapes.add(shape) then sym.shapeListeners.foreach(_(shape)) (p, spd, aliases) def funParams(t: Tree): Ctxl[(ParamList, Ctx)] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index fc0ff4a087..9b9a13aa68 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -49,6 +49,70 @@ class NewResolver: private val selfShapes: mutable.Map[InnerSymbol, BaseShape] = mutable.Map.empty val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty + /** Interpret types through completed symbolic candidates, independently of term overloads. */ + def typeResolution(term: Term): TypeResolution = term.typeInterpretation match + case S(result) => result + case N => + val result = new TypeResolution(term, messages => resolError(term, messages)) + term.typeInterpretation = S(result) // Register before following a potentially recursive alias. + def select(symbol: TypeSymbol): Unit = + term.withoutCaptures match + case ref: NewResolvable => + if !ref.resolvedTargets.contains(symbol) then ref.resolvedTargets ::= symbol + case _ => () + def definition(defn: Definition): Unit = defn match + case cls: ClassLikeDef => result.publish(TypeShape.Nominal(cls)) + case alias: TypeDef => result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) + case _ => result.publish(TypeShape.Abstract) + symbol.defn match + case S(defn) => definition(defn) + case N => symbol.defnListeners += definition + def reject(shape: Shape): Unit = + result.fail(msg"${shape.describe.capitalize} cannot be used as a type" -> shape.toLoc :: Nil) + result.publish(TypeShape.Abstract) + term match + case Capture(base, _) => typeResolution(base).listen(result.publish) + case TyApp(base, _) => typeResolution(base).listen(result.publish) + case Forall(_, _, body) => typeResolution(body).listen(result.publish) + case CompType(left, right, union) => + val l = typeResolution(left) + val r = typeResolution(right) + result.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) + case _: FunTy => result.publish(TypeShape.Function) + case UnitVal() => result.publish(TypeShape.Unit) + case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => + result.publish(TypeShape.Abstract) + case _: WildcardTy | _: Neg | _: Rcd | _: Tup | _: Lit | Missing | Error() => + result.publish(TypeShape.Abstract) + case _ => listen(term, discardMarks = true): + case shape: SymShape => shape.sym.onComplete: () => + shape.sym.asTpe.orElse(shape.sym.asModOrObj) match + case S(symbol) => select(symbol) + case N => reject(shape) + case shape: TermShape => reject(shape) + result + + def eraseSignature(sign: Term): Opt[codegen.ErasedValueType] = + if newResolution then + val resolution = typeResolution(sign) + S(new codegen.ErasedType.Deferred(resolution.erase(Set.empty))) + else codegen.ErasedType.eraseSign(sign) + + /** Declared types provide instance members before there are any calls or assignments. + * Recursive aliases are followed once per subscription; their operands may resolve later. + */ + def listenTypeValues(sign: Term)(listener: TermShape => Unit): Unit = + val visited = mutable.Set.empty[TypeResolution] + def follow(resolution: TypeResolution): Unit = if visited.add(resolution) then + resolution.listen: + case TypeShape.Nominal(defn) => + listenExt(defn.ext, ext => listener(selfShapes.getOrElseUpdate(defn.sym, BaseShape(defn, ext)))) + case TypeShape.Alias(_, rhs) => rhs.foreach(follow) + case TypeShape.Union(left, right) => follow(left); follow(right) + case TypeShape.Intersection(left, right) => follow(left); follow(right) + case _ => () + follow(typeResolution(sign)) + def isOwnedSym(sym: Symbol): Bool = sym.getState is state @@ -56,41 +120,48 @@ class NewResolver: ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Ls[Elem], src: Term, funSh: TermShape, argumentMarks: Ls[Marks]): Unit = - (ps, args) match - case (Nil, Nil) if r.isEmpty => () - // case (Nil, Spd(k, t) :: args) => - // zip(Nil, r, args) - case (Nil, args) => - r match - case N => - resolError(src, - msg"${funSh.describe.capitalize} expected ${ps.length} ${ - "argument".pluralized(ps.length)}, but got ${args.length}" -> funSh.toLoc :: Nil) - case S(p) => - val packaged = new Tup(args)(Tree.DummyTup).withLoc(Loc.mk(args.iterator.flatMap(_.toLoc))) - IntroShape(packaged).exit(argumentMarks).enter(mss) match - case NoShape => () + val expectedCount = ps.length + val providedCount = args.length + // Keep the original arity for diagnostics while consuming matched arguments. + def loop(ps: Ls[Param], args: Ls[Elem]): Unit = + (ps, args) match + case (Nil, Nil) if r.isEmpty => () + // case (Nil, Spd(k, t) :: args) => + // zip(Nil, r, args) + case (Nil, args) => + r match + case N => + resolError(src, + msg"${funSh.describe.capitalize} expected ${expectedCount} ${ + "argument".pluralized(expectedCount)}, but got ${providedCount}" -> funSh.toLoc :: Nil) + case S(p) => + val packaged = new Tup(args)(Tree.DummyTup).withLoc(Loc.mk(args.iterator.flatMap(_.toLoc))) + IntroShape(packaged).exit(argumentMarks).enter(mss) match + case NoShape => () + case sh: TermShape => + if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then p.sym.shapeListeners.foreach(_(sh)) + case (p :: ps, Fld(fls, trm, asc) :: args) if p.sign.nonEmpty => + loop(ps, args) + case (p :: ps, Fld(fls, trm, asc) :: args) => + // case ((p, mss) :: ps, Fld(fls, trm, asc) :: args) => + listenTerm(trm): sh0 => + // val sh = sh0.enter(mss) + // log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg}") + // if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then + // p.sym.shapeListeners.foreach(listener => listener(sh)) + sh0.exit(argumentMarks).enter(mss) match + case NoShape => case sh: TermShape => - if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then p.sym.shapeListeners.foreach(_(sh)) - case (p :: ps, Fld(fls, trm, asc) :: args) => - // case ((p, mss) :: ps, Fld(fls, trm, asc) :: args) => - listenTerm(trm): sh0 => - // val sh = sh0.enter(mss) - // log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg}") - // if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then - // p.sym.shapeListeners.foreach(listener => listener(sh)) - sh0.exit(argumentMarks).enter(mss) match - case NoShape => - case sh: TermShape => - log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg} ${isOwnedSym(p.sym)}, ${p.sym.shapes.contains(sh)}") - if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then - // log(s"!!!! ${p.sym.shapeListeners}") - p.sym.shapeListeners.foreach(listener => listener(sh)) - zipArgs(mss, ps, r, args, src, funSh, argumentMarks) - case _ => - resolError(src, - msg"${funSh.describe.capitalize} expected ${ps.length} ${ - "argument".pluralized(ps.length)}, but got ${args.length}" -> funSh.toLoc :: Nil) + log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg} ${isOwnedSym(p.sym)}, ${p.sym.shapes.contains(sh)}") + if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then + // log(s"!!!! ${p.sym.shapeListeners}") + p.sym.shapeListeners.foreach(listener => listener(sh)) + loop(ps, args) + case _ => + resolError(src, + msg"${funSh.describe.capitalize} expected ${expectedCount} ${ + "argument".pluralized(expectedCount)}, but got ${providedCount}" -> funSh.toLoc :: Nil) + loop(ps, args) /** Resolve every constructor pattern through the same symbolic interpretation. * A class overload takes precedence over its term companion in this context. @@ -182,6 +253,8 @@ class NewResolver: matchShapePat(shape, left)(matched) matchShapePat(shape, right)(matched) case Pattern.Guarded(pat, _) => matchShapePat(shape, pat)(matched) + // Compilation-strategy annotations do not change the pattern's bindings. + case Pattern.Annotated(pat, _) => matchShapePat(shape, pat)(matched) case ctor: Pattern.Constructor => def listenConstructor(psh: PatternShape): Unit = psh match case CtorPatternShape(cls, fs, _, resSym) => @@ -304,7 +377,7 @@ class NewResolver: * Capture marks are retained for subsequent instance-member lookup. */ private def listenClass(trm: Term)(selected: (ClassDef, Ls[Marks]) => Unit, reject: Shape => Unit): Unit = def select(cls: ClassDef, marks: Ls[Marks]): Unit = - trm.withoutCaptures match + trm.classHead match case ref: NewResolvable => if !ref.resolvedTargets.contains(cls.sym) then ref.resolvedTargets ::= cls.sym case _ => () @@ -318,6 +391,7 @@ class NewResolver: case _ => reject(sh) case _ => reject(sh) trm match + case TyApp(base, _) => listenClass(base)(select, reject) case Capture(base, thru) => listenClass(base)((cls, marks) => select(cls, marks ::: EntryMark(thru, N, NoMarks) :: Nil), reject) case _ => listen(trm): @@ -354,7 +428,7 @@ class NewResolver: def resolveNew(nw: Term.New): Unit = log(s"resolveNew? res = ${nw.showDbg}") - nw.cls.withoutCaptures match + nw.cls.classHead match case trm: NewResolvable => listen(trm): shape => def reject(): Unit = nw.isErroneous = true @@ -540,6 +614,8 @@ class NewResolver: fromBMS(bms, ss.resSym, Nil//TODO? , listener, trm, _ => ()) case N => ??? + case TyApp(underlying, _) => listen(underlying, discardMarks)(listener) + case Mut(underlying) => listenTerm(underlying)(listener) case intro: IntroTerm => val sh = introShapes.getOrElseUpdate(intro, { log(s"introShape: intro = $intro") @@ -600,5 +676,3 @@ class NewResolver: () end NewResolver - - diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index f75c5ad83d..9aed9234c0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -376,6 +376,9 @@ sealed trait UnresolvedRefImpl extends NewResolvableImpl: enum Term extends Statement, ShapePublisher: + /** Filled by the type interpreter during elaboration; lowering only validates the result. */ + private[hkmc2] var typeInterpretation: Opt[TypeResolution] = N + case Error() case UnitVal() case Missing // Placeholder terms that were not elaborated due to the "lightweight" elaboration mode `Mode.Light` @@ -472,6 +475,14 @@ enum Term extends Statement, ShapePublisher: def withoutCaptures: Term = this match case Capture(base, _) => base.withoutCaptures case _ => this + + /** Class target validation looks through wrappers with no runtime representation. + * Shape propagation still processes captures to retain their provenance. + */ + def classHead: Term = this match + case Capture(base, _) => base.classHead + case TyApp(base, _) => base.classHead + case _ => this def expanded: Term = this match case t: Resolvable => t.expansion match @@ -553,6 +564,9 @@ enum Term extends Statement, ShapePublisher: * (These definitions were generated as part of a slop PR.) */ lazy val freeVars: Set[Str] = this match case Ref(sym) => Set.single(sym.nme) + case SimpleRef(sym) => Set.single(sym.nme) + case MemberRef(sym) => Set.single(sym.nme) + case SelfRef(sym) => Set.single(sym.nme) case Lam(params, body) => val paramNames = params.allParams.iterator.map(_.sym.nme).toSet body.freeVars -- paramNames @@ -1672,4 +1686,3 @@ trait BlkImpl: (stats ::: (res match case Lit(Tree.UnitLit(false)) => Nil case res => res :: Nil)).map(_.show).mkDocument(doc", # ") - diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala new file mode 100644 index 0000000000..d90bf24e12 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -0,0 +1,77 @@ +package hkmc2 +package semantics + +import hkmc2.utils.*, shorthands.* +import codegen.{ErasedType, ErasedValueType} +import Elaborator.{Ctx, State} + +/** A type interpretation, separate from the value shapes of the same reference syntax. + * Compound types retain links to their operands so forward aliases need not be expanded eagerly. + */ +enum TypeShape: + case Nominal(defn: ClassLikeDef) + case Alias(symbol: TypeAliasSymbol, rhs: Opt[TypeResolution]) + case Union(left: TypeResolution, right: TypeResolution) + case Intersection(left: TypeResolution, right: TypeResolution) + case Function + case Unit + case Abstract + +/** Candidates are published during elaboration. Erasure reads the resulting graph; it + * never performs lookup or resolves an alias by inspecting an elaborated definition. + */ +final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => Unit) extends Host[TypeShape]: + private var reported = false + def showDbg(using DebugPrinter): Str = s"type of ${source.showDbg}" + def publish(shape: TypeShape): Unit = + if shapes.add(shape) then shapeListeners.foreach(_(shape)) + def listen(listener: TypeShape => Unit): Unit = + shapeListeners += listener + shapes.foreach(listener) + def fail(messages: Ls[(Message, Opt[Loc])]): Unit = if !reported then + reported = true + report(messages) + def validate(seen: Set[TypeResolution]): Unit = if !seen(this) then + import TypeShape.* + import Message.MessageContext + val next = seen + this + val ambiguousReceiver = source.withoutCaptures match + case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.sizeCompare(1) > 0 + case sel: Term.NewSel => sel.hasAmbiguousClass + case _ => false + if shapes.isEmpty then fail(msg"This type has no resolved target" -> source.toLoc :: Nil) + else if ambiguousReceiver || shapes.sizeCompare(1) > 0 then + val candidates = source.withoutCaptures match + case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.map: (prefix, member) => + msg"candidate: ${member.describe}" -> member.toLoc + case _ => shapes.toList.flatMap: + case Nominal(defn) => msg"candidate: ${defn.sym.describeKind}" -> defn.toLoc :: Nil + case Alias(symbol, _) => msg"candidate: ${symbol.describeKind}" -> symbol.toLoc :: Nil + case _ => Nil + fail(msg"This type is ambiguous, as it has multiple resolved targets" -> source.toLoc :: candidates) + shapes.foreach: + case Alias(_, rhs) => rhs.foreach(_.validate(next)) + case Union(left, right) => left.validate(next); right.validate(next) + case Intersection(left, right) => left.validate(next); right.validate(next) + case _ => () + + def erase(seen: Set[TypeResolution])(using Ctx, State): ErasedValueType = + import TypeShape.* + import Message.MessageContext + validate(seen) + if seen(this) || reported then ErasedType.Unknown + else + val next = seen + this + shapes.toList match + case Nominal(defn) :: Nil => defn.sym match + case symbol: (ClassSymbol | ModuleOrObjectSymbol) => ErasedType.ValueLike(S(false), symbol) + case _ => ErasedType.Unknown + case Alias(_, rhs) :: Nil => rhs.fold(ErasedType.Unknown)(_.erase(next)) + case Union(left, right) :: Nil => ErasedType.union(left.erase(next), right.erase(next)) + case Intersection(_, _) :: Nil | Abstract :: Nil => ErasedType.Unknown + case Function :: Nil => ErasedType.Function(S(false)) + case Unit :: Nil => ErasedType.Unit + case Nil => + ErasedType.Unknown + case _ => + ErasedType.Unknown diff --git a/hkmc2/shared/src/test/mlscript/apps/.mls b/hkmc2/shared/src/test/mlscript/apps/.mls new file mode 100644 index 0000000000..cc02430a79 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/apps/.mls @@ -0,0 +1,3 @@ +:.. +:!js +#lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/CamlLightTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/CamlLightTest.mls index 5eb155c918..a035c8130b 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/CamlLightTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/CamlLightTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/apps/parsing/Test.mls" diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/LoopExpressions.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/LoopExpressions.mls index 9be1e49d34..92c4349381 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/LoopExpressions.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/LoopExpressions.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/apps/parsing/Test.mls" diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/ParserErrorTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/ParserErrorTest.mls index 5ccf15d2e4..8880ad29b3 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/ParserErrorTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/ParserErrorTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/apps/parsing/Test.mls" diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/ParserTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/ParserTest.mls index 860e8f3f82..720c9a34f7 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/ParserTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/ParserTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/apps/parsing/Test.mls" diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/TypeTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/TypeTest.mls index 20ab8faf8e..f18a7002e3 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/TypeTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/TypeTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/apps/parsing/Test.mls" diff --git a/hkmc2/shared/src/test/mlscript/basics/.mls b/hkmc2/shared/src/test/mlscript/basics/.mls new file mode 100644 index 0000000000..cc02430a79 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/.mls @@ -0,0 +1,3 @@ +:.. +:!js +#lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/basics/ADTs.mls b/hkmc2/shared/src/test/mlscript/basics/ADTs.mls index eb3c7bd2d5..50a395cad4 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ADTs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ADTs.mls @@ -1,3 +1,4 @@ +:. :js :ignore @@ -8,7 +9,7 @@ class Expr constructor Lit(n: Int) constructor Add(lhs: Expr, rhs: Expr) //│ ╔══[PARSE ERROR] Expected end of input; found indented block instead -//│ ║ l.8: constructor Lit(n: Int) +//│ ║ l.9: constructor Lit(n: Int) //│ ╙── ^^ class Expr[A] with @@ -32,7 +33,7 @@ enum Expr = | Lit(n: Int) | Add(lhs: Expr, rhs: Expr) //│ ╔══[COMPILATION ERROR] Unrecognized definitional assignment left-hand side: juxtaposition -//│ ║ l.31: enum Expr = +//│ ║ l.32: enum Expr = //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/AppOp.mls b/hkmc2/shared/src/test/mlscript/basics/AppOp.mls index e40a3d44bc..d0b974fafa 100644 --- a/hkmc2/shared/src/test/mlscript/basics/AppOp.mls +++ b/hkmc2/shared/src/test/mlscript/basics/AppOp.mls @@ -1,3 +1,4 @@ +:. :js @@ -107,7 +108,7 @@ pipeInto(1, _) @ inc :re 1 pipeInto @ inc //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). -//│ ║ l.108: 1 pipeInto @ inc +//│ ║ l.109: 1 pipeInto @ inc //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: 1 is not a function diff --git a/hkmc2/shared/src/test/mlscript/basics/Assert.mls b/hkmc2/shared/src/test/mlscript/basics/Assert.mls index 55fc7befd5..82aabf0f80 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Assert.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Assert.mls @@ -1,3 +1,4 @@ +:. :js @@ -10,12 +11,12 @@ assert true //│ if (true === true) { //│ return runtime.Unit //│ } -//│ return runtime.safeCall(runtime.assertFail("Assert.mls", "9")); +//│ return runtime.safeCall(runtime.assertFail("Assert.mls", "10")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :re assert false -//│ ═══[RUNTIME ERROR] Error: Assertion failed (Assert.mls:18) +//│ ═══[RUNTIME ERROR] Error: Assertion failed (Assert.mls:19) assert xs is Array @@ -28,9 +29,9 @@ assert(true); 123 //│ if (123 === true) { //│ return runtime.Unit //│ } -//│ return runtime.safeCall(runtime.assertFail("Assert.mls", "27")); +//│ return runtime.safeCall(runtime.assertFail("Assert.mls", "28")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] Error: Assertion failed (Assert.mls:27) +//│ ═══[RUNTIME ERROR] Error: Assertion failed (Assert.mls:28) // * FIXME: the `else` is parsed as part of the `assert`! :sjs @@ -78,7 +79,7 @@ ys //│ = 5 :re -(assert false else throw Error("whoops")) + 3 +(assert false else throw new Error("whoops")) + 3 //│ ═══[RUNTIME ERROR] Error: whoops diff --git a/hkmc2/shared/src/test/mlscript/basics/BadClasses.mls b/hkmc2/shared/src/test/mlscript/basics/BadClasses.mls index e7a3fa435d..417642be27 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadClasses.mls @@ -1,3 +1,4 @@ +:. class C { print("hi") } @@ -5,7 +6,7 @@ class C { print("hi") } :w class C { 1 } //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.6: class C { 1 } +//│ ║ l.7: class C { 1 } //│ ╙── ^ class C { } @@ -20,26 +21,26 @@ class C { class C { val a = 1 } with val b = 2 //│ ╔══[COMPILATION ERROR] Multiple type definition bodies are not supported. -//│ ║ l.21: val b = 2 +//│ ║ l.22: val b = 2 //│ ╙── ^^^^^^^^^ :e class C { val a = 1 } { val b = 2 } //│ ╔══[COMPILATION ERROR] Multiple type definition bodies are not supported. -//│ ║ l.27: class C { val a = 1 } { val b = 2 } +//│ ║ l.28: class C { val a = 1 } { val b = 2 } //│ ╙── ^^^^^^^^^^^^^ :e class C { val a = 1 } { val b = 2 } { val c = 2 } //│ ╔══[COMPILATION ERROR] Multiple type definition bodies are not supported. -//│ ║ l.33: class C { val a = 1 } { val b = 2 } { val c = 2 } +//│ ║ l.34: class C { val a = 1 } { val b = 2 } { val c = 2 } //│ ╙── ^^^^^^^^^^^^^ :e class C { val a = 1 } { val b = 2 } with val c = 2 //│ ╔══[COMPILATION ERROR] Multiple type definition bodies are not supported. -//│ ║ l.39: class C { val a = 1 } { val b = 2 } with +//│ ║ l.40: class C { val a = 1 } { val b = 2 } with //│ ╙── ^^^^^^^^^^^^^ @@ -48,21 +49,21 @@ class C { val a = 1 } { val b = 2 } with :w class Foo with { 1 } //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.49: class Foo with { 1 } +//│ ║ l.50: class Foo with { 1 } //│ ╙── ^ :w class Foo with 1 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.56: 1 +//│ ║ l.57: 1 //│ ╙── ^ :e class Foo(42) //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found integer literal -//│ ║ l.63: class Foo(42) +//│ ║ l.64: class Foo(42) //│ ╙── ^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/BadConstructor.mls b/hkmc2/shared/src/test/mlscript/basics/BadConstructor.mls index 7895b9b62c..abc856703e 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadConstructor.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadConstructor.mls @@ -1,38 +1,39 @@ +:. :js :e constructor() //│ ╔══[COMPILATION ERROR] Unsupported constructor in this position. -//│ ║ l.5: constructor() +//│ ║ l.6: constructor() //│ ╙── ^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e constructor(x, y) //│ ╔══[COMPILATION ERROR] 'constructor(...)' declarations are only allowed in class bodies -//│ ║ l.12: constructor(x, y) +//│ ║ l.13: constructor(x, y) //│ ╙── ^^^^^^^^^^^^^^^^^ :e fun main() = constructor(x, y) //│ ╔══[COMPILATION ERROR] 'constructor(...)' declarations are only allowed in class bodies -//│ ║ l.19: constructor(x, y) +//│ ║ l.20: constructor(x, y) //│ ╙── ^^^^^^^^^^^^^^^^^ :e object A with constructor(x, y) //│ ╔══[COMPILATION ERROR] 'constructor(...)' declarations are only allowed in class bodies -//│ ║ l.26: constructor(x, y) +//│ ║ l.27: constructor(x, y) //│ ╙── ^^^^^^^^^^^^^^^^^ :e module A with constructor(x, y) //│ ╔══[COMPILATION ERROR] 'constructor(...)' declarations are only allowed in class bodies -//│ ║ l.33: constructor(x, y) +//│ ║ l.34: constructor(x, y) //│ ╙── ^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls b/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls index 865eaadc2a..196190f34a 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadDefs.mls @@ -1,3 +1,4 @@ +:. :js @@ -30,14 +31,14 @@ val ++ = 0 :e val ++ y = 1 //│ ╔══[COMPILATION ERROR] Invalid value definition head: unexpected identifier in this position -//│ ║ l.31: val ++ y = 1 +//│ ║ l.32: val ++ y = 1 //│ ╙── ^ //│ ++ = 1 :e y //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.38: y +//│ ║ l.39: y //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -48,7 +49,7 @@ y :e fun ++ z = 0 //│ ╔══[COMPILATION ERROR] Invalid function definition head: unexpected identifier in this position -//│ ║ l.49: fun ++ z = 0 +//│ ║ l.50: fun ++ z = 0 //│ ╙── ^ :sjs @@ -62,7 +63,7 @@ fun ++ z = 0 :e fun (class Lol) foo = 0 //│ ╔══[COMPILATION ERROR] This type definition is not a valid symbolic name -//│ ║ l.63: fun (class Lol) foo = 0 +//│ ║ l.64: fun (class Lol) foo = 0 //│ ╙── ^^^^^^^^^ foo @@ -71,7 +72,7 @@ foo :e Lol //│ ╔══[COMPILATION ERROR] Name not found: Lol -//│ ║ l.72: Lol +//│ ║ l.73: Lol //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls b/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls index 74ba5ed90c..0106d3f60f 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls @@ -1,21 +1,20 @@ +:. :js :sjs :e 1::x -//│ ╔══[COMPILATION ERROR] Integer literal is not a known class. -//│ ║ l.6: 1::x -//│ ║ ^ -//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; -//│ ╙── add a space before ‹identifier› to make it an operator application. -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found integer literal. -//│ ║ l.6: 1::x +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.7: 1::x +//│ ║ ^^^ +//│ ╟── Integer literal cannot be used as a projection class. +//│ ║ l.7: 1::x //│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let lambda; //│ lambda = (undefined, function (self, ...args) { -//│ return runtime.safeCall(self.x(...args)) +//│ throw "This code cannot be run as its compilation yielded an error." //│ }); //│ return lambda //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -25,21 +24,25 @@ :e :re 1::x() -//│ ╔══[COMPILATION ERROR] Integer literal is not a known class. -//│ ║ l.27: 1::x() -//│ ║ ^ -//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; -//│ ╙── add a space before ‹identifier› to make it an operator application. -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found integer literal. -//│ ║ l.27: 1::x() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.26: 1::x() +//│ ║ ^^^ +//│ ╟── Integer literal cannot be used as a projection class. +//│ ║ l.26: 1::x() //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.26: 1::x() +//│ ║ ^^^^^ +//│ ╟── Function literal expected 1 argument, but got 0 +//│ ║ l.26: 1::x() +//│ ╙── ^ //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let lambda1; //│ lambda1 = (undefined, function (self, ...args) { //│ runtime.checkArgs("", 1, false, arguments.length); -//│ return runtime.safeCall(self.x(...args)) +//│ throw "This code cannot be run as its compilation yielded an error." //│ }); -//│ block$res2 = runtime.safeCall(lambda1()); +//│ block$res3 = runtime.safeCall(lambda1()); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] Error: Function expected at least 1 argument but got 0 @@ -57,25 +60,21 @@ let x = 1 :e "A"::x -//│ ╔══[COMPILATION ERROR] String literal is not a known class. -//│ ║ l.59: "A"::x -//│ ║ ^^^ -//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; -//│ ╙── add a space before ‹identifier› to make it an operator application. -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found string literal. -//│ ║ l.59: "A"::x +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.62: "A"::x +//│ ║ ^^^ +//│ ╟── String literal cannot be used as a projection class. +//│ ║ l.62: "A"::x //│ ╙── ^^^ //│ = fun :e "A" ::x -//│ ╔══[COMPILATION ERROR] String literal is not a known class. -//│ ║ l.71: "A" ::x -//│ ║ ^^^ -//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; -//│ ╙── add a space before ‹identifier› to make it an operator application. -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found string literal. -//│ ║ l.71: "A" ::x +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.72: "A" ::x +//│ ║ ^^^ +//│ ╟── String literal cannot be used as a projection class. +//│ ║ l.72: "A" ::x //│ ╙── ^^^ //│ = fun diff --git a/hkmc2/shared/src/test/mlscript/basics/BadModules.mls b/hkmc2/shared/src/test/mlscript/basics/BadModules.mls index db0027d9d7..e4ee6c73cf 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadModules.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadModules.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,7 +7,7 @@ class Foo :ge module M extends Foo //│ ╔══[COMPILATION ERROR] Modules cannot have an extension clause. -//│ ║ l.7: module M extends Foo +//│ ║ l.8: module M extends Foo //│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/BadMut.mls b/hkmc2/shared/src/test/mlscript/basics/BadMut.mls index 8a6eb23415..c524c056ae 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadMut.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadMut.mls @@ -1,24 +1,25 @@ +:. :js :e mut "oops" //│ ╔══[COMPILATION ERROR] Illegal position for 'mut' modifier. -//│ ║ l.5: mut "oops" +//│ ║ l.6: mut "oops" //│ ╙── ^^^ //│ = "oops" :e mut a: 1 //│ ╔══[COMPILATION ERROR] Unexpected record key shape. -//│ ║ l.12: mut a: 1 +//│ ║ l.13: mut a: 1 //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e mut {"oops"} //│ ╔══[COMPILATION ERROR] Expected a record after 'mut' keyword; found a block -//│ ║ l.19: mut {"oops"} +//│ ║ l.20: mut {"oops"} //│ ╙── ^^^^^^ //│ = "oops" diff --git a/hkmc2/shared/src/test/mlscript/basics/BadParams.mls b/hkmc2/shared/src/test/mlscript/basics/BadParams.mls index 44d1cb7c85..c92a5fbcf1 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadParams.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadParams.mls @@ -1,43 +1,44 @@ +:. :js :e (val x) => x //│ ╔══[COMPILATION ERROR] Illegal function parameter modifiers: val -//│ ║ l.5: (val x) => x +//│ ║ l.6: (val x) => x //│ ╙── ^ //│ = fun :e (mut x) => x //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found 'mut'-modified identifier -//│ ║ l.12: (mut x) => x +//│ ║ l.13: (mut x) => x //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.12: (mut x) => x +//│ ║ l.13: (mut x) => x //│ ╙── ^ //│ = fun :e (mut val x) => x //│ ╔══[COMPILATION ERROR] Illegal function parameter modifiers: mut val -//│ ║ l.22: (mut val x) => x +//│ ║ l.23: (mut val x) => x //│ ╙── ^ //│ = fun :e fun f(val x) = x //│ ╔══[COMPILATION ERROR] Illegal function parameter modifiers: val -//│ ║ l.29: fun f(val x) = x +//│ ║ l.30: fun f(val x) = x //│ ╙── ^ :e (...val x) => x //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found spread -//│ ║ l.35: (...val x) => x +//│ ║ l.36: (...val x) => x //│ ╙── ^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.35: (...val x) => x +//│ ║ l.36: (...val x) => x //│ ╙── ^ //│ = fun @@ -52,14 +53,14 @@ fun f(val x) = x :e (...x, ...y) => x //│ ╔══[COMPILATION ERROR] Spread parameters must be the last in the parameter list. -//│ ║ l.53: (...x, ...y) => x +//│ ║ l.54: (...x, ...y) => x //│ ╙── ^^^^ //│ = fun :e (..., ...y) => y //│ ╔══[COMPILATION ERROR] Spread parameters must be the last in the parameter list. -//│ ║ l.60: (..., ...y) => y +//│ ║ l.61: (..., ...y) => y //│ ╙── ^^^ //│ = fun @@ -67,7 +68,7 @@ fun f(val x) = x :e class Foo(class C) //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found type definition -//│ ║ l.68: class Foo(class C) +//│ ║ l.69: class Foo(class C) //│ ╙── ^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/BlockArgs.mls b/hkmc2/shared/src/test/mlscript/basics/BlockArgs.mls index c58226b8c7..df2da02a0d 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BlockArgs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BlockArgs.mls @@ -1,31 +1,32 @@ +:. :todo fun foo(x){f} = f(x) //│ ╔══[COMPILATION ERROR] Name not found: f -//│ ║ l.5: f(x) +//│ ║ l.6: f(x) //│ ╙── ^ foo(1){x => x} //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.10: foo(1){x => x} +//│ ║ l.11: foo(1){x => x} //│ ╙── ^^^^^^^^^^^^^^ foo(1) x => x //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (infix operator '=>'). -//│ ║ l.16: x => x +//│ ║ l.17: x => x //│ ╙── ^^^^^^ foo(1) { x => x } //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.21: foo(1) { x => x } +//│ ║ l.22: foo(1) { x => x } //│ ╙── ^^^^^^^^^^^^^^^^^ foo(1) with x => x //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'with' here -//│ ║ l.26: foo(1) with x => x +//│ ║ l.27: foo(1) with x => x //│ ╙── ^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/ByNameTailPosition.mls b/hkmc2/shared/src/test/mlscript/basics/ByNameTailPosition.mls index 296a65c834..897d89144a 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ByNameTailPosition.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ByNameTailPosition.mls @@ -1,3 +1,4 @@ +:. :js // `baz()` should be a call in tail position (like `print(1)`), not bound to a variable diff --git a/hkmc2/shared/src/test/mlscript/basics/CallSyntaxes.mls b/hkmc2/shared/src/test/mlscript/basics/CallSyntaxes.mls index bbbc1ad7b8..6a6c328b29 100644 --- a/hkmc2/shared/src/test/mlscript/basics/CallSyntaxes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/CallSyntaxes.mls @@ -1,3 +1,4 @@ +:. :js @@ -28,10 +29,10 @@ tuple(123, 456) :re 123 |> tuple(., 456) //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.29: 123 |> tuple(., 456) +//│ ║ l.30: 123 |> tuple(., 456) //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.29: 123 |> tuple(., 456) +//│ ║ l.30: 123 |> tuple(., 456) //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/CompanionFunctions.mls b/hkmc2/shared/src/test/mlscript/basics/CompanionFunctions.mls index b9f055c485..685b0711d7 100644 --- a/hkmc2/shared/src/test/mlscript/basics/CompanionFunctions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/CompanionFunctions.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,17 +7,22 @@ fun Foo(x) = new Foo(x, 0) data class Foo(x, y) //│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Foo' -//│ ║ l.6: fun Foo(x) = new Foo(x, 0) +//│ ║ l.7: fun Foo(x) = new Foo(x, 0) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── with class of the same name -//│ ║ l.7: data class Foo(x, y) +//│ ║ l.8: data class Foo(x, y) //│ ╙── ^^^^^^^^^^^^^^^ :fixme Foo(123) -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 1 -//│ ║ l.16: Foo(123) -//│ ╙── ^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.17: Foo(123) +//│ ║ ^^^^^^^^ +//│ ╟── Class constructor 'Foo' expected 2 arguments, but got 1 +//│ ║ l.7: fun Foo(x) = new Foo(x, 0) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.8: data class Foo(x, y) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ //│ = Foo(123, undefined) diff --git a/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Patterns.mls b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Patterns.mls index 3c71f39936..f253600603 100644 --- a/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Patterns.mls +++ b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Patterns.mls @@ -1,3 +1,4 @@ +:. :js @@ -5,10 +6,10 @@ pattern Foo = 1 module Foo //│ ╔══[COMPILATION ERROR] Not yet supported: overloading of pattern 'Foo' -//│ ║ l.5: pattern Foo = 1 +//│ ║ l.6: pattern Foo = 1 //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── with module of the same name -//│ ║ l.6: module Foo +//│ ║ l.7: module Foo //│ ╙── ^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Types.mls b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Types.mls index 6d9744f9ec..ded4f98a75 100644 --- a/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Types.mls +++ b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Types.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/CyclicValues.mls b/hkmc2/shared/src/test/mlscript/basics/CyclicValues.mls index 3ce2008fec..e6f5a54fc1 100644 --- a/hkmc2/shared/src/test/mlscript/basics/CyclicValues.mls +++ b/hkmc2/shared/src/test/mlscript/basics/CyclicValues.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/DataClass.mls b/hkmc2/shared/src/test/mlscript/basics/DataClass.mls index 5ec090d93b..4383e0c843 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DataClass.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DataClass.mls @@ -1,3 +1,4 @@ +:. :js @@ -11,12 +12,10 @@ Foo(1).x :todo data Foo(x: Int) //│ ╔══[WARNING] This annotation has no effect. -//│ ╟── This annotation is not supported on application of type Foo terms. -//│ ║ l.12: data Foo(x: Int) +//│ ╟── This annotation is not supported on application terms. +//│ ║ l.13: data Foo(x: Int) //│ ╙── ^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term -//│ ║ l.12: data Foo(x: Int) -//│ ╙── ^^^ +//│ ═══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/Declare.mls b/hkmc2/shared/src/test/mlscript/basics/Declare.mls index 1cf22cbdcd..f7a3349f7e 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Declare.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Declare.mls @@ -1,3 +1,4 @@ +:. :js @@ -58,29 +59,35 @@ fun isMD(x) = x is M.D C //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=class:C⁰}: -//│ t = Ref{sym=member:C¹} of member:C¹ -//│ sym = class:C⁰ +//│ res = MemberRef of member:C⁰ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— // * Also resolved M.D //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=class:D⁰}: -//│ t = Sel{sym=member:D¹}: -//│ prefix = Resolved{sym=module:M⁰,typ=module:M⁰}: -//│ t = Ref{sym=member:M¹} of member:M¹ -//│ sym = module:M⁰ -//│ nme = Ident of "D" -//│ sym = class:D⁰ +//│ res = NewSel: +//│ prefix = MemberRef of member:M⁰ +//│ id = Ident of "D" //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :fixme // FIXME: Should report a proper error about lack of declared C companion module C.oops -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'oops') - -:re +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.74: C.oops +//│ ║ ^^^^^^ +//│ ╟── Class 'C' does not contain member 'oops' +//│ ║ l.38: declare class C +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:ge M.D.oops -//│ ═══[RUNTIME ERROR] Error: Access to required field 'D' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.84: M.D.oops +//│ ║ ^^^^^^^^ +//│ ╟── Class 'D' does not contain member 'oops' +//│ ║ l.48: declare class D +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/DisruptiveComments.mls b/hkmc2/shared/src/test/mlscript/basics/DisruptiveComments.mls index 5291d3efae..fd98bfe9be 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DisruptiveComments.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DisruptiveComments.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/Drop.mls b/hkmc2/shared/src/test/mlscript/basics/Drop.mls index d93cb4721e..8e58550c18 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Drop.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Drop.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/ExplicitSelections.mls b/hkmc2/shared/src/test/mlscript/basics/ExplicitSelections.mls index f72942cd53..a6846f354c 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ExplicitSelections.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ExplicitSelections.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/FunnyRecordKeys.mls b/hkmc2/shared/src/test/mlscript/basics/FunnyRecordKeys.mls index cfe651dd48..8a6a8faf5b 100644 --- a/hkmc2/shared/src/test/mlscript/basics/FunnyRecordKeys.mls +++ b/hkmc2/shared/src/test/mlscript/basics/FunnyRecordKeys.mls @@ -1,3 +1,4 @@ +:. :js :global diff --git a/hkmc2/shared/src/test/mlscript/basics/Getters.mls b/hkmc2/shared/src/test/mlscript/basics/Getters.mls index 50f80f3ebe..52436d61c0 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Getters.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Getters.mls @@ -1,3 +1,4 @@ +:. :js :ignore @@ -5,7 +6,7 @@ get foo = 123 //│ ╔══[COMPILATION ERROR] Unrecognized definitional assignment left-hand side: juxtaposition -//│ ║ l.6: get foo = 123 +//│ ║ l.7: get foo = 123 //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/IdentEscape.mls b/hkmc2/shared/src/test/mlscript/basics/IdentEscape.mls index e0bfb882b8..83d7100e96 100644 --- a/hkmc2/shared/src/test/mlscript/basics/IdentEscape.mls +++ b/hkmc2/shared/src/test/mlscript/basics/IdentEscape.mls @@ -1,3 +1,4 @@ +:. let id"of" = 42 fun id"fun"(x: Int) = x + 1 @@ -13,11 +14,11 @@ module id"object" with :pe id"" //│ ╔══[LEXICAL ERROR] unexpected identifier escape -//│ ║ l.14: id"" +//│ ║ l.15: id"" //│ ╙── ^ //│ ╔══[PARSE ERROR] Expected an expression; found error instead -//│ ║ l.14: id"" +//│ ║ l.15: id"" //│ ╙── ^^^^ //│ ╔══[PARSE ERROR] Expected end of input; found error instead -//│ ║ l.14: id"" +//│ ║ l.15: id"" //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/IfThenNewline.mls b/hkmc2/shared/src/test/mlscript/basics/IfThenNewline.mls index 384646a237..178e6a9eb3 100644 --- a/hkmc2/shared/src/test/mlscript/basics/IfThenNewline.mls +++ b/hkmc2/shared/src/test/mlscript/basics/IfThenNewline.mls @@ -1,3 +1,4 @@ +:. :js // Regression test: `if true` with `then` on a new indented line should work diff --git a/hkmc2/shared/src/test/mlscript/basics/Indentation.mls b/hkmc2/shared/src/test/mlscript/basics/Indentation.mls index 73c8a0bd67..6ede3021b4 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Indentation.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Indentation.mls @@ -1,3 +1,4 @@ +:. :js @@ -48,11 +49,11 @@ tuple( tuple() tuple() //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (juxtaposition). -//│ ║ l.47: tuple() +//│ ║ l.48: tuple() //│ ║ ^^^^^^^ -//│ ║ l.48: tuple() +//│ ║ l.49: tuple() //│ ║ ^^^^^^^^^^^ -//│ ║ l.49: tuple() +//│ ║ l.50: tuple() //│ ╙── ^^ //│ = [[4]] @@ -72,12 +73,12 @@ tuple( 1 ) //│ ╔══[WARNING] This literal should be indented -//│ ║ l.72: 1 +//│ ║ l.73: 1 //│ ║ ^ //│ ╟── since it is a continuation of the new line here -//│ ║ l.71: tuple( +//│ ║ l.72: tuple( //│ ║ ^ -//│ ║ l.72: 1 +//│ ║ l.73: 1 //│ ╙── //│ = [1] @@ -139,7 +140,7 @@ id //│ Parsed: //│ Jux(Ident(id),Block(List(IntLit(1)))) //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (integer literal). -//│ ║ l.137: 1 +//│ ║ l.138: 1 //│ ╙── ^ //│ = fun id @@ -152,7 +153,7 @@ id(0 //│ Parsed: //│ App(App(Ident(id),Tup(List(Jux(IntLit(0),Block(List(Ident(id))))))),Tup(List(IntLit(1)))) //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). -//│ ║ l.150: id)(1) +//│ ║ l.151: id)(1) //│ ╙── ^^ //│ ═══[RUNTIME ERROR] TypeError: 0 is not a function diff --git a/hkmc2/shared/src/test/mlscript/basics/LabeledIfWhileBodies.mls b/hkmc2/shared/src/test/mlscript/basics/LabeledIfWhileBodies.mls index 40d798b484..438df15cf9 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LabeledIfWhileBodies.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LabeledIfWhileBodies.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls index 9eb9a5259b..08ab088c96 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls @@ -1,3 +1,4 @@ +:. :js :expect [[1, 2], 3] @@ -78,13 +79,23 @@ sum2(arr) :e fun f(..xs) = xs //│ ╔══[COMPILATION ERROR] Lazy spread parameters not allowed. -//│ ║ l.79: fun f(..xs) = xs +//│ ║ l.80: fun f(..xs) = xs //│ ╙── ^^^^ :ge sum2(..arr) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.86: sum2(..arr) +//│ ║ ^^^^^^^^^^^ +//│ ╟── Function literal expected 1 argument, but got 1 +//│ ║ l.47: fun sum2 = case +//│ ║ ^^^^ +//│ ║ l.48: [] then 0 +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.49: [x, ..xs, y] then x + y + sum2(xs) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Lazy spreads are not supported in call arguments -//│ ║ l.85: sum2(..arr) +//│ ║ l.86: sum2(..arr) //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 2 diff --git a/hkmc2/shared/src/test/mlscript/basics/LetBindings.mls b/hkmc2/shared/src/test/mlscript/basics/LetBindings.mls index 89aec1198a..d651a47ca4 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LetBindings.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LetBindings.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/Literals.mls b/hkmc2/shared/src/test/mlscript/basics/Literals.mls index 0b50e2de03..6574f18ba1 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Literals.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Literals.mls @@ -1,3 +1,4 @@ +:. :js @@ -15,14 +16,14 @@ :w (1, 2) //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.16: (1, 2) +//│ ║ l.17: (1, 2) //│ ╙── ^ //│ = 2 :w (1, 2,) //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.23: (1, 2,) +//│ ║ l.24: (1, 2,) //│ ╙── ^ //│ = 2 @@ -54,7 +55,7 @@ undefined :todo NaN //│ ╔══[COMPILATION ERROR] Name not found: NaN -//│ ║ l.55: NaN +//│ ║ l.56: NaN //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -74,7 +75,7 @@ NaN :e 'AA BB' //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). -//│ ║ l.75: 'AA BB' +//│ ║ l.76: 'AA BB' //│ ╙── ^^^ //│ = "AA" @@ -97,42 +98,42 @@ NaN :w "\uFE&&" //│ ╔══[WARNING] Invalid Unicode escape sequence: expected 4 hexadecimal digits but got 2 -//│ ║ l.98: "\uFE&&" +//│ ║ l.99: "\uFE&&" //│ ╙── ^^ //│ = "&&" :w "\u{}" //│ ╔══[WARNING] Expected at least one hexadecimal digit in Unicode escape sequence -//│ ║ l.105: "\u{}" +//│ ║ l.106: "\u{}" //│ ╙── ^ //│ = "" :w "\u{ffffffff}" //│ ╔══[WARNING] Too many hexadecimal digits in Unicode escape sequence -//│ ║ l.112: "\u{ffffffff}" +//│ ║ l.113: "\u{ffffffff}" //│ ╙── ^^ //│ = "" :w "\u{efefef}" //│ ╔══[WARNING] Unicode code point out of range: 0xefefef -//│ ║ l.119: "\u{efefef}" +//│ ║ l.120: "\u{efefef}" //│ ╙── ^^^^^^ //│ = "" :w "\u{55" //│ ╔══[WARNING] Unterminated Unicode escape sequence: missing '}' -//│ ║ l.126: "\u{55" +//│ ║ l.127: "\u{55" //│ ╙── ^ //│ = "U" :w "\u{41****" //│ ╔══[WARNING] Unterminated Unicode escape sequence: missing '}' -//│ ║ l.133: "\u{41****" +//│ ║ l.134: "\u{41****" //│ ╙── ^ //│ = "A****" diff --git a/hkmc2/shared/src/test/mlscript/basics/LocalVal.mls b/hkmc2/shared/src/test/mlscript/basics/LocalVal.mls index 99fa0bac75..644d6a65ab 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LocalVal.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LocalVal.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls index b60b96b6cc..337934bcdb 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls @@ -1,3 +1,4 @@ +:. :js @@ -36,10 +37,10 @@ M.Foo ::n (foo, 2) :e M.Foo:: n(foo, 2) //│ ╔══[COMPILATION ERROR] Name not found: :: -//│ ║ l.37: M.Foo:: n(foo, 2) +//│ ║ l.38: M.Foo:: n(foo, 2) //│ ╙── ^^ //│ ╔══[COMPILATION ERROR] Name not found: n -//│ ║ l.37: M.Foo:: n(foo, 2) +//│ ║ l.38: M.Foo:: n(foo, 2) //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -78,52 +79,57 @@ do :e foo.Foo#m() //│ ╔══[COMPILATION ERROR] Name not found: Foo -//│ ║ l.79: foo.Foo#m() +//│ ║ l.80: foo.Foo#m() //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Identifier `Foo` does not name a known class symbol. -//│ ║ l.79: foo.Foo#m() +//│ ╔══[COMPILATION ERROR] Cannot resolve the projection class +//│ ║ l.80: foo.Foo#m() //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found ‹error›. -//│ ║ l.79: foo.Foo#m() -//│ ╙── ^^^^ -//│ = 124 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :re foo.M.Foo::m() -//│ ╔══[COMPILATION ERROR] Selection is not a known class. -//│ ║ l.93: foo.M.Foo::m() -//│ ║ ^^^^^^^^^ -//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; -//│ ╙── add a space before ‹identifier› to make it an operator application. +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.91: foo.M.Foo::m() +//│ ║ ^^^^^ +//│ ╟── Instance of class constructor 'Foo' does not contain member 'M' +//│ ║ l.11: let foo = M.Foo(123) +//│ ╙── ^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.91: foo.M.Foo::m() +//│ ║ ^^^^^ +//│ ╟── Function literal expected 1 argument, but got 0 +//│ ║ l.91: foo.M.Foo::m() +//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Cannot resolve the projection class +//│ ║ l.91: foo.M.Foo::m() +//│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected at least 1 argument but got 0 :e :re foo.M.Foo#m() //│ ╔══[COMPILATION ERROR] Name not found: Foo -//│ ║ l.103: foo.M.Foo#m() -//│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Identifier `Foo` does not name a known class symbol. -//│ ║ l.103: foo.M.Foo#m() +//│ ║ l.111: foo.M.Foo#m() //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found ‹error›. -//│ ║ l.103: foo.M.Foo#m() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.111: foo.M.Foo#m() +//│ ║ ^^^^^ +//│ ╟── Instance of class constructor 'Foo' does not contain member 'M' +//│ ║ l.11: let foo = M.Foo(123) +//│ ╙── ^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Cannot resolve the projection class +//│ ║ l.111: foo.M.Foo#m() //│ ╙── ^^^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'M' yielded 'undefined' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e Foo::m //│ ╔══[COMPILATION ERROR] Name not found: Foo -//│ ║ l.116: Foo::m +//│ ║ l.127: Foo::m //│ ╙── ^^^ -//│ ╔══[COMPILATION ERROR] Identifier is not a known class. -//│ ║ l.116: Foo::m -//│ ║ ^^^ -//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; -//│ ╙── add a space before ‹identifier› to make it an operator application. -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found ‹error›. -//│ ║ l.116: Foo::m +//│ ╔══[COMPILATION ERROR] Cannot resolve the projection class +//│ ║ l.127: Foo::m //│ ╙── ^^^ //│ = fun @@ -132,23 +138,18 @@ Foo::m :noInline Foo::n(foo, 2) //│ ╔══[COMPILATION ERROR] Name not found: Foo -//│ ║ l.133: Foo::n(foo, 2) +//│ ║ l.139: Foo::n(foo, 2) //│ ╙── ^^^ -//│ ╔══[COMPILATION ERROR] Identifier is not a known class. -//│ ║ l.133: Foo::n(foo, 2) -//│ ║ ^^^ -//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; -//│ ╙── add a space before ‹identifier› to make it an operator application. -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found ‹error›. -//│ ║ l.133: Foo::n(foo, 2) +//│ ╔══[COMPILATION ERROR] Cannot resolve the projection class +//│ ║ l.139: Foo::n(foo, 2) //│ ╙── ^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let lambda2; //│ lambda2 = (undefined, function (self7, ...args7) { -//│ return runtime.safeCall(self7.n(...args7)) +//│ throw "This code cannot be run as its compilation yielded an error." //│ }); //│ return runtime.safeCall(lambda2(foo, 2)) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = 125 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/Modules.mls b/hkmc2/shared/src/test/mlscript/basics/Modules.mls index 1f45a66355..a20a58211d 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Modules.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Modules.mls @@ -1,3 +1,4 @@ +:. // :flow @@ -6,20 +7,23 @@ module Foo :pe module Foo with //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.7: module Foo with +//│ ║ l.8: module Foo with //│ ╙── ^ :e module Foo with val x = 1 //│ ╔══[COMPILATION ERROR] Illegal body of module definition (should be a block; found term definition). -//│ ║ l.13: module Foo with val x = 1 +//│ ║ l.14: module Foo with val x = 1 //│ ╙── ^^^^^^^^^ :e Foo.x -//│ ╔══[COMPILATION ERROR] Module 'Foo' does not contain member 'x' -//│ ║ l.19: Foo.x -//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.20: Foo.x +//│ ║ ^^^^^ +//│ ╟── Module 'Foo' does not contain member 'x' +//│ ║ l.14: module Foo with val x = 1 +//│ ╙── ^^^ module Foo with val x = 1 diff --git a/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls b/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls index 7500c8d032..91d237d784 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls @@ -1,3 +1,4 @@ +:. :js :global diff --git a/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls b/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls index ca54d78305..daf393d2bd 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls @@ -1,3 +1,4 @@ +:. :js @@ -113,23 +114,23 @@ if 1 + 2 * 3 then 0 + 4 then 1 //│ ╔══[PARSE ERROR] Operator cannot be used inside this operator split -//│ ║ l.114: + 4 then 1 +//│ ║ l.115: + 4 then 1 //│ ║ ^ //│ ╟── as it has lower precedence than the splitting operator here -//│ ║ l.113: * 3 then 0 +//│ ║ l.114: * 3 then 0 //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected 'then' keyword in this operator split inner position -//│ ║ l.114: + 4 then 1 +//│ ║ l.115: + 4 then 1 //│ ║ ^^^^ //│ ╟── Note: the operator split starts here -//│ ║ l.113: * 3 then 0 +//│ ║ l.114: * 3 then 0 //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.112: if 1 + 2 +//│ ║ l.113: if 1 + 2 //│ ║ ^ -//│ ║ l.113: * 3 then 0 +//│ ║ l.114: * 3 then 0 //│ ║ ^^^^^^^^^^^^ -//│ ║ l.114: + 4 then 1 +//│ ║ l.115: + 4 then 1 //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/MutCls.mls b/hkmc2/shared/src/test/mlscript/basics/MutCls.mls index 44f19580c0..2963822636 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MutCls.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MutCls.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/NamedArgs.mls b/hkmc2/shared/src/test/mlscript/basics/NamedArgs.mls index ce10cfd96a..3c21e07c90 100644 --- a/hkmc2/shared/src/test/mlscript/basics/NamedArgs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/NamedArgs.mls @@ -1,3 +1,4 @@ +:. :js @@ -34,9 +35,9 @@ id(a: 0) //│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— //│ Blk: //│ res = App: -//│ lhs = SynthSel{sym=member:id⁰}: -//│ prefix = Ref{sym=member:Predef⁰} of member:Predef⁰ -//│ nme = Ident of "id" +//│ lhs = NewSel: +//│ prefix = MemberRef of member:Predef⁰ +//│ id = Ident of "id" //│ rhs = Tup of Ls of //│ Fld: //│ term = Lit of StrLit of "a" diff --git a/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls index e26a4780cb..c74f97215f 100644 --- a/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls +++ b/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls @@ -1,3 +1,4 @@ +:. :js @@ -10,10 +11,13 @@ object Foo with :e :re Foo.x -//│ ╔══[COMPILATION ERROR] Object 'Foo' does not contain member 'x' -//│ ║ l.12: Foo.x -//│ ╙── ^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.13: Foo.x +//│ ║ ^^^^^ +//│ ╟── Object 'Foo' does not contain member 'x' +//│ ║ l.7: object Foo with +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. module Oops with @@ -29,18 +33,30 @@ object Oops with val fakeField = 1 print(fakeField) print(this.fakeField) // shouldn't work! -//│ ╔══[COMPILATION ERROR] Object 'Oops' does not contain member 'fakeField' -//│ ║ l.31: print(this.fakeField) // shouldn't work! -//│ ╙── ^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.35: print(this.fakeField) // shouldn't work! +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Module/object definition 'Oops' does not contain member 'fakeField' +//│ ║ l.32: if true then +//│ ║ ^^^^^^^^^ +//│ ║ l.33: val fakeField = 1 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.34: print(fakeField) +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.35: print(this.fakeField) // shouldn't work! +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ > 1 -//│ ═══[RUNTIME ERROR] Error: Access to required field 'fakeField' yielded 'undefined' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :re Oops.fakeField -//│ ╔══[COMPILATION ERROR] Object 'Oops' does not contain member 'fakeField' -//│ ║ l.40: Oops.fakeField -//│ ╙── ^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'fakeField' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.53: Oops.fakeField +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Object 'Oops' does not contain member 'fakeField' +//│ ║ l.31: object Oops with +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/NewlineOperatorEnd.mls b/hkmc2/shared/src/test/mlscript/basics/NewlineOperatorEnd.mls index 9d3001c0b7..0ec57cfeea 100644 --- a/hkmc2/shared/src/test/mlscript/basics/NewlineOperatorEnd.mls +++ b/hkmc2/shared/src/test/mlscript/basics/NewlineOperatorEnd.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls b/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls index 1c234ac025..f771e6303c 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls @@ -1,3 +1,4 @@ +:. :js @@ -14,12 +15,18 @@ foo of (id of x => 1), id of y => 2 :e :re foo(id of x => 1, id of y => 2) -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 1 -//│ ║ l.16: foo(id of x => 1, id of y => 2) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 2 -//│ ║ l.16: foo(id of x => 1, id of y => 2) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.17: foo(id of x => 1, id of y => 2) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'id' expected 1 argument, but got 2 +//│ ║ l.5: fun id(x) = x +//│ ╙── ^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.17: foo(id of x => 1, id of y => 2) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'foo' expected 2 arguments, but got 1 +//│ ║ l.6: fun foo(f, g) = f(g(1)) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'id' expected 1 argument but got 2 diff --git a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls index 02b93b5255..6d2a993698 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls @@ -1,3 +1,4 @@ +:. :js @@ -95,12 +96,12 @@ + 1 * 3 //│ ╔══[PARSE ERROR] Unexpected indented block in this operator split inner position -//│ ║ l.95: + 1 +//│ ║ l.96: + 1 //│ ║ ^^^^^^^ -//│ ║ l.96: * 3 +//│ ║ l.97: * 3 //│ ║ ^^ //│ ╟── Note: the operator split starts here -//│ ║ l.94: * 2 +//│ ║ l.95: * 2 //│ ╙── ^ //│ = 12 @@ -110,10 +111,10 @@ 1 * 3 //│ ╔══[PARSE ERROR] Operator cannot be used inside this operator split -//│ ║ l.109: * 2 + +//│ ║ l.110: * 2 + //│ ║ ^ //│ ╟── as it has lower precedence than the splitting operator here -//│ ║ l.109: * 2 + +//│ ║ l.110: * 2 + //│ ╙── ^ //│ = 19 @@ -123,17 +124,17 @@ * 3 + 1 //│ ╔══[PARSE ERROR] Unexpected indented block in this operator split inner position -//│ ║ l.124: + 1 +//│ ║ l.125: + 1 //│ ║ ^^^^ //│ ╟── Note: the operator split starts here -//│ ║ l.122: * 2 +//│ ║ l.123: * 2 //│ ╙── ^ //│ = 6 :e // * Note: we used to parse such curly braces equivalently to an indented section, so this used to work 1 + 2 {* 3 {+ 1}} //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.134: 1 + 2 {* 3 {+ 1}} +//│ ║ l.135: 1 + 2 {* 3 {+ 1}} //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -147,10 +148,10 @@ * 3 + 1 * 3 //│ ╔══[PARSE ERROR] Operator cannot be used inside this operator split -//│ ║ l.147: * 3 + 1 +//│ ║ l.148: * 3 + 1 //│ ║ ^ //│ ╟── as it has lower precedence than the splitting operator here -//│ ║ l.147: * 3 + 1 +//│ ║ l.148: * 3 + 1 //│ ╙── ^ //│ = 28 @@ -171,7 +172,7 @@ 1 0 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.171: 1 +//│ ║ l.172: 1 //│ ╙── ^ //│ = true @@ -366,9 +367,9 @@ fun f(x) = if x foo(A) then a bar(B) then b //│ ╔══[COMPILATION ERROR] Unrecognized term split (juxtaposition) -//│ ║ l.365: fun f(x) = if x +//│ ║ l.366: fun f(x) = if x //│ ║ ^ -//│ ║ l.366: foo(A) then a +//│ ║ l.367: foo(A) then a //│ ╙── ^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/OpDivPrecedence.mls b/hkmc2/shared/src/test/mlscript/basics/OpDivPrecedence.mls index 19e9edfaa1..cc9b798817 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OpDivPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OpDivPrecedence.mls @@ -1,3 +1,4 @@ +:. :js // Division should have the same precedence as multiplication diff --git a/hkmc2/shared/src/test/mlscript/basics/OutParams.mls b/hkmc2/shared/src/test/mlscript/basics/OutParams.mls index 131ee8d754..63ed811ee9 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OutParams.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OutParams.mls @@ -1,3 +1,4 @@ +:. :js :todo @@ -15,22 +16,22 @@ fun typeTerm(_, set ctx) = print(ctx) ctx = 123 //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found set -//│ ║ l.14: fun typeTerm(_, set ctx) = +//│ ║ l.15: fun typeTerm(_, set ctx) = //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Name not found: ctx -//│ ║ l.15: print(ctx) +//│ ║ l.16: print(ctx) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Name not found: ctx -//│ ║ l.16: ctx = 123 +//│ ║ l.17: ctx = 123 //│ ╙── ^^^ fun typeTerm(_, let ctx) = ctx = 123 //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found let -//│ ║ l.27: fun typeTerm(_, let ctx) = +//│ ║ l.28: fun typeTerm(_, let ctx) = //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Name not found: ctx -//│ ║ l.28: ctx = 123 +//│ ║ l.29: ctx = 123 //│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/PartialApps.mls b/hkmc2/shared/src/test/mlscript/basics/PartialApps.mls index 704b703943..781bb4d878 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PartialApps.mls @@ -1,3 +1,4 @@ +:. :js @@ -5,10 +6,10 @@ :re . //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.6: . +//│ ║ l.7: . //│ ╙── ^ //│ ╔══[PARSE ERROR] Expected end of input; found period instead -//│ ║ l.6: . +//│ ║ l.7: . //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -16,10 +17,10 @@ :re . // //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.17: . // +//│ ║ l.18: . // //│ ╙── ^ //│ ╔══[PARSE ERROR] Expected end of input; found period instead -//│ ║ l.17: . // +//│ ║ l.18: . // //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -27,10 +28,10 @@ :fixme id(.) //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.28: id(.) +//│ ║ l.29: id(.) //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.28: id(.) +//│ ║ l.29: id(.) //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -52,10 +53,10 @@ passTo(2, incr @ _)() :fixme passTo(2, incr(.) @ _)() //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.53: passTo(2, incr(.) @ _)() +//│ ║ l.54: passTo(2, incr(.) @ _)() //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.53: passTo(2, incr(.) @ _)() +//│ ║ l.54: passTo(2, incr(.) @ _)() //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -70,11 +71,17 @@ passTo(2, add(_, 2))() :fixme passTo(2, add(., 2))() //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.71: passTo(2, add(., 2))() +//│ ║ l.72: passTo(2, add(., 2))() //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.71: passTo(2, add(., 2))() +//│ ║ l.72: passTo(2, add(., 2))() //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.72: passTo(2, add(., 2))() +//│ ║ ^^^^^^^^^ +//│ ╟── Function literal expected 2 arguments, but got 1 +//│ ║ l.40: fun add = _ + _ +//│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :breakme @@ -91,21 +98,27 @@ passTo(1, (add(_, 1)) @ _ + _)(2) :fixme passTo(1, incr(.) @ _ + _)(2) //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.92: passTo(1, incr(.) @ _ + _)(2) +//│ ║ l.99: passTo(1, incr(.) @ _ + _)(2) //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.92: passTo(1, incr(.) @ _ + _)(2) +//│ ║ l.99: passTo(1, incr(.) @ _ + _)(2) //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme passTo(1, add(., 1) @ _ + _)(2) //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.102: passTo(1, add(., 1) @ _ + _)(2) +//│ ║ l.109: passTo(1, add(., 1) @ _ + _)(2) //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.102: passTo(1, add(., 1) @ _ + _)(2) +//│ ║ l.109: passTo(1, add(., 1) @ _ + _)(2) //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.109: passTo(1, add(., 1) @ _ + _)(2) +//│ ║ ^^^^^^^^^ +//│ ╟── Function literal expected 2 arguments, but got 1 +//│ ║ l.40: fun add = _ + _ +//│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -120,11 +133,17 @@ let f = add(_, 1) :fixme let f = add(., 1) //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.121: let f = add(., 1) +//│ ║ l.134: let f = add(., 1) //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.121: let f = add(., 1) +//│ ║ l.134: let f = add(., 1) //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.134: let f = add(., 1) +//│ ║ ^^^^^^^^^ +//│ ╟── Function literal expected 2 arguments, but got 1 +//│ ║ l.40: fun add = _ + _ +//│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ f = undefined diff --git a/hkmc2/shared/src/test/mlscript/basics/PostfixOps.mls b/hkmc2/shared/src/test/mlscript/basics/PostfixOps.mls index 2523548438..f45a3de188 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PostfixOps.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PostfixOps.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,10 +7,10 @@ :re 0 | //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.7: 0 | +//│ ║ l.8: 0 | //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) -//│ ║ l.7: 0 | +//│ ║ l.8: 0 | //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -20,14 +21,14 @@ 0 | 1 //│ ╔══[PARSE ERROR] Expected start of expression in this position; found new line instead -//│ ║ l.20: 0 | +//│ ║ l.21: 0 | //│ ║ ^ -//│ ║ l.21: 1 +//│ ║ l.22: 1 //│ ╙── //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) -//│ ║ l.20: 0 | +//│ ║ l.21: 0 | //│ ║ ^^^ -//│ ║ l.21: 1 +//│ ║ l.22: 1 //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -37,13 +38,13 @@ fun inc(x) = x + 1 :todo // ? inc of 1 | + 1 //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) -//│ ║ l.38: inc of 1 | + 1 +//│ ║ l.39: inc of 1 | + 1 //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :todo // ? 1 |> inc | + 1 //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) -//│ ║ l.45: 1 |> inc | + 1 +//│ ║ l.46: 1 |> inc | + 1 //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls b/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls index 35009b2c8b..348b1a454d 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls @@ -1,22 +1,23 @@ +:. :js :w do 1 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.5: do 1 +//│ ║ l.6: do 1 //│ ╙── ^ :w if true do 1 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.11: if true do 1 +//│ ║ l.12: if true do 1 //│ ╙── ^ :w if true do () else 1 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.17: if true do () else 1 +//│ ║ l.18: if true do () else 1 //│ ╙── ^ if true then () else 1 @@ -25,22 +26,22 @@ if true then () else 1 :w x => x, 0 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.26: x => x, 0 -//│ ╙── ^^^^^^ +//│ ║ l.27: x => x, 0 +//│ ╙── ^ //│ = 0 :w () => 1 2 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.33: () => 1 +//│ ║ l.34: () => 1 //│ ╙── ^^^^^^^ //│ = 2 :w case x then x, 0 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.41: case x then x, 0 +//│ ║ l.42: case x then x, 0 //│ ╙── ^^^^^^^^^^^^^ //│ = 0 @@ -48,7 +49,7 @@ case x then x, 0 case x then x case x then x //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.48: case x then x +//│ ║ l.49: case x then x //│ ╙── ^^^^^^^^^^^^^ //│ = fun @@ -57,27 +58,21 @@ case x then x fun foo(x) = x x -//│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.58: x -//│ ╙── ^ +//│ ═══[WARNING] Pure expression in statement position :w fun foo() = val x = 1 x x -//│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.67: x -//│ ╙── ^ +//│ ═══[WARNING] Pure expression in statement position :w fun foo() = let x = 1 x x -//│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.76: x -//│ ╙── ^ +//│ ═══[WARNING] Pure expression in statement position // * Note that uses of `;` currently do not warn... diff --git a/hkmc2/shared/src/test/mlscript/basics/Return.mls b/hkmc2/shared/src/test/mlscript/basics/Return.mls index 1f25cac6ee..cf6c8347d5 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Return.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Return.mls @@ -1,3 +1,4 @@ +:. :js @@ -39,14 +40,14 @@ foo(123) :e return "whoops" //│ ╔══[COMPILATION ERROR] Return statements are not allowed outside of functions. -//│ ║ l.40: return "whoops" +//│ ║ l.41: return "whoops" //│ ╙── ^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e x => return "whoops" //│ ╔══[COMPILATION ERROR] Return statements are not allowed outside of functions. -//│ ║ l.47: x => return "whoops" +//│ ║ l.48: x => return "whoops" //│ ╙── ^^^^^^^^^^^^^^^ //│ = fun @@ -54,19 +55,13 @@ x => return "whoops" fun foo() = x => return "whoops" //│ ╔══[COMPILATION ERROR] Non-local return statements are only supported with effect handlers enabled. -//│ ║ l.55: x => return "whoops" +//│ ║ l.56: x => return "whoops" //│ ╙── ^^^^^^^^^^^^^^^ :e fun foo(x: return "whoops") = 1 //│ ╔══[COMPILATION ERROR] Return statements are not allowed in this context. -//│ ║ l.61: fun foo(x: return "whoops") = 1 -//│ ╙── ^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.61: fun foo(x: return "whoops") = 1 -//│ ╙── ^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Expected a type, got ‹error› -//│ ║ l.61: fun foo(x: return "whoops") = 1 +//│ ║ l.62: fun foo(x: return "whoops") = 1 //│ ╙── ^^^^^^^^^^^^^^^ :e @@ -74,13 +69,7 @@ fun foo() = fun bar(x: return "whoops") = 1 bar(123) //│ ╔══[COMPILATION ERROR] Return statements are not allowed in this context. -//│ ║ l.74: fun bar(x: return "whoops") = 1 -//│ ╙── ^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.74: fun bar(x: return "whoops") = 1 -//│ ╙── ^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Expected a type, got ‹error› -//│ ║ l.74: fun bar(x: return "whoops") = 1 +//│ ║ l.69: fun bar(x: return "whoops") = 1 //│ ╙── ^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/SetIndented.mls b/hkmc2/shared/src/test/mlscript/basics/SetIndented.mls index f79bba5b3e..0ba50aef81 100644 --- a/hkmc2/shared/src/test/mlscript/basics/SetIndented.mls +++ b/hkmc2/shared/src/test/mlscript/basics/SetIndented.mls @@ -1,3 +1,4 @@ +:. :js // Same-line set with member selection parses fine @@ -6,11 +7,14 @@ set this.pc = 0 set (this).pc = 0 //│ ╔══[COMPILATION ERROR] Cannot use 'this' outside of an object scope -//│ ║ l.5: set this.pc = 0 +//│ ║ l.6: set this.pc = 0 //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Cannot use 'this' outside of an object scope -//│ ║ l.7: (this).pc = 0 +//│ ║ l.8: (this).pc = 0 //│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Assignment requires an unambiguous term member +//│ ║ l.6: set this.pc = 0 +//│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Indented set with member selection should also parse fine @@ -18,6 +22,9 @@ set set this.pc = 0 //│ ╔══[COMPILATION ERROR] Cannot use 'this' outside of an object scope -//│ ║ l.19: this.pc = 0 +//│ ║ l.23: this.pc = 0 //│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Assignment requires an unambiguous term member +//│ ║ l.23: this.pc = 0 +//│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/ShortcircuitingOps.mls b/hkmc2/shared/src/test/mlscript/basics/ShortcircuitingOps.mls index 4fde079240..f1e20720e2 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ShortcircuitingOps.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ShortcircuitingOps.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/SuspensionComments.mls b/hkmc2/shared/src/test/mlscript/basics/SuspensionComments.mls index 20d6ace54a..46063dbcdf 100644 --- a/hkmc2/shared/src/test/mlscript/basics/SuspensionComments.mls +++ b/hkmc2/shared/src/test/mlscript/basics/SuspensionComments.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/SymbolicSuffixIdentifiers.mls b/hkmc2/shared/src/test/mlscript/basics/SymbolicSuffixIdentifiers.mls index e5a605d4ba..14fbc84542 100644 --- a/hkmc2/shared/src/test/mlscript/basics/SymbolicSuffixIdentifiers.mls +++ b/hkmc2/shared/src/test/mlscript/basics/SymbolicSuffixIdentifiers.mls @@ -1,3 +1,4 @@ +:. :js @@ -29,7 +30,7 @@ box_#.add_++(1) :sjs fun (++) foo_+(x) = x + 1 //│ ╔══[COMPILATION ERROR] Cannot combine an explicit symbolic name with a symbolic suffix identifier. -//│ ║ l.30: fun (++) foo_+(x) = x + 1 +//│ ║ l.31: fun (++) foo_+(x) = x + 1 //│ ╙── ^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let foo; foo = function foo(x) { return x + 1 }; @@ -39,7 +40,7 @@ fun (++) foo_+(x) = x + 1 :sjs foo_+ //│ ╔══[COMPILATION ERROR] Name not found: foo_+ -//│ ║ l.40: foo_+ +//│ ║ l.41: foo_+ //│ ╙── ^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ throw "This code cannot be run as its compilation yielded an error." @@ -50,7 +51,7 @@ foo_+ :sjs (++) //│ ╔══[COMPILATION ERROR] Name not found: ++ -//│ ║ l.51: (++) +//│ ║ l.52: (++) //│ ╙── ^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ throw "This code cannot be run as its compilation yielded an error." diff --git a/hkmc2/shared/src/test/mlscript/basics/ThenRecord.mls b/hkmc2/shared/src/test/mlscript/basics/ThenRecord.mls index 5dd844d7d6..44f7958d86 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ThenRecord.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ThenRecord.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/basics/TryFinally.mls b/hkmc2/shared/src/test/mlscript/basics/TryFinally.mls index 598130de01..889de8b6dc 100644 --- a/hkmc2/shared/src/test/mlscript/basics/TryFinally.mls +++ b/hkmc2/shared/src/test/mlscript/basics/TryFinally.mls @@ -1,3 +1,4 @@ +:. :js @@ -28,10 +29,10 @@ finally print("done") try 42 finally print("done") //│ ╔══[PARSE ERROR] Unexpected 'finally' keyword here -//│ ║ l.29: 42 finally print("done") +//│ ║ l.30: 42 finally print("done") //│ ╙── ^^^^^^^ //│ ╔══[PARSE ERROR] Expected 'finally' keyword after `try` body; found end of input instead -//│ ║ l.29: 42 finally print("done") +//│ ║ l.30: 42 finally print("done") //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -41,13 +42,13 @@ try 42 finally print("done") //│ ╔══[PARSE ERROR] Expected start of expression in this position; found 'finally' keyword instead -//│ ║ l.42: finally print("done") +//│ ║ l.43: finally print("done") //│ ╙── ^^^^^^^ //│ ╔══[PARSE ERROR] Unexpected identifier here -//│ ║ l.42: finally print("done") +//│ ║ l.43: finally print("done") //│ ╙── ^^^^^ //│ ╔══[PARSE ERROR] Expected 'finally' keyword after `try` body; found end of input instead -//│ ║ l.41: 42 +//│ ║ l.42: 42 //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/WeirdCalls.mls b/hkmc2/shared/src/test/mlscript/basics/WeirdCalls.mls index a00ae7a617..2d93e75fc3 100644 --- a/hkmc2/shared/src/test/mlscript/basics/WeirdCalls.mls +++ b/hkmc2/shared/src/test/mlscript/basics/WeirdCalls.mls @@ -1,3 +1,4 @@ +:. :js @@ -14,11 +15,11 @@ print "A" ) //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (parenthesis section). -//│ ║ l.13: ( +//│ ║ l.14: ( //│ ║ ^ -//│ ║ l.14: "A" +//│ ║ l.15: "A" //│ ║ ^^^^^^^ -//│ ║ l.15: ) +//│ ║ l.16: ) //│ ╙── ^^^ //│ = fun print @@ -28,16 +29,16 @@ print of "A" ) //│ ╔══[WARNING] This parenthesis section should be indented -//│ ║ l.27: ( +//│ ║ l.28: ( //│ ║ ^ -//│ ║ l.28: "A" +//│ ║ l.29: "A" //│ ║ ^^^^^ -//│ ║ l.29: ) +//│ ║ l.30: ) //│ ║ ^ //│ ╟── since it is a continuation of the new line here -//│ ║ l.26: print of +//│ ║ l.27: print of //│ ║ ^ -//│ ║ l.27: ( +//│ ║ l.28: ( //│ ╙── //│ > A @@ -45,12 +46,12 @@ print of print of ("A") //│ ╔══[WARNING] This parenthesis section should be indented -//│ ║ l.46: ("A") +//│ ║ l.47: ("A") //│ ║ ^^^^^ //│ ╟── since it is a continuation of the new line here -//│ ║ l.45: print of +//│ ║ l.46: print of //│ ║ ^ -//│ ║ l.46: ("A") +//│ ║ l.47: ("A") //│ ╙── //│ > A @@ -66,15 +67,15 @@ print of "A" ) //│ ╔══[PARSE ERROR] Mistmatched closing indentation -//│ ║ l.66: "A" +//│ ║ l.67: "A" //│ ║ ^ -//│ ║ l.67: ) +//│ ║ l.68: ) //│ ║ //│ ╟── does not correspond to opening parenthesis -//│ ║ l.65: ( +//│ ║ l.66: ( //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected closing parenthesis -//│ ║ l.67: ) +//│ ║ l.68: ) //│ ╙── ^ //│ > A diff --git a/hkmc2/shared/src/test/mlscript/codegen/.mls b/hkmc2/shared/src/test/mlscript/codegen/.mls new file mode 100644 index 0000000000..cc02430a79 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/codegen/.mls @@ -0,0 +1,3 @@ +:.. +:!js +#lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadConfigDirective.mls b/hkmc2/shared/src/test/mlscript/codegen/BadConfigDirective.mls index 1cff34535c..994775b521 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadConfigDirective.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadConfigDirective.mls @@ -1,3 +1,4 @@ +:. :js @@ -5,7 +6,7 @@ @config(tailRecOpt: false) "hi" //│ ╔══[WARNING] This annotation has no effect. //│ ╟── This annotation is not supported on string literal terms. -//│ ║ l.5: @config(tailRecOpt: false) "hi" +//│ ║ l.6: @config(tailRecOpt: false) "hi" //│ ╙── ^^^^ //│ = "hi" @@ -18,7 +19,7 @@ :e #lang(1.0.x) //│ ╔══[COMPILATION ERROR] Unknown language version '1.0.x' -//│ ║ l.19: #lang(1.0.x) +//│ ║ l.20: #lang(1.0.x) //│ ║ ^^^^^ //│ ╙── Available language versions: 0.2.x, 0.3.x @@ -26,12 +27,12 @@ :e #lang(unknownLanguageField: true) //│ ╔══[COMPILATION ERROR] Unsupported Language argument -//│ ║ l.27: #lang(unknownLanguageField: true) +//│ ║ l.28: #lang(unknownLanguageField: true) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ :e #lang(versionName: "custom") //│ ╔══[COMPILATION ERROR] Unsupported Language argument -//│ ║ l.34: #lang(versionName: "custom") +//│ ║ l.35: #lang(versionName: "custom") //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadFunctions.mls b/hkmc2/shared/src/test/mlscript/codegen/BadFunctions.mls index 8925cdf882..5c37acd9ab 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadFunctions.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadFunctions.mls @@ -1,13 +1,14 @@ +:. :js :fixme fun (let) oops = 1 //│ ╔══[PARSE ERROR] Expected expression after 'let' binding keyword; found end of input instead -//│ ║ l.5: fun (let) oops = 1 +//│ ║ l.6: fun (let) oops = 1 //│ ╙── ^ //│ ╔══[COMPILATION ERROR] This ‹erroneous syntax› is not a valid symbolic name -//│ ║ l.5: fun (let) oops = 1 +//│ ║ l.6: fun (let) oops = 1 //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadImport.mls b/hkmc2/shared/src/test/mlscript/codegen/BadImport.mls index 038491fbe8..a2dfa008b6 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadImport.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadImport.mls @@ -1,10 +1,11 @@ +:. :js :e import 123 //│ ╔══[COMPILATION ERROR] Expected string literal after 'import' keyword -//│ ║ l.5: import 123 +//│ ║ l.6: import 123 //│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls b/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls index ce196a50da..69dfa9e6a8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadInit.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls b/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls index f526b1e2ee..a7cca186f9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls @@ -1,17 +1,18 @@ +:. :js :e ..., 1 //│ ╔══[COMPILATION ERROR] Illegal position for '...' spread operator. -//│ ║ l.5: ..., 1 +//│ ║ l.6: ..., 1 //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e fun f = ..., 1 //│ ╔══[COMPILATION ERROR] Illegal position for '...' spread operator. -//│ ║ l.12: fun f = ..., 1 +//│ ║ l.13: fun f = ..., 1 //│ ╙── ^^^ //│ = 1 @@ -24,13 +25,13 @@ fun f = ..., 1 :e fun foo(a, ...b, c) = [a, ...b, c] //│ ╔══[COMPILATION ERROR] Spread parameters must be the last in the parameter list. -//│ ║ l.25: fun foo(a, ...b, c) = [a, ...b, c] +//│ ║ l.26: fun foo(a, ...b, c) = [a, ...b, c] //│ ╙── ^^^^ :e fun foo(a, ...b, c) = [a, ...b] //│ ╔══[COMPILATION ERROR] Spread parameters must be the last in the parameter list. -//│ ║ l.31: fun foo(a, ...b, c) = [a, ...b] +//│ ║ l.32: fun foo(a, ...b, c) = [a, ...b] //│ ╙── ^^^^ :re @@ -43,22 +44,25 @@ fun foo(a, ...b) = [a, ...b] :e :re foo() -//│ ╔══[COMPILATION ERROR] Expected at least 1 arguments, got 0 -//│ ║ l.45: foo() -//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.46: foo() +//│ ║ ^^^^^ +//│ ╟── Function 'foo' expected 1 argument, but got 0 +//│ ║ l.42: fun foo(a, ...b) = [a, ...b] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'foo' expected at least 1 argument but got 0 :e fun foo(1) = 2 //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found integer literal -//│ ║ l.53: fun foo(1) = 2 +//│ ║ l.57: fun foo(1) = 2 //│ ╙── ^ :e fun foo(...1) = 2 //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found spread -//│ ║ l.59: fun foo(...1) = 2 +//│ ║ l.63: fun foo(...1) = 2 //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadValInit.mls b/hkmc2/shared/src/test/mlscript/codegen/BadValInit.mls index db4c9bd2e4..ce8e7061c0 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadValInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadValInit.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls b/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls index fe71d8b35d..0a57957cae 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls @@ -1,3 +1,4 @@ +:. :js @@ -5,7 +6,7 @@ :sjs while {} do() //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'do' here -//│ ║ l.6: while {} do() +//│ ║ l.7: while {} do() //│ ╙── ^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ throw "This code cannot be run as its compilation yielded an error." @@ -20,6 +21,4 @@ fun f() = while i > 0 do set i -= 1 i -//│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.22: i -//│ ╙── ^ +//│ ═══[WARNING] Pure expression in statement position diff --git a/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls b/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls index bc83b775bc..0c22fe05a5 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls @@ -1,3 +1,4 @@ +:. :js // :sjs // :elt @@ -54,21 +55,21 @@ let x |>= incr :e let 2 //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.55: let 2 +//│ ║ l.56: let 2 //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e let x += 1 = 0 //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.62: let x += 1 = 0 +//│ ║ l.63: let x += 1 = 0 //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e let x += 1 = 0 in x //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.69: let x += 1 = 0 in x +//│ ║ l.70: let x += 1 = 0 in x //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -76,20 +77,20 @@ let x += 1 = 0 in x :e (let x = 1) //│ ╔══[COMPILATION ERROR] Expected a body for let bindings in expression position -//│ ║ l.77: (let x = 1) +//│ ║ l.78: (let x = 1) //│ ╙── ^^^^^ :e (let x = 1) + 1 //│ ╔══[COMPILATION ERROR] Expected a body for let bindings in expression position -//│ ║ l.83: (let x = 1) + 1 +//│ ║ l.84: (let x = 1) + 1 //│ ╙── ^^^^^ //│ = "()1" :e (let f(x) = x) + 1 //│ ╔══[COMPILATION ERROR] Expected a body for let bindings in expression position -//│ ║ l.90: (let f(x) = x) + 1 +//│ ║ l.91: (let f(x) = x) + 1 //│ ╙── ^^^^^^^^ //│ = "()1" diff --git a/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls b/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls index 09c77e58ad..b4fbd37596 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls @@ -1,3 +1,4 @@ +:. :js @@ -29,7 +30,7 @@ :w +(2, 3) //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.30: +(2, 3) +//│ ║ l.31: +(2, 3) //│ ╙── ^ //│ = 3 @@ -39,7 +40,7 @@ :ge *(1) //│ ╔══[COMPILATION ERROR] Builtin '*' is not a unary operator -//│ ║ l.40: *(1) +//│ ║ l.41: *(1) //│ ╙── ^^^^ //│ ═══[COMPILATION ERROR] Cannot call non-unary builtin symbol '*' //│ ═══[RUNTIME ERROR] Error: Cannot call non-unary builtin symbol '*' @@ -56,7 +57,7 @@ :ge (*)(2) //│ ╔══[COMPILATION ERROR] Builtin '*' is not a unary operator -//│ ║ l.57: (*)(2) +//│ ║ l.58: (*)(2) //│ ╙── ^^^^^^ //│ ═══[COMPILATION ERROR] Cannot call non-unary builtin symbol '*' //│ ═══[RUNTIME ERROR] Error: Cannot call non-unary builtin symbol '*' diff --git a/hkmc2/shared/src/test/mlscript/codegen/BuiltinSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/BuiltinSymbols.mls index c8ffd860ff..dcf5543d3d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BuiltinSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BuiltinSymbols.mls @@ -1,3 +1,4 @@ +:. :js @@ -5,23 +6,17 @@ :ge Int -//│ ╔══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term -//│ ║ l.7: Int -//│ ╙── ^^^ +//│ ═══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge Str -//│ ╔══[COMPILATION ERROR] Symbol 'Str' is virtual (i.e., "compiler fiction"); cannot be used as a term -//│ ║ l.14: Str -//│ ╙── ^^^ +//│ ═══[COMPILATION ERROR] Symbol 'Str' is virtual (i.e., "compiler fiction"); cannot be used as a term //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge Bool -//│ ╔══[COMPILATION ERROR] Symbol 'Bool' is virtual (i.e., "compiler fiction"); cannot be used as a term -//│ ║ l.21: Bool -//│ ╙── ^^^^ +//│ ═══[COMPILATION ERROR] Symbol 'Bool' is virtual (i.e., "compiler fiction"); cannot be used as a term //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :soir diff --git a/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls b/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls index 78e68efd28..05f45d1492 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls index d7ce43f175..fefa8bca1b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls @@ -1,3 +1,4 @@ +:. :js @@ -77,10 +78,10 @@ case :todo // TODO: support this braceless syntax? case [x] then x, [] then 0 //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.78: case [x] then x, [] then 0 +//│ ║ l.79: case [x] then x, [] then 0 //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.78: case [x] then x, [] then 0 +//│ ║ l.79: case [x] then x, [] then 0 //│ ╙── ^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -123,6 +124,6 @@ val isDefined = case :todo case 0 | 1 //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (infix operator '|'). -//│ ║ l.124: case 0 | 1 +//│ ║ l.125: case 0 | 1 //│ ╙── ^^^^^ //│ = fun diff --git a/hkmc2/shared/src/test/mlscript/codegen/CheckedCasts.mls b/hkmc2/shared/src/test/mlscript/codegen/CheckedCasts.mls index 3e58a2f6a5..c2fac25dc9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CheckedCasts.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CheckedCasts.mls @@ -1,3 +1,4 @@ +:. // `:noCheckCasts` is the only input to a `Cast`'s `check` flag, via `Config.checkCasts`. Nothing // expands a check into a runtime type test since `CheckedCastExpansion` was removed, so the two // blocks below generate the same code and differ only in how the IR prints the cast: `as!` when diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls index f854d4c4cc..368c490709 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls @@ -1,3 +1,4 @@ +:. :js @@ -55,7 +56,7 @@ data class Outer(a, b) with //│ tmp = this.Inner(this.a); //│ this.i = tmp; //│ Predef.print(this.i.c); -//│ tmp1 = runtime.safeCall(this.i.i1(1)); +//│ tmp1 = this.i.i1(1); //│ Predef.print(tmp1); //│ } //│ o1(c) { diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index a63ca02fe7..e429205764 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -1,3 +1,4 @@ +:. :js @@ -69,9 +70,16 @@ test(123) :e :re test() -//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 0 -//│ ║ l.71: test() -//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.72: test() +//│ ║ ^^^^^^ +//│ ╟── Function 'test' expected 1 argument, but got 0 +//│ ║ l.38: fun test(x) = +//│ ║ ^^^^^^^^^^^^^ +//│ ║ l.39: data class Foo(a, b) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.40: Foo(x, x + 1) +//│ ╙── ^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'test' expected 1 argument but got 0 diff --git a/hkmc2/shared/src/test/mlscript/codegen/Classes.mls b/hkmc2/shared/src/test/mlscript/codegen/Classes.mls index f249319e22..60e965dc9a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Classes.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Classes.mls @@ -1,3 +1,4 @@ +:. :js :global diff --git a/hkmc2/shared/src/test/mlscript/codegen/CodegenScratch.mls b/hkmc2/shared/src/test/mlscript/codegen/CodegenScratch.mls index ef0cd50ba4..fb9812439e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CodegenScratch.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CodegenScratch.mls @@ -1,3 +1,4 @@ +:. :js :global diff --git a/hkmc2/shared/src/test/mlscript/codegen/Comma.mls b/hkmc2/shared/src/test/mlscript/codegen/Comma.mls index faf901b723..8001f4a9b8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Comma.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Comma.mls @@ -1,3 +1,4 @@ +:. :js :global diff --git a/hkmc2/shared/src/test/mlscript/codegen/CurriedFunctions.mls b/hkmc2/shared/src/test/mlscript/codegen/CurriedFunctions.mls index fe52043950..3dedf45c6a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CurriedFunctions.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CurriedFunctions.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls index edc1ed3175..0cef4522d0 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls @@ -1,3 +1,4 @@ +:. :js :global @@ -13,7 +14,7 @@ let x print(x) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res2 = runtime.checkCall(Predef.print(x)); +//│ block$res3 = runtime.checkCall(Predef.print(x)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > undefined @@ -24,7 +25,7 @@ x = 1 x //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res4 = x; +//│ block$res5 = x; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 @@ -36,7 +37,7 @@ x = 2 x //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res6 = x; +//│ block$res7 = x; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 2 @@ -51,7 +52,7 @@ let y = 1 :e z = 1 //│ ╔══[COMPILATION ERROR] Name not found: z -//│ ║ l.52: z = 1 +//│ ║ l.53: z = 1 //│ ╙── ^ //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ throw "This code cannot be run as its compilation yielded an error." @@ -70,7 +71,7 @@ fun f() = 1 f //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res10 = f; +//│ block$res11 = f; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun f @@ -89,7 +90,7 @@ f(x) = x + 1 f(1) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res12 = runtime.safeCall(f1(1)); +//│ block$res13 = runtime.safeCall(f1(1)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 2 @@ -108,12 +109,12 @@ if true then foo = 0 else foo = 1 //│ ╔══[PARSE ERROR] Expected end of input; found '=' keyword instead -//│ ║ l.108: then foo = 0 +//│ ║ l.109: then foo = 0 //│ ╙── ^ //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo1; //│ if (true === true) { -//│ block$res14 = foo1; +//│ block$res15 = foo1; //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ foo = undefined @@ -128,8 +129,8 @@ else //│ let foo2; //│ if (true === true) { //│ foo2 = 0; -//│ block$res15 = runtime.Unit; -//│ } else { foo2 = 1; block$res15 = runtime.Unit; } +//│ block$res16 = runtime.Unit; +//│ } else { foo2 = 1; block$res16 = runtime.Unit; } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ foo = 0 @@ -147,12 +148,12 @@ fun f() = f() //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ foo2 = 42; block$res17 = runtime.Unit; +//│ foo2 = 42; block$res18 = runtime.Unit; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— foo //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res18 = foo2; +//│ block$res19 = foo2; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 42 @@ -160,7 +161,7 @@ foo :fixme fun f() = foo = 0 //│ ╔══[PARSE ERROR] Expected end of input; found '=' keyword instead -//│ ║ l.161: fun f() = foo = 0 +//│ ║ l.162: fun f() = foo = 0 //│ ╙── ^ //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let f4; diff --git a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls index d61a5367f2..6e65525d02 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/ElseLess.mls b/hkmc2/shared/src/test/mlscript/codegen/ElseLess.mls index 47e6789d3a..38e29ed2cc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ElseLess.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ElseLess.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls index db70877fd3..d85258ddbf 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls @@ -1,3 +1,4 @@ +:. :js @@ -10,13 +11,13 @@ data class Foo(val x) with Foo(42).x //│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Sel: +//│ res = NewSel: //│ prefix = App: -//│ lhs = Ref{sym=member:Foo⁰} of member:Foo⁰ +//│ lhs = MemberRef of member:Foo⁰ //│ rhs = Tup of Ls of //│ Fld: //│ term = Lit of IntLit of 42 -//│ nme = Ident of "x" +//│ id = Ident of "x" //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 42 @@ -29,9 +30,9 @@ let foo = Foo(42) foo.x //│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Sel: -//│ prefix = Ref{sym=foo⁰} of foo⁰ -//│ nme = Ident of "x" +//│ res = NewSel: +//│ prefix = SimpleRef of foo⁰ +//│ id = Ident of "x" //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 42 @@ -41,9 +42,9 @@ foo.x foo.y //│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Sel: -//│ prefix = Ref{sym=foo⁰} of foo⁰ -//│ nme = Ident of "y" +//│ res = NewSel: +//│ prefix = SimpleRef of foo⁰ +//│ id = Ident of "y" //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 42 @@ -68,12 +69,12 @@ case //│ branch = Match: //│ scrutinee = Ref{sym=caseScrut⁰} of caseScrut⁰ //│ pattern = Constructor: -//│ target = Ref{sym=member:Foo⁰} of member:Foo⁰ +//│ target = MemberRef of member:Foo⁰ //│ arguments = S of Ls of //│ Alias: //│ pattern = Wildcard //│ id = Ident of "a" -//│ consequent = Else of Ref{sym=a⁰} of a⁰ +//│ consequent = Else of SimpleRef of a⁰ //│ tail = End //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let lambda; diff --git a/hkmc2/shared/src/test/mlscript/codegen/Formatting.mls b/hkmc2/shared/src/test/mlscript/codegen/Formatting.mls index 673fc1c5cf..e607eb602a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Formatting.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Formatting.mls @@ -1,3 +1,4 @@ +:. :js :sjs diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index b4eeaaa5f4..50f78aae01 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls index 9361b87dc7..1cdbde8230 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls @@ -1,3 +1,4 @@ +:. :js @@ -87,7 +88,7 @@ outerfun(100)(200) :showRepl fun test1(x) = test2(x) fun test2(y) = y + 1 -//│ REPL> Sending: block$res18 = undefined +//│ REPL> Sending: block$res19 = undefined //│ REPL> Collected: //│ > undefined //│ REPL> Sending: let test2, test1;try { test1 = function test1(x2) { runtime.checkArgs("test1", 1, true, arguments.length); return x2 + 1 }; test2 = function test2(y2) { runtime.checkArgs("test2", 1, true, arguments.length); return y2 + 1 };; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } @@ -95,7 +96,7 @@ fun test2(y) = y + 1 //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res18));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res19));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ > undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunctionsThis.mls b/hkmc2/shared/src/test/mlscript/codegen/FunctionsThis.mls index 55876338f3..25314b140d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunctionsThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunctionsThis.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls b/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls index efef853f1d..b76e96117a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls @@ -1,3 +1,4 @@ +:. :js @@ -8,9 +9,6 @@ module Mod with :ctx open Mod //│ Env: -//│ pipee -> SelElem(RefElem(member:Mod),pipee,Some(member:pipee),true) -//│ printt -> SelElem(RefElem(member:Mod),printt,Some(member:printt),true) -//│ |>> -> SelElem(RefElem(member:Mod),pipee,Some(member:pipee),true) pipee //│ = fun pipee @@ -25,14 +23,11 @@ pipee :e Mod.|>> //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.26: Mod.|>> +//│ ║ l.24: Mod.|>> //│ ╙── ^ -//│ ╔══[COMPILATION ERROR] Name not found: .|>> -//│ ║ l.26: Mod.|>> +//│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: .|>> +//│ ║ l.24: Mod.|>> //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Unexpected moduleful reference of type Mod. -//│ ║ l.26: Mod.|>> -//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -44,17 +39,17 @@ Mod.|>> :e open Mod { |>> } //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.45: open Mod { |>> } +//│ ║ l.40: open Mod { |>> } //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Illegal 'open' statement shape. -//│ ║ l.45: open Mod { |>> } +//│ ║ l.40: open Mod { |>> } //│ ╙── ^^^^^^^^^^^ :e open Mod { (|>>) } //│ ╔══[COMPILATION ERROR] Illegal 'open' statement element. -//│ ║ l.55: open Mod { (|>>) } +//│ ║ l.50: open Mod { (|>>) } //│ ╙── ^^^^^ 12 |>> print diff --git a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls index 44e7977a36..263baa47c3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls index bb83055261..4e08e7c305 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls @@ -1,3 +1,4 @@ +:. :js @@ -48,7 +49,11 @@ let f = x => print((if x and x then "ok" else "ko") + "!") //│ lambda1 = (undefined, function (x) { //│ let tmp, tmp1; //│ if (x === true) { -//│ tmp = "ok"; +//│ if (x === true) { +//│ tmp = "ok"; +//│ } else { +//│ tmp = "ko"; +//│ } //│ } else { tmp = "ko"; } //│ tmp1 = tmp + "!"; //│ return Predef.print(tmp1) diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImperativeConditionals.mls b/hkmc2/shared/src/test/mlscript/codegen/ImperativeConditionals.mls index 8d47a28991..017df4b71d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImperativeConditionals.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImperativeConditionals.mls @@ -1,3 +1,4 @@ +:. :js :ignore @@ -12,10 +13,10 @@ fun f(x) = if x < 0 return 0 Math.sqrt(x) //│ ╔══[PARSE ERROR] Unexpected 'return' keyword here -//│ ║ l.12: if x < 0 return 0 +//│ ║ l.13: if x < 0 return 0 //│ ╙── ^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized term split (integer literal) -//│ ║ l.12: if x < 0 return 0 +//│ ║ l.13: if x < 0 return 0 //│ ╙── ^ @@ -25,19 +26,19 @@ fun hasZeroElement(xs) = Cons(hd, tl) do set xs = tl Nil return false //│ ╔══[PARSE ERROR] Unexpected 'return' keyword here -//│ ║ l.24: Cons(0, tl) return true +//│ ║ l.25: Cons(0, tl) return true //│ ╙── ^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (integer literal). -//│ ║ l.24: Cons(0, tl) return true +//│ ║ l.25: Cons(0, tl) return true //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (identifier). -//│ ║ l.24: Cons(0, tl) return true +//│ ║ l.25: Cons(0, tl) return true //│ ╙── ^^ //│ ╔══[WARNING] This catch-all clause makes the following branches unreachable. -//│ ║ l.24: Cons(0, tl) return true +//│ ║ l.25: Cons(0, tl) return true //│ ║ ^ //│ ╟── This branch is unreachable. -//│ ║ l.24: Cons(0, tl) return true +//│ ║ l.25: Cons(0, tl) return true //│ ╙── ^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportConflicts.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportConflicts.mls index 6ed479eddf..e3cc903583 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportConflicts.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportConflicts.mls @@ -1,3 +1,4 @@ +:. :js @@ -25,7 +26,7 @@ fun Some3 = "333" open Option3 { Some3 } Some3 //│ ╔══[WARNING] Imported name 'Some3' is shadowed by a name already defined in the same scope -//│ ║ l.25: open Option3 { Some3 } +//│ ║ l.26: open Option3 { Some3 } //│ ╙── ^^^^^ //│ = "333" @@ -47,6 +48,6 @@ fun bar = open Option5 Some5 bar -//│ = 5 +//│ = "555" diff --git a/hkmc2/shared/src/test/mlscript/codegen/InlineLambdas.mls b/hkmc2/shared/src/test/mlscript/codegen/InlineLambdas.mls index e0f8e06c10..05beb46b73 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/InlineLambdas.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/InlineLambdas.mls @@ -1,3 +1,4 @@ +:. :js :noInline diff --git a/hkmc2/shared/src/test/mlscript/codegen/InlineMultiArgLists.mls b/hkmc2/shared/src/test/mlscript/codegen/InlineMultiArgLists.mls index e2f095a247..649f5b4ffd 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/InlineMultiArgLists.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/InlineMultiArgLists.mls @@ -1,3 +1,4 @@ +:. :js // * TODO: take `private` into accoutn and inline these single-use functions regardless of threshold diff --git a/hkmc2/shared/src/test/mlscript/codegen/InnerNameHygiene.mls b/hkmc2/shared/src/test/mlscript/codegen/InnerNameHygiene.mls index 2678d1eba3..c383885ec4 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/InnerNameHygiene.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/InnerNameHygiene.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/Juxtapositions.mls b/hkmc2/shared/src/test/mlscript/codegen/Juxtapositions.mls index 1e2dd77a1e..a8ec00817a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Juxtapositions.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Juxtapositions.mls @@ -1,3 +1,4 @@ +:. :js @@ -39,11 +40,14 @@ fun add(x, y) = x + y :w 42 add(2) -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 1 -//│ ║ l.41: add(2) -//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.42: add(2) +//│ ║ ^^^^^^ +//│ ╟── Function 'add' expected 2 arguments, but got 1 +//│ ║ l.30: fun add(x, y) = x + y +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.40: 42 +//│ ║ l.41: 42 //│ ╙── ^^ //│ ═══[RUNTIME ERROR] Error: Function 'add' expected 2 arguments but got 1 @@ -68,7 +72,7 @@ add(2) :e // Note: this used to parse as a juxtaposition (the curly braces were treated equally to an indented section) 42 { inc() } //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.69: 42 { inc() } +//│ ║ l.73: 42 { inc() } //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -76,42 +80,42 @@ add(2) 42 { inc() } inc() //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.76: 42 { inc() } +//│ ║ l.80: 42 { inc() } //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e // Note: idem 42 { add(2) } inc() //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.84: 42 { add(2) } inc() +//│ ║ l.88: 42 { add(2) } inc() //│ ╙── ^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e // Note: idem 42 { add(2) } { inc() } //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.91: 42 { add(2) } { inc() } +//│ ║ l.95: 42 { add(2) } { inc() } //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e // Note: idem 42 { add(2), inc() } //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.98: 42 { add(2), inc() } -//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.102: 42 { add(2), inc() } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e 42 { add(2) inc() } //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.105: 42 { add(2) inc() } +//│ ║ l.109: 42 { add(2) inc() } //│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e 42 { add(2) { inc() } } //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.112: 42 { add(2) { inc() } } +//│ ║ l.116: 42 { add(2) { inc() } } //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -147,9 +151,9 @@ inc(2) * 2 inc() * 1 add(2) //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (operator application). -//│ ║ l.147: inc() * 1 +//│ ║ l.151: inc() * 1 //│ ║ ^^^^^^^^^ -//│ ║ l.148: add(2) +//│ ║ l.152: add(2) //│ ╙── ^^^^ //│ = 42 @@ -158,9 +162,9 @@ inc(2) * 2 inc() add(2) //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (juxtaposition). -//│ ║ l.158: inc() +//│ ║ l.162: inc() //│ ║ ^^^^^ -//│ ║ l.159: add(2) +//│ ║ l.163: add(2) //│ ╙── ^^^^ //│ = 42 @@ -170,7 +174,7 @@ inc(2) * 2 42 inc() + 1 //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (operator application). -//│ ║ l.171: inc() + 1 +//│ ║ l.175: inc() + 1 //│ ╙── ^^^^^^^^^ //│ = 42 @@ -179,7 +183,7 @@ inc(2) * 2 add(2) + 1 inc() //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (operator application). -//│ ║ l.179: add(2) + 1 +//│ ║ l.183: add(2) + 1 //│ ╙── ^^^^^^^^^^ //│ = 43 @@ -188,7 +192,7 @@ inc(2) * 2 add(2) inc() + 1 //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (operator application). -//│ ║ l.189: inc() + 1 +//│ ║ l.193: inc() + 1 //│ ╙── ^^^^^^^^^ //│ = 44 @@ -222,9 +226,12 @@ inc(2) * 2 (2 * 2 inc()) inc() -//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 0 -//│ ║ l.224: inc() -//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.228: inc() +//│ ║ ^^^^^ +//│ ╟── Function 'inc' expected 1 argument, but got 0 +//│ ║ l.15: fun inc(x) = x + 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'inc' expected 1 argument but got 0 :e @@ -233,9 +240,12 @@ inc() inc() ) inc() -//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 0 -//│ ║ l.235: inc() -//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.242: inc() +//│ ║ ^^^^^ +//│ ╟── Function 'inc' expected 1 argument, but got 0 +//│ ║ l.15: fun inc(x) = x + 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'inc' expected 1 argument but got 0 @@ -251,7 +261,7 @@ inc() :e 42 console.log //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (selection). -//│ ║ l.252: 42 console.log +//│ ║ l.262: 42 console.log //│ ╙── ^^^^^^^^^^^ //│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls index 2f8c5fcfab..4d04191368 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls @@ -1,3 +1,4 @@ +:. :js @@ -32,7 +33,7 @@ f(true, true) :e x => if x is 0 | 1 then 1 //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) -//│ ║ l.33: x => if x is 0 | 1 then 1 +//│ ║ l.34: x => if x is 0 | 1 then 1 //│ ╙── ^^^^^^^^^^ //│ = fun @@ -66,7 +67,7 @@ if (foo of x => x + 1) then 1 :e if foo of x => x + 1 then 1 //│ ╔══[COMPILATION ERROR] Unrecognized term split (application) -//│ ║ l.67: if foo of x => x + 1 then 1 +//│ ║ l.68: if foo of x => x + 1 then 1 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/codegen/Lazy.mls b/hkmc2/shared/src/test/mlscript/codegen/Lazy.mls index 265ac12db1..e39fd1f963 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Lazy.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Lazy.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/LetPatterns.mls b/hkmc2/shared/src/test/mlscript/codegen/LetPatterns.mls index 548dc14ac8..0b65b2720e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/LetPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/LetPatterns.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,7 +7,7 @@ data class Some[out A](value: A) :todo let Some(x) = Some(42) //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.7: let Some(x) = Some(42) +//│ ║ l.8: let Some(x) = Some(42) //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -19,7 +20,7 @@ let N = None :todo let (None) = N //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.20: let (None) = N +//│ ║ l.21: let (None) = N //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -29,17 +30,17 @@ data class Foo(a) :todo let Foo(x) = Foo(1) in print(x) //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.30: let Foo(x) = Foo(1) in print(x) +//│ ║ l.31: let Foo(x) = Foo(1) in print(x) //│ ╙── ^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.30: let Foo(x) = Foo(1) in print(x) +//│ ║ l.31: let Foo(x) = Foo(1) in print(x) //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :todo let Foo(x) and x > 0 = Foo(1) in print("ok") //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.40: let Foo(x) and x > 0 = Foo(1) in print("ok") +//│ ║ l.41: let Foo(x) and x > 0 = Foo(1) in print("ok") //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls b/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls index 0fc8dfc3b5..e9c9d46985 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls @@ -1,3 +1,4 @@ +:. :js #config(commentGeneratedCode: true) @@ -22,13 +23,15 @@ if a is C then 3 D then 4 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (a instanceof A1.class) { +//│ let scrut; +//│ scrut = a; +//│ if (scrut instanceof A1.class) { //│ return 1 -//│ } else if (a instanceof B1.class) { +//│ } else if (scrut instanceof B1.class) { //│ return 2 -//│ } else if (a instanceof C1.class) { +//│ } else if (scrut instanceof C1.class) { //│ return 3 -//│ } else if (a instanceof D1.class) { +//│ } else if (scrut instanceof D1.class) { //│ return 4 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -45,11 +48,15 @@ if a is B then 2 C then 3 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (a instanceof A1.class) { +//│ let scrut1, scrut2; +//│ scrut1 = a; +//│ if (scrut1 instanceof A1.class) { //│ return 1 -//│ } else if (a instanceof B1.class) { +//│ } +//│ scrut2 = a; +//│ if (scrut2 instanceof B1.class) { //│ return 2 -//│ } else if (a instanceof C1.class) { +//│ } else if (scrut2 instanceof C1.class) { //│ return 3 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -65,16 +72,19 @@ if a is B then 2 C then 3 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (a instanceof A1.class) { -//│ if (b instanceof A1.class) { +//│ let scrut3, scrut4; +//│ scrut3 = a; +//│ if (scrut3 instanceof A1.class) { +//│ scrut4 = b; +//│ if (scrut4 instanceof A1.class) { //│ return 1 -//│ } else if (b instanceof B1.class) { +//│ } else if (scrut4 instanceof B1.class) { //│ return 2 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); -//│ } else if (a instanceof B1.class) { +//│ } else if (scrut3 instanceof B1.class) { //│ return 2 -//│ } else if (a instanceof C1.class) { +//│ } else if (scrut3 instanceof C1.class) { //│ return 3 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -100,22 +110,30 @@ let x = if a is print("done") print(x) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let x, tmp; -//│ if (a instanceof A1.class) { +//│ let x, scrut5, scrut6, scrut7, tmp; +//│ scrut5 = a; +//│ if (scrut5 instanceof A1.class) { //│ tmp = 1; -//│ } else if (a instanceof B1.class) { +//│ } else if (scrut5 instanceof B1.class) { //│ tmp = 2; -//│ } else if (a instanceof C1.class) { +//│ } else if (scrut5 instanceof C1.class) { //│ tmp = 3; -//│ } else if (a instanceof D1.class) { -//│ if (a instanceof A1.class) {} else if (a instanceof B1.class) {} else { +//│ } else { +//│ scrut6 = a; +//│ if (scrut6 instanceof D1.class) { +//│ scrut7 = a; +//│ if (scrut7 instanceof A1.class) { +//│ /* Rest moved to non-abortive branch(es) */ +//│ } else if (scrut7 instanceof B1.class) { +//│ /* Rest moved to non-abortive branch(es) */ +//│ } else { +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ } +//│ /* Rest moved to non-abortive branch(es) */ +//│ } else if (scrut6 instanceof E1.class) {} else { //│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } //│ tmp = Predef.print("done"); -//│ } else if (a instanceof E1.class) { -//│ tmp = Predef.print("done"); -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } //│ x = tmp; //│ return Predef.print(tmp) @@ -132,9 +150,11 @@ if a is let tmp = 2 B then 2 + tmp //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (a instanceof A1.class) { +//│ let scrut8; +//│ scrut8 = a; +//│ if (scrut8 instanceof A1.class) { //│ return 1 -//│ } else if (a instanceof B1.class) { +//│ } else if (scrut8 instanceof B1.class) { //│ return 4 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -154,11 +174,13 @@ if a is let tmp = printAndId(3) B then 2 + tmp //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (a instanceof A1.class) { +//│ let scrut9; +//│ scrut9 = a; +//│ if (scrut9 instanceof A1.class) { //│ return 1 //│ } //│ Predef.print(3); -//│ if (a instanceof B1.class) { +//│ if (scrut9 instanceof B1.class) { //│ return 5 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -177,14 +199,19 @@ if a is C then 3 print(x) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (a instanceof A1.class) { +//│ let scrut10, scrut11; +//│ scrut10 = a; +//│ if (scrut10 instanceof A1.class) { //│ return 1 -//│ } else if (a instanceof B1.class) { +//│ } +//│ scrut11 = a; +//│ if (scrut11 instanceof B1.class) { //│ return Predef.print(2) -//│ } else if (a instanceof C1.class) { +//│ } else if (scrut11 instanceof C1.class) { //│ return Predef.print(3) //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ /* Rest moved to non-abortive branch(es) */ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 @@ -203,13 +230,15 @@ if a is print(x + 1) print(x + 2) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let tmp1, tmp2, tmp3; -//│ if (a instanceof B1.class) { +//│ let scrut12, scrut13, tmp1, tmp2, tmp3; +//│ scrut12 = a; +//│ if (scrut12 instanceof B1.class) { //│ return 1 //│ } -//│ if (a instanceof A1.class) { +//│ scrut13 = a; +//│ if (scrut13 instanceof A1.class) { //│ tmp1 = 2; -//│ } else if (a instanceof C1.class) { +//│ } else if (scrut13 instanceof C1.class) { //│ tmp1 = 3; //│ } else { //│ throw globalThis.Object.freeze(new globalThis.Error("match error")) @@ -241,12 +270,14 @@ fun f(x) = //│ match x //│ 5 => //│ return "" -//│ 8 => -//│ do Predef⁰.print⁰("8"); -//│ do Predef⁰.print⁰("hi"); -//│ return "" //│ else -//│ do Predef⁰.print⁰("?"); +//│ match x +//│ 8 => +//│ do Predef⁰.print⁰("8"); +//│ end +//│ else +//│ do Predef⁰.print⁰("?"); +//│ end //│ do Predef⁰.print⁰("hi"); //│ return "" //│ end diff --git a/hkmc2/shared/src/test/mlscript/codegen/ModuleMatching.mls b/hkmc2/shared/src/test/mlscript/codegen/ModuleMatching.mls index 8178706682..7b62c8047b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ModuleMatching.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ModuleMatching.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/NestedLoops.mls b/hkmc2/shared/src/test/mlscript/codegen/NestedLoops.mls index e791ec99c5..31f7e7a819 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/NestedLoops.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/NestedLoops.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/NestedTypes.mls b/hkmc2/shared/src/test/mlscript/codegen/NestedTypes.mls index 38a65ee381..3b35b7ecb8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/NestedTypes.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/NestedTypes.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/ObjectInit.mls b/hkmc2/shared/src/test/mlscript/codegen/ObjectInit.mls index 0130205358..e615b1fc73 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ObjectInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ObjectInit.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/OpenJS.mls b/hkmc2/shared/src/test/mlscript/codegen/OpenJS.mls index 5678f6aaec..a5404fafcd 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/OpenJS.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/OpenJS.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls index 46318322d6..2026cd2124 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/Primes.mls b/hkmc2/shared/src/test/mlscript/codegen/Primes.mls index a0b2967457..3cbbdb1e66 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Primes.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Primes.mls @@ -1,3 +1,4 @@ +:. :js @@ -19,17 +20,20 @@ x' object Foo with val x' = 0 //│ ╔══[COMPILATION ERROR] Illegal value member name: 'x'' -//│ ║ l.20: val x' = 0 +//│ ║ l.21: val x' = 0 //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. :e :re Foo.x' -//│ ╔══[COMPILATION ERROR] Object 'Foo' does not contain member 'x'' -//│ ║ l.28: Foo.x' -//│ ╙── ^^^^^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x'' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.29: Foo.x' +//│ ║ ^^^^^^ +//│ ╟── Object 'Foo' does not contain member 'x'' +//│ ║ l.20: object Foo with +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. Foo.("x'") @@ -37,20 +41,29 @@ Foo.("x'") object Foo with mut val x' = 0 //│ ╔══[COMPILATION ERROR] Illegal mutable value member name: 'x'' -//│ ║ l.38: mut val x' = 0 +//│ ║ l.42: mut val x' = 0 //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. :e :re set Foo.x' += 1 -//│ ╔══[COMPILATION ERROR] Object 'Foo' does not contain member 'x'' -//│ ║ l.46: set Foo.x' += 1 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.50: set Foo.x' += 1 +//│ ║ ^^^^^^ +//│ ╟── Object 'Foo' does not contain member 'x'' +//│ ║ l.41: object Foo with +//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.50: set Foo.x' += 1 +//│ ║ ^^^^^^ +//│ ╟── Object 'Foo' does not contain member 'x'' +//│ ║ l.41: object Foo with +//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Assignment requires an unambiguous term member +//│ ║ l.50: set Foo.x' += 1 //│ ╙── ^^^^^^ -//│ ╔══[COMPILATION ERROR] Object 'Foo' does not contain member 'x'' -//│ ║ l.46: set Foo.x' += 1 -//│ ╙── ^^^^^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x'' yielded 'undefined' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. Foo.("x'") @@ -59,7 +72,7 @@ Foo.("x'") class Foo with fun f' = 1 //│ ╔══[COMPILATION ERROR] Illegal function member name: 'f'' -//│ ║ l.60: fun f' = 1 +//│ ║ l.73: fun f' = 1 //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. @@ -67,7 +80,7 @@ class Foo with class Foo with class F' //│ ╔══[COMPILATION ERROR] Illegal class member name: 'F'' -//│ ║ l.68: class F' +//│ ║ l.81: class F' //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. @@ -75,7 +88,7 @@ class Foo with class Foo with object F' //│ ╔══[COMPILATION ERROR] Illegal object member name: 'F'' -//│ ║ l.76: object F' +//│ ║ l.89: object F' //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. @@ -83,7 +96,7 @@ class Foo with class Foo with module F' //│ ╔══[COMPILATION ERROR] Illegal module member name: 'F'' -//│ ║ l.84: module F' +//│ ║ l.97: module F' //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. diff --git a/hkmc2/shared/src/test/mlscript/codegen/PrivateMemberExtrusion.mls b/hkmc2/shared/src/test/mlscript/codegen/PrivateMemberExtrusion.mls index be71632d7d..ccb42e44cd 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PrivateMemberExtrusion.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PrivateMemberExtrusion.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/RecordSpreads.mls b/hkmc2/shared/src/test/mlscript/codegen/RecordSpreads.mls index 66f61d5291..071093b122 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/RecordSpreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/RecordSpreads.mls @@ -1,3 +1,4 @@ +:. :js @@ -12,7 +13,7 @@ let rcd = :e (...rcd) //│ ╔══[COMPILATION ERROR] Illegal position for '...' spread operator. -//│ ║ l.13: (...rcd) +//│ ║ l.14: (...rcd) //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/Repl.mls b/hkmc2/shared/src/test/mlscript/codegen/Repl.mls index ea9fe5297c..a388005b62 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Repl.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Repl.mls @@ -1,3 +1,4 @@ +:. :js @@ -7,7 +8,7 @@ val res: Int :showRepl fun res() = 1 -//│ REPL> Sending: block$res3 = undefined +//│ REPL> Sending: block$res4 = undefined //│ REPL> Collected: //│ > undefined //│ REPL> Sending: let res2;try { res2 = function res() { runtime.checkArgs("res", 0, true, arguments.length); return 1 };; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } @@ -15,7 +16,7 @@ fun res() = 1 //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res3));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res4));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ > undefined @@ -25,15 +26,15 @@ fun res() = 1 :showRepl 2 + 2 -//│ REPL> Sending: block$res4 = undefined +//│ REPL> Sending: block$res5 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: try { block$res4 = 4;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { block$res5 = 4;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res4));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res5));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 4 //│ > undefined @@ -44,15 +45,15 @@ fun res() = 1 :showRepl res -//│ REPL> Sending: block$res5 = undefined +//│ REPL> Sending: block$res6 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: try { block$res5 = res2;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { block$res6 = res2;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res5));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res6));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > fun res //│ > undefined @@ -64,10 +65,10 @@ res :showRepl let x = 1, print(x), x -//│ REPL> Sending: block$res6 = undefined +//│ REPL> Sending: block$res7 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: let x;try { x = 1; runtime.checkCall(Predef.print(1)); block$res6 = 1;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: let x;try { x = 1; runtime.checkCall(Predef.print(1)); block$res7 = 1;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 1 //│ > undefined @@ -75,7 +76,7 @@ let x = 1, print(x), x //│ > 1 //│ > undefined //│ > 1 -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res6));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res7));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 1 //│ > undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls b/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls index 3b8ed23c41..062fe5bb07 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/ShortCircuitReturn.mls b/hkmc2/shared/src/test/mlscript/codegen/ShortCircuitReturn.mls index e6c99323be..25ba61c9ee 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ShortCircuitReturn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ShortCircuitReturn.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/SimplePatMat.mls b/hkmc2/shared/src/test/mlscript/codegen/SimplePatMat.mls index cf92e966a3..663e3a6bfe 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SimplePatMat.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SimplePatMat.mls @@ -1,3 +1,4 @@ +:. :js @@ -7,7 +8,7 @@ data class Some[out A](value: A) :todo let Some(x) = Some(42) //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.8: let Some(x) = Some(42) +//│ ║ l.9: let Some(x) = Some(42) //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls b/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls index 463e88cf59..fdbcee2987 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls @@ -1,3 +1,4 @@ +:. :js @@ -24,9 +25,7 @@ module A with print(x, y) A val y = 2 -//│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.25: A -//│ ╙── ^ +//│ ═══[WARNING] Pure expression in statement position //│ > 1 undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/SpecialJSNames.mls b/hkmc2/shared/src/test/mlscript/codegen/SpecialJSNames.mls index 6774b165fb..17f8af21b2 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SpecialJSNames.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SpecialJSNames.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/SwitchSpecialization.mls b/hkmc2/shared/src/test/mlscript/codegen/SwitchSpecialization.mls index 28920cc604..17a8a402f3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SwitchSpecialization.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SwitchSpecialization.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/TailRecFormerFailure.mls b/hkmc2/shared/src/test/mlscript/codegen/TailRecFormerFailure.mls index 804a65e63c..dc07b292f2 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TailRecFormerFailure.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TailRecFormerFailure.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls b/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls index 0f281880a1..dee099a7cf 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls @@ -1,3 +1,4 @@ +:. :js fun plus(x, y) = x + y @@ -10,9 +11,12 @@ fun g(z) = z == -1 then 42 z == 0 then plus(0) z > 0 then g(z - 1) -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 1 -//│ ║ l.11: z == 0 then plus(0) -//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.12: z == 0 then plus(0) +//│ ║ ^^^^^^^ +//│ ╟── Function 'plus' expected 2 arguments, but got 1 +//│ ║ l.4: fun plus(x, y) = x + y +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ fun f(x, y)(z) = x + y + g(z) @@ -64,10 +68,10 @@ f(1,2)(-3) //│ REPL> Sending: runtime.TraceLogger.enabled = true; runtime.TraceLogger.resetIndent(0) //│ REPL> Collected: //│ > Unit {} -//│ REPL> Sending: block$res9 = undefined +//│ REPL> Sending: block$res10 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: let tmp2;try { tmp2 = runtime.checkCall(g(-3)); block$res9 = 3 + tmp2;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: let tmp2;try { tmp2 = runtime.checkCall(g(-3)); block$res10 = 3 + tmp2;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > CALL g(-3) //│ > | CALL g(-2) @@ -93,7 +97,7 @@ f(1,2)(-3) //│ REPL> Sending: runtime.TraceLogger.enabled = false //│ REPL> Collected: //│ > false -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res9));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res10));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 45 //│ > undefined @@ -104,15 +108,15 @@ f(1,2)(-3) :showRepl f(1,2)(-4) -//│ REPL> Sending: block$res10 = undefined +//│ REPL> Sending: block$res11 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: let tmp3;try { tmp3 = runtime.checkCall(g(-4)); block$res10 = 3 + tmp3;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: let tmp3;try { tmp3 = runtime.checkCall(g(-4)); block$res11 = 3 + tmp3;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res10));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res11));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 45 //│ > undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/TypedIRShortCircuit.mls b/hkmc2/shared/src/test/mlscript/codegen/TypedIRShortCircuit.mls index cd09c336ec..d3c46670ad 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TypedIRShortCircuit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TypedIRShortCircuit.mls @@ -1,3 +1,4 @@ +:. :global :js @@ -11,7 +12,7 @@ val n: Int = true && 42 n //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Bool' at an unrelated type 'Int' -//│ ║ l.11: val n: Int = true && 42 +//│ ║ l.12: val n: Int = true && 42 //│ ╙── ^^^^^^^^^^ //│ = 42 //│ n = 42 @@ -21,7 +22,7 @@ n val s: Str = false || "ok" s //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Bool' at an unrelated type 'Str' -//│ ║ l.21: val s: Str = false || "ok" +//│ ║ l.22: val s: Str = false || "ok" //│ ╙── ^^^^^^^^^^^^^ //│ = "ok" //│ s = "ok" diff --git a/hkmc2/shared/src/test/mlscript/codegen/WhileDefaults.mls b/hkmc2/shared/src/test/mlscript/codegen/WhileDefaults.mls index 9231f27125..de280c75ae 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/WhileDefaults.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/WhileDefaults.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/i382.mls b/hkmc2/shared/src/test/mlscript/codegen/i382.mls index a9f3a1b03d..595c4fa126 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/i382.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/i382.mls @@ -1,3 +1,4 @@ +:. :js // https://github.com/hkust-taco/mlscript/issues/382 diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index f4c8531b42..660e519b6d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -18,7 +18,7 @@ foo(1, 2) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.17: foo(1, 2) //│ ║ ^^^^^^^^^ -//│ ╟── Function 'foo' expected 0 arguments, but got 1 +//│ ╟── Function 'foo' expected 1 argument, but got 2 //│ ║ l.4: fun foo(x) = x + 1 //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'foo' expected 1 argument but got 2 diff --git a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls index 75faf0187c..7b257f5ee8 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls @@ -105,10 +105,7 @@ test(bz) //│ ═══[RUNTIME ERROR] Error: match error -:todo set x = bz -//│ ═══[COMPILATION ERROR] Unexpected left-hand side in assignment (reference) -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :breakme // Note: if compiled together with it, this would make the previous code ambiguous x = bz @@ -120,7 +117,7 @@ let x = if maybe then Bar else bz :e x.b //│ ╔══[COMPILATION ERROR] This selection of member 'b' is ambiguous, as it has multiple resolved targets -//│ ║ l.121: x.b +//│ ║ l.118: x.b //│ ║ ^^^ //│ ╟── target: function 'b' in overloaded function and class 'Baz' //│ ║ l.7: class Baz(c) { fun b = c } @@ -162,7 +159,7 @@ if Foo(Predef) is val f = case Foo(y) then y.b //│ ╔══[COMPILATION ERROR] This selection of member 'b' has no resolved target -//│ ║ l.163: Foo(y) then y.b +//│ ║ l.160: Foo(y) then y.b //│ ╙── ^^^ //│ f = fun @@ -170,4 +167,3 @@ val f = case - diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeApplications.mls b/hkmc2/shared/src/test/mlscript/newres/TypeApplications.mls new file mode 100644 index 0000000000..64f2cfefae --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/TypeApplications.mls @@ -0,0 +1,15 @@ +:. + +// Type application preserves the runtime constructor, including in inheritance. +:expect 42 +class Parent[T] +class Child[T](val value) extends Parent[T] +(new Child[Int](42)).value +//│ = 42 + +// Applying type arguments to a function preserves the shape of its result. +:expect 43 +fun identity[T](x) = x +identity[Int](new Child[Int](43)).value +//│ = 43 + diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls b/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls new file mode 100644 index 0000000000..9ba17f2dfe --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls @@ -0,0 +1,59 @@ +:. + +// Wildcard lookup waits for the module's type members. +open Types +fun double(n: Scalar): Scalar = n + n +module Types with + type Scalar = Int +double(21) +//│ = 42 + +// Explicit bindings take precedence over wildcard type names. +open OtherTypes +type Scalar = Int +module OtherTypes with + type Scalar = Str +fun explicit(n: Scalar): Scalar = n +explicit(42) +//│ = 42 + +// An unused signature still diagnoses ambiguous wildcard type names. +:ge +open LeftTypes +open RightTypes +module LeftTypes with + type Ambiguous = Int +module RightTypes with + type Ambiguous = Str +fun ambiguous(n: Ambiguous) = n +//│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference +//│ ║ l.28: fun ambiguous(n: Ambiguous) = n +//│ ║ ^^^^^^^^^ +//│ ╟── This type is ambiguous, as it has multiple resolved targets +//│ ║ l.28: fun ambiguous(n: Ambiguous) = n +//│ ║ ^^^^^^^^^ +//│ ╟── candidate: type alias +//│ ║ l.27: type Ambiguous = Str +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── candidate: type alias +//│ ║ l.25: type Ambiguous = Int +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ + +// A type annotation selects the class even when its name also has a value. +class Box +val Box = 7 +fun identity(box: Box): Box = box +identity(new Box) is Box +//│ = true +//│ Box = 7 + +// Forward aliases are checked even when the annotated function is unused. +:ge +fun unused(value: Invalid) = value +type Invalid = notAType +val notAType = 1 +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ╟── Value symbol 'notAType' cannot be used as a type +//│ ║ l.54: val notAType = 1 +//│ ╙── ^^^^^^^^^^^^^^^^ +//│ notAType = 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/.mls b/hkmc2/shared/src/test/mlscript/ucs/.mls new file mode 100644 index 0000000000..cc02430a79 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/.mls @@ -0,0 +1,3 @@ +:.. +:!js +#lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/ucs/AndNewline.mls b/hkmc2/shared/src/test/mlscript/ucs/AndNewline.mls index 731f20a09e..f0121e319f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/AndNewline.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/AndNewline.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls index 5f2f8e6268..d02ae082c7 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Stack.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/SimpleTree.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/SimpleTree.mls index 7602d1ed58..6fb0a6de6c 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/SimpleTree.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/SimpleTree.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/future/.mls b/hkmc2/shared/src/test/mlscript/ucs/future/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/future/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ucs/future/AppSplits.mls b/hkmc2/shared/src/test/mlscript/ucs/future/AppSplits.mls index 35ce1e6da0..619b4e6738 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/future/AppSplits.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/future/AppSplits.mls @@ -1,3 +1,4 @@ +:. :js // AppSplits.mls @@ -12,11 +13,11 @@ if foo of 0 then "a" 1 then "b" //│ ╔══[COMPILATION ERROR] Unrecognized term split (application) -//│ ║ l.11: if foo of +//│ ║ l.12: if foo of //│ ║ ^^^^^^ -//│ ║ l.12: 0 then "a" +//│ ║ l.13: 0 then "a" //│ ║ ^^^^^^^^^^^^ -//│ ║ l.13: 1 then "b" +//│ ║ l.14: 1 then "b" //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -27,11 +28,11 @@ if foo of 1, 0 then "a" 1 then "b" //│ ╔══[COMPILATION ERROR] Unrecognized term split (application) -//│ ║ l.26: if foo of 1, +//│ ║ l.27: if foo of 1, //│ ║ ^^^^^^^^^ -//│ ║ l.27: 0 then "a" +//│ ║ l.28: 0 then "a" //│ ║ ^^^^^^^^^^^^ -//│ ║ l.28: 1 then "b" +//│ ║ l.29: 1 then "b" //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -42,9 +43,9 @@ if foo (0) then "a" (1) then "b" //│ ╔══[COMPILATION ERROR] Unrecognized term split (juxtaposition) -//│ ║ l.41: if foo +//│ ║ l.42: if foo //│ ║ ^^^ -//│ ║ l.42: (0) then "a" +//│ ║ l.43: (0) then "a" //│ ╙── ^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/future/Or.mls b/hkmc2/shared/src/test/mlscript/ucs/future/Or.mls index 1b6d500fef..b4f2624395 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/future/Or.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/future/Or.mls @@ -1,3 +1,4 @@ +:. :js data class Some[T](value: T) @@ -11,9 +12,9 @@ fun f(a, b) = if a is or b is Some(v) then v else 0 //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.10: and b is Some(v') then v + v' +//│ ║ l.11: and b is Some(v') then v + v' //│ ║ ^^^^^^ -//│ ║ l.11: or b is Some(v) then v +//│ ║ l.12: or b is Some(v) then v //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls b/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls index 7cc3365fd9..1e15c28e91 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls @@ -1,3 +1,4 @@ +:. :js // :de @@ -19,7 +20,7 @@ Cons(1, Nil) :ge 1 :: 2 //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Object' -//│ ║ l.20: 1 :: 2 +//│ ║ l.21: 1 :: 2 //│ ╙── ^ //│ = Cons(1, 2) @@ -33,39 +34,37 @@ fun map(f, xs) = if xs is :re :sjs new 1 :: 2 -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found integer literal. -//│ ║ l.35: new 1 :: 2 -//│ ║ ^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Object' -//│ ║ l.35: new 1 :: 2 -//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.36: new 1 :: 2 +//│ ║ ^^^^^ +//│ ╟── Invalid class expression: integer literal +//│ ║ l.36: new 1 :: 2 +//│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let tmp; tmp = globalThis.Object.freeze(new 1()); return Cons1(tmp, 2) +//│ throw "This code cannot be run as its compilation yielded an error." //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] TypeError: 1 is not a constructor +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // TODO? :e :re :sjs new (1 :: 2) -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found application of type Cons. +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.52: new (1 :: 2) -//│ ║ ^^^^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Object' +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Invalid class expression: application //│ ║ l.52: new (1 :: 2) -//│ ╙── ^ +//│ ╙── ^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let tmp1; tmp1 = Cons1(1, 2); return globalThis.Object.freeze(new tmp1()) +//│ throw "This code cannot be run as its compilation yielded an error." //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] TypeError: tmp1 is not a constructor +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge new ::(1, 2) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Object' -//│ ║ l.66: new ::(1, 2) +//│ ║ l.65: new ::(1, 2) //│ ╙── ^ //│ = Cons(1, 2) diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/.mls b/hkmc2/shared/src/test/mlscript/ucs/general/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/general/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/CardSuits.mls b/hkmc2/shared/src/test/mlscript/ucs/general/CardSuits.mls index 137778fe7a..266ccd8d3a 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/CardSuits.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/CardSuits.mls @@ -1,3 +1,4 @@ +:. :js data class Card[T](color: T) diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/CrossModules.mls b/hkmc2/shared/src/test/mlscript/ucs/general/CrossModules.mls index 8d022599b9..eceb5e9e85 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/CrossModules.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/CrossModules.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls b/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls index ee2ae665be..9576c6d311 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls @@ -1,3 +1,4 @@ +:. :js // DualOptions.mls diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls b/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls index 56bbdcac11..89330baef5 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls @@ -1,3 +1,4 @@ +:. :js abstract class List[out T] diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls b/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls index dfbe6719ef..332573cebf 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/JoinPoints.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/List.mls b/hkmc2/shared/src/test/mlscript/ucs/general/List.mls index 3d25e59ace..079a4b3bf1 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/List.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/List.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Stack.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls index 9d57763640..6552221360 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls @@ -1,17 +1,18 @@ +:. :js :fixme true or true //│ ╔══[COMPILATION ERROR] Logical `or` is not yet supported. -//│ ║ l.5: true or true +//│ ║ l.6: true or true //│ ╙── ^^^^^^^^^^^^ //│ = true :fixme while false or false do print("ok") //│ ╔══[COMPILATION ERROR] Logical `or` is not yet supported. -//│ ║ l.12: while false or false do print("ok") +//│ ║ l.13: while false or false do print("ok") //│ ╙── ^^^^^^^^^^^^^^ @@ -38,7 +39,7 @@ true and test(42) :fixme true or test(42) //│ ╔══[COMPILATION ERROR] Logical `or` is not yet supported. -//│ ║ l.39: true or test(42) +//│ ║ l.40: true or test(42) //│ ╙── ^^^^^^^^^^^^^^^^ //│ > 42 //│ = false diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/Seqs.mls b/hkmc2/shared/src/test/mlscript/ucs/general/Seqs.mls index 016a074f82..8654aa1372 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/Seqs.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/Seqs.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/Simple.mls b/hkmc2/shared/src/test/mlscript/ucs/general/Simple.mls index 02ca86fcd4..4fac9814b3 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/Simple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/Simple.mls @@ -1,3 +1,4 @@ +:. :js // Simple.mls diff --git a/hkmc2/shared/src/test/mlscript/ucs/hygiene/.mls b/hkmc2/shared/src/test/mlscript/ucs/hygiene/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/hygiene/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ucs/hygiene/CrossBranchCapture.mls b/hkmc2/shared/src/test/mlscript/ucs/hygiene/CrossBranchCapture.mls index 1ff5f02e95..7c1e6f4f6a 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/hygiene/CrossBranchCapture.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/hygiene/CrossBranchCapture.mls @@ -1,3 +1,4 @@ +:. :js data class Numb(n: Int) @@ -8,8 +9,8 @@ fun process(e) = Numb(n) and n > 0 then n Numb(m) then n //│ ╔══[COMPILATION ERROR] Name not found: n -//│ ║ l.9: Numb(m) then n -//│ ╙── ^ +//│ ║ l.10: Numb(m) then n +//│ ╙── ^ :re @@ -42,19 +43,19 @@ fun process(e) = Pair(Vec(n), Numb(n)) then n Pair(Numb(n), Vec(n)) then n //│ ╔══[COMPILATION ERROR] Name not found: n -//│ ║ l.41: Pair(Vec(xs), Vec(ys)) then n +//│ ║ l.42: Pair(Vec(xs), Vec(ys)) then n //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Duplicated pattern variable. -//│ ║ l.42: Pair(Vec(n), Numb(n)) then n +//│ ║ l.43: Pair(Vec(n), Numb(n)) then n //│ ║ ^ //│ ╟── The previous definition is as follows. -//│ ║ l.42: Pair(Vec(n), Numb(n)) then n +//│ ║ l.43: Pair(Vec(n), Numb(n)) then n //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Duplicated pattern variable. -//│ ║ l.43: Pair(Numb(n), Vec(n)) then n +//│ ║ l.44: Pair(Numb(n), Vec(n)) then n //│ ║ ^ //│ ╟── The previous definition is as follows. -//│ ║ l.43: Pair(Numb(n), Vec(n)) then n +//│ ║ l.44: Pair(Numb(n), Vec(n)) then n //│ ╙── ^ @@ -64,10 +65,10 @@ fun process(e) = if e is Pair(Numb(n), Numb(n)) then n //│ ╔══[COMPILATION ERROR] Duplicated pattern variable. -//│ ║ l.65: Pair(Numb(n), Numb(n)) then n +//│ ║ l.66: Pair(Numb(n), Numb(n)) then n //│ ║ ^ //│ ╟── The previous definition is as follows. -//│ ║ l.65: Pair(Numb(n), Numb(n)) then n +//│ ║ l.66: Pair(Numb(n), Numb(n)) then n //│ ╙── ^ process(Pair(Numb(1), Numb(2))) diff --git a/hkmc2/shared/src/test/mlscript/ucs/hygiene/Hygiene.mls b/hkmc2/shared/src/test/mlscript/ucs/hygiene/Hygiene.mls index d0b74f1987..7a2544b205 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/hygiene/Hygiene.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/hygiene/Hygiene.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/hygiene/PatVars.mls b/hkmc2/shared/src/test/mlscript/ucs/hygiene/PatVars.mls index a5fff9b39a..83ae620fc6 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/hygiene/PatVars.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/hygiene/PatVars.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/DeduplicationWhile.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/DeduplicationWhile.mls index 885a14f64a..870974b442 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/DeduplicationWhile.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/DeduplicationWhile.mls @@ -1,3 +1,4 @@ +:. :js :patMatConsequentSharingThreshold 1 // ^ force dedup even for small consequents @@ -70,30 +71,31 @@ while x > 0 and x === //│ @private //│ define lambda as fun lambda¹(x) { //│ loop lbl: -//│ let scrut, scrut1, scrut2, scrut3, scrut4; +//│ let scrut, scrut1, scrut2, scrut3, scrut4, scrut5; //│ set scrut = >⁰(x, 0); //│ match scrut //│ true => -//│ set scrut1 = ===⁰(x, 1); -//│ match scrut1 +//│ set scrut1 = x; +//│ set scrut2 = ===⁰(scrut1, 1); +//│ match scrut2 //│ true => //│ do Predef⁰.print⁰("A"); //│ continue lbl //│ else -//│ set scrut2 = ===⁰(x, 2); -//│ match scrut2 +//│ set scrut3 = ===⁰(scrut1, 2); +//│ match scrut3 //│ true => //│ do Predef⁰.print⁰("A"); //│ continue lbl //│ else -//│ set scrut3 = ===⁰(x, 3); -//│ match scrut3 +//│ set scrut4 = ===⁰(scrut1, 3); +//│ match scrut4 //│ true => //│ do Predef⁰.print⁰("B"); //│ continue lbl //│ else -//│ set scrut4 = ===⁰(x, 4); -//│ match scrut4 +//│ set scrut5 = ===⁰(scrut1, 4); +//│ match scrut5 //│ true => //│ do Predef⁰.print⁰("B"); //│ continue lbl @@ -122,20 +124,21 @@ while x > 0 and x === //│ @private //│ define lambda as fun lambda²(x) { //│ loop lbl: -//│ let scrut, scrut1, scrut2, scrut3, scrut4, scrut5, tmp; +//│ let scrut, scrut1, scrut2, scrut3, scrut4, scrut5, scrut6, tmp; //│ set scrut = >⁰(x, 0); //│ match scrut //│ true => -//│ set scrut1 = ===⁰(x, 1); +//│ set scrut1 = x; +//│ set scrut2 = ===⁰(scrut1, 1); //│ block σ$x: //│ block σ: -//│ match scrut1 +//│ match scrut2 //│ true => -//│ set scrut3 = false; -//│ match scrut3 +//│ set scrut4 = false; +//│ match scrut4 //│ true => -//│ set scrut2 = false; -//│ match scrut2 +//│ set scrut3 = false; +//│ match scrut3 //│ true => //│ set tmp = Predef⁰.print⁰("A"); //│ break σ$x @@ -148,11 +151,11 @@ while x > 0 and x === //│ else //│ break σ //│ end -//│ set scrut4 = ===⁰(x, 2); -//│ match scrut4 +//│ set scrut5 = ===⁰(scrut1, 2); +//│ match scrut5 //│ true => -//│ set scrut5 = false; -//│ match scrut5 +//│ set scrut6 = false; +//│ match scrut6 //│ true => //│ set tmp = Predef⁰.print⁰("B"); //│ break σ$x diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls index a51f67d297..d50888862c 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls @@ -1,3 +1,4 @@ +:. :js :patMatConsequentSharingThreshold 1 // ^ force dedup even for small consequents @@ -19,10 +20,15 @@ if x then 0 y then 0 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (x === true) { +//│ let scrut, scrut1; +//│ scrut = x; +//│ if (scrut === true) { +//│ return 0 +//│ } +//│ scrut1 = y; +//│ if (scrut1 === true) { //│ return 0 //│ } -//│ if (y === true) { return 0 } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 0 @@ -33,10 +39,15 @@ if x then 0 y then 1 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (x === true) { +//│ let scrut2, scrut3; +//│ scrut2 = x; +//│ if (scrut2 === true) { //│ return 0 //│ } -//│ if (y === true) { return 1 } +//│ scrut3 = y; +//│ if (scrut3 === true) { +//│ return 1 +//│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 0 @@ -52,32 +63,39 @@ if y is [b] then let x = 1 in x + b //│ Normalized: //│ > ‹if|while› -//│ > let-split σ = y is []=1 and -//│ > let tmp:element0$ = ((tmp:runtime⁰.)Tuple‹module:Tuple›.)get‹term:Tuple/get›(y⁰, 0) -//│ > let b = tmp:element0$⁰ -//│ > else -//│ > let x -//│ > x = 1 -//│ > builtin:+⁰(x⁰, b⁰) -//│ > x is true and -//│ > z is true then 1 +//│ > let tmp:scrut = x⁰ +//│ > let-split σ = +//│ > let tmp:scrut = y⁰ +//│ > tmp:scrut is []=1 and +//│ > let tmp:element0$ = ((tmp:runtime⁰.)Tuple‹module:Tuple›.)get‹term:Tuple/get›(tmp:scrut⁰, 0) +//│ > let b = tmp:element0$⁰ +//│ > else +//│ > let x +//│ > x = 1 +//│ > builtin:+⁰(x¹, b⁰) +//│ > tmp:scrut is true and +//│ > let tmp:scrut = z⁰ +//│ > tmp:scrut is true then 1 //│ > $σ //│ > $σ //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ let element0$, tmp; +//│ let scrut4, scrut5, scrut6, element0$, tmp; +//│ scrut4 = x; //│ σ$x: { -//│ if (x === true) { -//│ if (z === true) { +//│ if (scrut4 === true) { +//│ scrut5 = z; +//│ if (scrut5 === true) { //│ tmp = 1; //│ break σ$x //│ } //│ } -//│ if (runtime.Tuple.isArrayLike(y) && y.length === 1) { -//│ element0$ = runtime.checkCall(runtime.Tuple.get(y, 0)); +//│ scrut6 = y; +//│ if (runtime.Tuple.isArrayLike(scrut6) && scrut6.length === 1) { +//│ element0$ = runtime.checkCall(runtime.Tuple.get(scrut6, 0)); //│ tmp = 1 + element0$; //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ } -//│ block$res4 = tmp; +//│ block$res5 = tmp; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls index d48457a8c0..c1e00b98bc 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/SpecializedSplitSharing.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/SpecializedSplitSharing.mls index 932049bb1e..b253e274c5 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/SpecializedSplitSharing.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/SpecializedSplitSharing.mls @@ -1,3 +1,4 @@ +:. :js @@ -26,10 +27,15 @@ fun f1(x, y) = if else 3 //│ Normalized: //│ > ‹if|while› -//│ > x is Cat() and -//│ > y is 0 then 1 +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is member:Cat⁰^term:f1⁰() and +//│ > let tmp:scrut = y⁰ +//│ > tmp:scrut is 0 then 1 +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is member:Dog⁰^term:f1⁰() then 2 //│ > else 3 -//│ > x is Dog() then 2 +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is member:Dog⁰^term:f1⁰() then 2 //│ > else 3 // * Same shape but with an inner refining test on the `Dog()` branch — the @@ -41,13 +47,19 @@ fun f2(x, y, z) = if else 3 //│ Normalized: //│ > ‹if|while› -//│ > x is Cat() and -//│ > y is 0 then 1 +//│ > let tmp:scrut = x¹ +//│ > let-split σ = +//│ > let tmp:scrut = x¹ +//│ > tmp:scrut is member:Dog⁰^term:f2⁰() and +//│ > let tmp:scrut = z⁰ +//│ > tmp:scrut is 0 then 2 +//│ > else 3 //│ > else 3 -//│ > x is Dog() and -//│ > z is 0 then 2 -//│ > else 3 -//│ > else 3 +//│ > tmp:scrut is member:Cat⁰^term:f2⁰() and +//│ > let tmp:scrut = y¹ +//│ > tmp:scrut is 0 then 1 +//│ > $σ +//│ > $σ // === Specialized split duplication — must remain === @@ -67,9 +79,11 @@ fun g1(x) = if x is S(0) then "zero" S(_) then "any" //│ Normalized: -//│ > ‹if|while› x is S(arg$S$0$) and -//│ > tmp:arg$S$0$ is 0 then "zero" -//│ > else "any" +//│ > ‹if|while› +//│ > let tmp:scrut = x² +//│ > tmp:scrut is member:S⁰^term:g1⁰(arg$S$0$) and +//│ > tmp:arg$S$0$ is 0 then "zero" +//│ > else "any" // * Three branches on the same scrutinee with progressively coarser tests: // * each specialization produces a distinct simplified split. @@ -79,7 +93,9 @@ fun g2(x) = if x is S(1) then "one" S(_) then "other" //│ Normalized: -//│ > ‹if|while› x is S(arg$S$0$) and -//│ > tmp:arg$S$0$ is 0 then "zero" -//│ > tmp:arg$S$0$ is 1 then "one" -//│ > else "other" +//│ > ‹if|while› +//│ > let tmp:scrut = x³ +//│ > tmp:scrut is member:S⁰^term:g2⁰(arg$S$0$) and +//│ > tmp:arg$S$0$ is 0 then "zero" +//│ > tmp:arg$S$0$ is 1 then "one" +//│ > else "other" diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/UnifySubScrutinees.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/UnifySubScrutinees.mls index e690d6b6b5..0d831706d2 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/UnifySubScrutinees.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/UnifySubScrutinees.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Stack.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/UnifyTupleElements.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/UnifyTupleElements.mls index 328390e10c..917b3050ad 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/UnifyTupleElements.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/UnifyTupleElements.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/papers/.mls b/hkmc2/shared/src/test/mlscript/ucs/papers/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/papers/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls index a95feb7531..9fc79d2b22 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls @@ -1,3 +1,4 @@ +:. :js fun example(x) = diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls index 85ebdc5920..b56907f663 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls index 537eacf7e6..94f37913a0 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -8,31 +9,25 @@ open annotations fun trimStart(str) = if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.9: if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str -//│ ╙── ^^^^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'rest' -//│ ║ l.9: if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str -//│ ╙── ^^^^ +//│ ║ l.10: if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str +//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'rest' :fixme fun trimStart(str) = if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.19: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.18: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'rest' -//│ ║ l.19: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str -//│ ╙── ^^^^ +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'rest' :fixme fun trimStart(str) = if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.29: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ║ l.26: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'rest' -//│ ║ l.29: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str -//│ ╙── ^^^^ +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'rest' // * Note: `@compile(...)` is parsed as a whole annotation @@ -41,16 +36,16 @@ fun trimStart(str) = fun trimStart(str) = if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.42: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.37: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.42: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.37: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.42: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.37: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^ -//│ ╔══[COMPILATION ERROR] Name not found: rest -//│ ║ l.42: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: rest +//│ ║ l.37: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/GuardedPatternBindings.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/GuardedPatternBindings.mls index f488cd7364..bc4da1be6b 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/GuardedPatternBindings.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/GuardedPatternBindings.mls @@ -1,13 +1,14 @@ +:. :js :w pattern PlainPair = [x, y] //│ ╔══[WARNING] Unused pattern binding: x. -//│ ║ l.5: pattern PlainPair = [x, y] +//│ ║ l.6: pattern PlainPair = [x, y] //│ ╙── ^ //│ ╔══[WARNING] Unused pattern binding: y. -//│ ║ l.5: pattern PlainPair = [x, y] +//│ ║ l.6: pattern PlainPair = [x, y] //│ ╙── ^ @@ -28,35 +29,35 @@ pattern Symmetry = pattern PairSum = [x, y] where (let x = true, let y = x, y) // We should check the free variables in `where` guards. //│ ╔══[WARNING] Unused pattern binding: x. -//│ ║ l.28: pattern PairSum = [x, y] where (let x = true, let y = x, y) +//│ ║ l.29: pattern PairSum = [x, y] where (let x = true, let y = x, y) //│ ╙── ^ //│ ╔══[WARNING] Unused pattern binding: y. -//│ ║ l.28: pattern PairSum = [x, y] where (let x = true, let y = x, y) +//│ ║ l.29: pattern PairSum = [x, y] where (let x = true, let y = x, y) //│ ╙── ^ :w pattern PairSum = [x, y] where (if x is [y] then y) //│ ╔══[WARNING] Unused pattern binding: y. -//│ ║ l.38: pattern PairSum = [x, y] where (if x is [y] then y) +//│ ║ l.39: pattern PairSum = [x, y] where (if x is [y] then y) //│ ╙── ^ :w pattern PairSum = [x, y] where (class C(y) with { fun test = y }, C(x).test) //│ ╔══[WARNING] Unused pattern binding: y. -//│ ║ l.44: pattern PairSum = [x, y] where (class C(y) with { fun test = y }, C(x).test) +//│ ║ l.45: pattern PairSum = [x, y] where (class C(y) with { fun test = y }, C(x).test) //│ ╙── ^ :w pattern PairSum = [x, y] where (fun y = 1, print(y), x) //│ ╔══[WARNING] Unused pattern binding: y. -//│ ║ l.50: pattern PairSum = [x, y] where (fun y = 1, print(y), x) +//│ ║ l.51: pattern PairSum = [x, y] where (fun y = 1, print(y), x) //│ ╙── ^ // Forward reference to `y` should not be counted as a free variable :w pattern PairSum = [x, y] where (print(y), fun y = 1, x) //│ ╔══[WARNING] Unused pattern binding: y. -//│ ║ l.57: pattern PairSum = [x, y] where (print(y), fun y = 1, x) +//│ ║ l.58: pattern PairSum = [x, y] where (print(y), fun y = 1, x) //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Literals.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Literals.mls index f564604c99..9da7e685cc 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Literals.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Literals.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" @@ -77,7 +78,7 @@ fun dual_patterns(x, y) = :fixme // TODO () is () //│ ╔══[COMPILATION ERROR] Unrecognized pattern (unit). -//│ ║ l.78: () is () +//│ ║ l.79: () is () //│ ╙── ^^ //│ = true diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls index 1004891777..61145a2255 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls @@ -1,3 +1,4 @@ +:. :js fun id(x) = x diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls index f0f0c45a41..7d4038b97a 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" @@ -9,10 +10,10 @@ class Foo(a: Int, val b: Int) :e fun foo(x) = if x is Foo(p, q) then p + q //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.10: fun foo(x) = if x is Foo(p, q) then p + q +//│ ║ l.11: fun foo(x) = if x is Foo(p, q) then p + q //│ ║ ^ //│ ╟── because the corresponding parameter `a` is not publicly accessible -//│ ║ l.7: class Foo(a: Int, val b: Int) +//│ ║ l.8: class Foo(a: Int, val b: Int) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible @@ -39,14 +40,12 @@ keepSecret(SecretPair(2, 3)) :e fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.40: fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result +//│ ║ l.41: fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result //│ ║ ^ //│ ╟── because the corresponding parameter `a` is not publicly accessible -//│ ║ l.29: class SecretPair(a: Int, val b: Int) +//│ ║ l.30: class SecretPair(a: Int, val b: Int) //│ ║ ^ //│ ╟── because rebuilding the matched `SecretPair` value requires reading every constructor argument -//│ ║ l.40: fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result -//│ ║ ^^^^^^^^^^ //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible class Test(a, b, c, d, e, f) @@ -56,59 +55,60 @@ fun test(x) = if x is Test then "ok" :e fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.57: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `a` is not publicly accessible -//│ ║ l.52: class Test(a, b, c, d, e, f) +//│ ║ l.51: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.57: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `b` is not publicly accessible -//│ ║ l.52: class Test(a, b, c, d, e, f) +//│ ║ l.51: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.57: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `c` is not publicly accessible -//│ ║ l.52: class Test(a, b, c, d, e, f) +//│ ║ l.51: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.57: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `d` is not publicly accessible -//│ ║ l.52: class Test(a, b, c, d, e, f) +//│ ║ l.51: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.57: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `e` is not publicly accessible -//│ ║ l.52: class Test(a, b, c, d, e, f) +//│ ║ l.51: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.57: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `f` is not publicly accessible -//│ ║ l.52: class Test(a, b, c, d, e, f) +//│ ║ l.51: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible :e fun test(x) = if x is Test(_, _, _) then "ok" -//│ ╔══[COMPILATION ERROR] Expected six arguments, but found only three arguments. -//│ ║ l.108: fun test(x) = if x is Test(_, _, _) then "ok" -//│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.107: fun test(x) = if x is Test(_, _, _) then "ok" +//│ ║ ^^^^^^^^^^^^^ +//│ ╙── Class definition 'Test' expected 6 pattern arguments, but got 3 fun test(x) = if x is Test(_, _, _, _, _, _) then "ok" @@ -118,7 +118,7 @@ fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ║ l.116: fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `d` is not publicly accessible -//│ ║ l.52: class Test(a, b, c, d, e, f) +//│ ║ l.51: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible @@ -145,16 +145,14 @@ fun foo(x) = if x is A() then 0 //│ ║ ^ //│ ╟── Its pattern cannot have an argument list. //│ ║ l.142: fun foo(x) = if x is A() then 0 -//│ ╙── ^ +//│ ╙── ^^^ :e fun foo(x) = if x is A(y) then y -//│ ╔══[COMPILATION ERROR] `A` is an object. -//│ ║ l.139: object A -//│ ║ ^ -//│ ╟── Its pattern cannot have arguments. +//│ ╔══[COMPILATION ERROR] Resolution error in constructor //│ ║ l.151: fun foo(x) = if x is A(y) then y -//│ ╙── ^^^ +//│ ║ ^^^^ +//│ ╙── Module/object definition 'A' does not take pattern arguments. 0 is Foo //│ = false @@ -208,7 +206,7 @@ if p is Crazy(a, b, c, d, e, f, g) then :re if p is Crazy(a, b, c, d, e, f) then 42 //│ ╔══[COMPILATION ERROR] Expected seven extraction arguments, but found only six arguments. -//│ ║ l.209: if p is Crazy(a, b, c, d, e, f) then 42 +//│ ║ l.207: if p is Crazy(a, b, c, d, e, f) then 42 //│ ╙── ^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -216,7 +214,7 @@ if p is Crazy(a, b, c, d, e, f) then 42 :re if p is Crazy(all) then 42 //│ ╔══[COMPILATION ERROR] Expected seven extraction arguments, but found only one argument. -//│ ║ l.217: if p is Crazy(all) then 42 +//│ ║ l.215: if p is Crazy(all) then 42 //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls index 632d31f3bd..cd2605a06e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls @@ -1,3 +1,4 @@ +:. :ignore :ucs normalized @@ -8,54 +9,41 @@ open Option // OK x => if x is refined(None) then x -//│ ╔══[COMPILATION ERROR] Name not found: refined -//│ ║ l.10: refined(None) then x -//│ ╙── ^^^^^^^ -//│ ╔══[COMPILATION ERROR] Cannot use this ‹error› as a pattern. -//│ ║ l.10: refined(None) then x -//│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] Cannot use this wildcard-open reference as a pattern. +//│ ║ l.11: refined(None) then x +//│ ╙── ^^^^^^^^^^^^^ //│ Normalized: -//│ > ‹if|while› +//│ > ‹if|while› let tmp:scrut = x⁰ // OK x => if x is refined(Some) then x -//│ ╔══[COMPILATION ERROR] Name not found: refined -//│ ║ l.22: refined(Some) then x -//│ ╙── ^^^^^^^ -//│ ╔══[COMPILATION ERROR] Cannot use this ‹error› as a pattern. -//│ ║ l.22: refined(Some) then x -//│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] Cannot use this wildcard-open reference as a pattern. +//│ ║ l.20: refined(Some) then x +//│ ╙── ^^^^^^^^^^^^^ //│ Normalized: -//│ > ‹if|while› +//│ > ‹if|while› let tmp:scrut = x¹ // Should warn about the inconsistent refinement x => if x is refined(None) then x Some then x -//│ ╔══[COMPILATION ERROR] Name not found: refined -//│ ║ l.34: refined(None) then x -//│ ╙── ^^^^^^^ -//│ ╔══[COMPILATION ERROR] Cannot use this ‹error› as a pattern. -//│ ║ l.34: refined(None) then x -//│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] Cannot use this wildcard-open reference as a pattern. +//│ ║ l.29: refined(None) then x +//│ ╙── ^^^^^^^^^^^^^ //│ Normalized: -//│ > ‹if|while› x is Option.Some then x⁰ +//│ > ‹if|while› +//│ > let tmp:scrut = x² +//│ > tmp:scrut is Some‹open› then x² x => if x is refined(None) then x refined(Some) then x -//│ ╔══[COMPILATION ERROR] Name not found: refined -//│ ║ l.46: refined(None) then x -//│ ╙── ^^^^^^^ -//│ ╔══[COMPILATION ERROR] Name not found: refined -//│ ║ l.47: refined(Some) then x -//│ ╙── ^^^^^^^ -//│ ╔══[COMPILATION ERROR] Cannot use this ‹error› as a pattern. -//│ ║ l.46: refined(None) then x -//│ ╙── ^^^^^^^ -//│ ╔══[COMPILATION ERROR] Cannot use this ‹error› as a pattern. -//│ ║ l.47: refined(Some) then x -//│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] Cannot use this wildcard-open reference as a pattern. +//│ ║ l.40: refined(None) then x +//│ ╙── ^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Cannot use this wildcard-open reference as a pattern. +//│ ║ l.41: refined(Some) then x +//│ ╙── ^^^^^^^^^^^^^ //│ Normalized: -//│ > ‹if|while› +//│ > ‹if|while› let tmp:scrut = x³ diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls index fafca822ce..8a8ca18e09 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls index 0e04c45788..9eed01d339 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls @@ -1,3 +1,4 @@ +:. :js @@ -15,17 +16,20 @@ sum(1, 2) fun sum([x, y]) = x + y sum([1, 2]) //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found tuple -//│ ║ l.15: fun sum([x, y]) = x + y +//│ ║ l.16: fun sum([x, y]) = x + y //│ ╙── ^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.15: fun sum([x, y]) = x + y +//│ ║ l.16: fun sum([x, y]) = x + y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.15: fun sum([x, y]) = x + y +//│ ║ l.16: fun sum([x, y]) = x + y //│ ╙── ^ -//│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 1 -//│ ║ l.16: sum([1, 2]) -//│ ╙── ^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.17: sum([1, 2]) +//│ ║ ^^^^^^^^^^^ +//│ ╟── Function 'sum' expected 0 arguments, but got 1 +//│ ║ l.16: fun sum([x, y]) = x + y +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. fun sum(pair) = diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/SpreadTrailingIndex.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/SpreadTrailingIndex.mls index 794c5f6797..6a7a4d0daa 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/SpreadTrailingIndex.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/SpreadTrailingIndex.mls @@ -1,3 +1,4 @@ +:. :js // Regression test: spread destructuring with different trailing sizes should diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls index 449f9bc182..d82e36b318 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls @@ -1,3 +1,4 @@ +:. :parseOnly :pt @@ -133,9 +134,9 @@ if x is B then z B then C //│ ╔══[PARSE ERROR] Expected start of expression in this position; found new line instead -//│ ║ l.132: and v is +//│ ║ l.133: and v is //│ ║ ^ -//│ ║ l.133: B then z +//│ ║ l.134: B then z //│ ╙── ^^^^ //│ ———————————————| Parsed tree |—————————————————————————————————————————————————————————————————————— //│ IfLike: diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/BadUCSSyntax.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/BadUCSSyntax.mls index 4fe34c676d..e7deda684b 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/BadUCSSyntax.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/BadUCSSyntax.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,10 +7,10 @@ :e if x else z //│ ╔══[COMPILATION ERROR] Unrecognized term split (identifier) -//│ ║ l.7: if x else z +//│ ║ l.8: if x else z //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: z -//│ ║ l.7: if x else z +//│ ║ l.8: if x else z //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -18,7 +19,7 @@ fun f(x, z) = if x else z //│ ╔══[COMPILATION ERROR] Unrecognized term split (identifier) -//│ ║ l.18: if x +//│ ║ l.19: if x //│ ╙── ^ // ——— ——— ——— @@ -47,14 +48,14 @@ if and true do print("ok") //│ ╔══[COMPILATION ERROR] Unrecognized term split (true literal) -//│ ║ l.45: and true +//│ ║ l.46: and true //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'do' here -//│ ║ l.46: let x = 1 +//│ ║ l.47: let x = 1 //│ ║ ^ -//│ ║ l.47: and true do +//│ ║ l.48: and true do //│ ║ ^^^^^^^^^^^^^ -//│ ║ l.48: print("ok") +//│ ║ l.49: print("ok") //│ ╙── ^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Case.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Case.mls index b49686c907..63c27351f1 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Case.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Case.mls @@ -1,3 +1,4 @@ +:. :js let f = case x then 0 @@ -48,13 +49,13 @@ h'(2) :e case (0 | 1) //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (parenthesis section). -//│ ║ l.49: case (0 | 1) +//│ ║ l.50: case (0 | 1) //│ ╙── ^^^^^^^ //│ = fun :e case 0 | 1 //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (infix operator '|'). -//│ ║ l.56: case 0 | 1 +//│ ║ l.57: case 0 | 1 //│ ╙── ^^^^^ //│ = fun diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls index dcd99557b3..3359ccacf6 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls @@ -1,3 +1,4 @@ +:. // ConjunctMatches.mls // =================== // Examine the structure of conjunct matches. diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls index 3c69b22b0c..508f911be7 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" @@ -68,13 +69,13 @@ fun g(y) = Some(v) and v % 2 == 0 then set x = Some(v / 2) x //│ ╔══[COMPILATION ERROR] Mixed use of `do` and `then` in the `if` expression. -//│ ║ l.66: if x is +//│ ║ l.67: if x is //│ ║ ^^ //│ ╟── Keyword `then` is used here. -//│ ║ l.68: Some(v) and v % 2 == 0 then set x = Some(v / 2) +//│ ║ l.69: Some(v) and v % 2 == 0 then set x = Some(v / 2) //│ ║ ^^^^ //│ ╟── Keyword `do` is used here. -//│ ║ l.67: Some(0) do set x = None +//│ ║ l.68: Some(0) do set x = None //│ ╙── ^^ g(0) @@ -128,7 +129,7 @@ h(41) Some(1) is Some of [process, rest] do "hi" //│ ╔══[COMPILATION ERROR] Unrecognized pattern (infix operator 'do'). -//│ ║ l.129: Some of [process, rest] do "hi" +//│ ║ l.130: Some of [process, rest] do "hi" //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ = true diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/DoProblems.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/DoProblems.mls index 7fbf8c7295..264019e318 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/DoProblems.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/DoProblems.mls @@ -1,3 +1,4 @@ +:. :js // * It looks like trying to support both infix `do` and prefix `do` is a problem; @@ -20,13 +21,13 @@ while do print(arrMatches) //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'do' here -//│ ║ l.18: let arrMatches = true +//│ ║ l.19: let arrMatches = true //│ ║ ^^^^^^^^^^^^^^^^^ -//│ ║ l.19: arrMatches +//│ ║ l.20: arrMatches //│ ║ ^^^^^^^^^^^^^^ -//│ ║ l.20: do +//│ ║ l.21: do //│ ║ ^^^^ -//│ ║ l.21: print(arrMatches) +//│ ║ l.22: print(arrMatches) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -49,7 +50,7 @@ if do () //│ ╔══[COMPILATION ERROR] Unrecognized term split (null) -//│ ║ l.48: arrMatches !== null +//│ ║ l.49: arrMatches !== null //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Empty.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Empty.mls index 06438179b9..0315a9e370 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Empty.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Empty.mls @@ -1,3 +1,4 @@ +:. :js @@ -28,7 +29,7 @@ if if let x //│ ╔══[COMPILATION ERROR] Unrecognized term split (let) -//│ ║ l.29: let x +//│ ║ l.30: let x //│ ╙── ^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/IfOpSplit.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/IfOpSplit.mls index ff3ae66288..68795c3d0e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/IfOpSplit.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/IfOpSplit.mls @@ -1,3 +1,4 @@ +:. :js @@ -27,23 +28,23 @@ if 1 + 2 * 3 then 0 + 4 then 0 //│ ╔══[PARSE ERROR] Operator cannot be used inside this operator split -//│ ║ l.28: + 4 then 0 +//│ ║ l.29: + 4 then 0 //│ ║ ^ //│ ╟── as it has lower precedence than the splitting operator here -//│ ║ l.27: * 3 then 0 +//│ ║ l.28: * 3 then 0 //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected 'then' keyword in this operator split inner position -//│ ║ l.28: + 4 then 0 +//│ ║ l.29: + 4 then 0 //│ ║ ^^^^ //│ ╟── Note: the operator split starts here -//│ ║ l.27: * 3 then 0 +//│ ║ l.28: * 3 then 0 //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.26: if 1 + 2 +//│ ║ l.27: if 1 + 2 //│ ║ ^ -//│ ║ l.27: * 3 then 0 +//│ ║ l.28: * 3 then 0 //│ ║ ^^^^^^^^^^^^ -//│ ║ l.28: + 4 then 0 +//│ ║ l.29: + 4 then 0 //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -70,9 +71,9 @@ if 1 + 2 * 3 then 0 then 0 //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.70: * 3 then 0 +//│ ║ l.71: * 3 then 0 //│ ║ ^ -//│ ║ l.71: then 0 +//│ ║ l.72: then 0 //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Is.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Is.mls index ac01636309..fde098b59b 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Is.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Is.mls @@ -1,3 +1,4 @@ +:. :parseOnly diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls index 81ed717faf..532a89807e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls @@ -1,3 +1,4 @@ +:. :js :ucs desugared @@ -9,14 +10,16 @@ fun f(x) = _ then 1 //│ Split with nested patterns: //│ > if +//│ > let tmp:scrut = x⁰ //│ > let tmp:scrut = 1 -//│ > let tmp:scrut = (member:Predef⁰.)equals‹member:equals›(x⁰, builtin:+⁰(tmp:scrut⁰, 2)) +//│ > let tmp:scrut = member:Predef⁰.equals^term:f⁰(tmp:scrut⁰, builtin:+⁰(tmp:scrut¹, 2)) //│ > tmp:scrut is true then 0 //│ > else 1 //│ Expanded split with flattened patterns: //│ > ‹if|while› +//│ > let tmp:scrut = x⁰ //│ > let tmp:scrut = 1 -//│ > let tmp:scrut = (member:Predef⁰.)equals‹member:equals›(x⁰, builtin:+⁰(tmp:scrut⁰, 2)) +//│ > let tmp:scrut = member:Predef⁰.equals^term:f⁰(tmp:scrut⁰, builtin:+⁰(tmp:scrut¹, 2)) //│ > tmp:scrut is true then 0 //│ > else 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Of.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Of.mls index d3b79d9968..ced34e0623 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Of.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Of.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls index a0f3d97a8d..58fcadf6c2 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls @@ -1,3 +1,4 @@ +:. :js fun f(x) = x @@ -77,15 +78,15 @@ fun foo(x) = x is Pair(a, b) and a > b Int //│ ╔══[COMPILATION ERROR] Unrecognized pattern (infix operator 'and'). -//│ ║ l.77: Pair(a, b) and a > b +//│ ║ l.78: Pair(a, b) and a > b //│ ╙── ^^^^^^^^^^^^^^^^^^^^ :e fun foo(x) = x is Pair(a, b) | Int //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) -//│ ║ l.85: fun foo(x) = x is Pair(a, b) | Int -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.86: fun foo(x) = x is Pair(a, b) | Int +//│ ╙── ^^^^^^^^^^^^^^^ // Test how shorthand conditionals work together with lambda functions. // ==================================================================== @@ -93,7 +94,7 @@ fun foo(x) = x is Pair(a, b) | Int data class A[T](arg: T) let foo = x => x is A(_) -//│ foo = fun foo +//│ foo = fun :expect [false, true] [foo(0), foo(A(0))] diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls index 936ec87454..1d6f5a7412 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls @@ -1,3 +1,4 @@ +:. :parseOnly :pt diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls index d02c7c20c1..f5e13a1a14 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Stack.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/WithBraces.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/WithBraces.mls index 2634c5bf7d..da7d8cecc6 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/WithBraces.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/WithBraces.mls @@ -1,3 +1,4 @@ +:. // :elt x => if x is { 0 then false, 1 then true } diff --git a/hkmc2/shared/src/test/mlscript/ups/.mls b/hkmc2/shared/src/test/mlscript/ups/.mls new file mode 100644 index 0000000000..cc02430a79 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/.mls @@ -0,0 +1,3 @@ +:.. +:!js +#lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/ups/BadPatterns.mls b/hkmc2/shared/src/test/mlscript/ups/BadPatterns.mls index 2aa64eff58..4dd012109c 100644 --- a/hkmc2/shared/src/test/mlscript/ups/BadPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ups/BadPatterns.mls @@ -1,31 +1,32 @@ +:. :js :e pattern P //│ ╔══[COMPILATION ERROR] Pattern definitions must have a body. -//│ ║ l.5: pattern P +//│ ║ l.6: pattern P //│ ╙── ^^^^^^^^^ :e pattern Foo1[A]: ... //│ ╔══[COMPILATION ERROR] Pattern definitions must have a body. -//│ ║ l.11: pattern Foo1[A]: ... +//│ ║ l.12: pattern Foo1[A]: ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^ :fixme pattern Foo2[pattern A]: ... //│ ╔══[COMPILATION ERROR] Unsupported type parameter type definition -//│ ║ l.17: pattern Foo2[pattern A]: ... +//│ ║ l.18: pattern Foo2[pattern A]: ... //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Pattern definitions must have a body. -//│ ║ l.17: pattern Foo2[pattern A]: ... +//│ ║ l.18: pattern Foo2[pattern A]: ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :e pattern Foo(pattern T) //│ ╔══[COMPILATION ERROR] Pattern definitions must have a body. -//│ ║ l.26: pattern Foo(pattern T) +//│ ║ l.27: pattern Foo(pattern T) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/JoinPatterns.mls b/hkmc2/shared/src/test/mlscript/ups/JoinPatterns.mls index 2114f0349f..4358eafc10 100644 --- a/hkmc2/shared/src/test/mlscript/ups/JoinPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ups/JoinPatterns.mls @@ -1,3 +1,4 @@ +:. :js pattern SimpleJoin = ("abc" | "xyz") ~ ("def" | "") diff --git a/hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls b/hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls index a3a7b6b833..c13359515a 100644 --- a/hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls @@ -1,3 +1,4 @@ +:. :js pattern Lower = "a"..="z" @@ -50,19 +51,16 @@ pattern UnsignedShort = 0 ..< 65536 // Don't forget to add a space between numbers and the range operator. pattern UnsignedByte = 0..< 256 //│ ╔══[LEXICAL ERROR] Expected at least one digit after the decimal point -//│ ║ l.51: pattern UnsignedByte = 0..< 256 +//│ ║ l.52: pattern UnsignedByte = 0..< 256 //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: .< -//│ ║ l.51: pattern UnsignedByte = 0..< 256 -//│ ╙── ^^ -//│ ╔══[COMPILATION ERROR] Cannot use this ‹error› as a pattern. -//│ ║ l.51: pattern UnsignedByte = 0..< 256 +//│ ║ l.52: pattern UnsignedByte = 0..< 256 //│ ╙── ^^ :e pattern BadRange = "s"..=0 //│ ╔══[COMPILATION ERROR] The upper and lower bounds of range patterns should be literals of the same type. -//│ ║ l.63: pattern BadRange = "s"..=0 +//│ ║ l.61: pattern BadRange = "s"..=0 //│ ╙── ^^^^^^^ // It becomes an absurd pattern. @@ -72,14 +70,14 @@ pattern BadRange = "s"..=0 :e pattern BadRange = 0 ..= "s" //│ ╔══[COMPILATION ERROR] The upper and lower bounds of range patterns should be literals of the same type. -//│ ║ l.73: pattern BadRange = 0 ..= "s" +//│ ║ l.71: pattern BadRange = 0 ..= "s" //│ ╙── ^^^^^^^^^ :e pattern BadRange = "yolo" ..= "swag" //│ ╔══[COMPILATION ERROR] The lower bound of character ranges must be a single character. -//│ ║ l.79: pattern BadRange = "yolo" ..= "swag" +//│ ║ l.77: pattern BadRange = "yolo" ..= "swag" //│ ║ ^^^^^^ //│ ╟── The upper bound of character ranges must be a single character. -//│ ║ l.79: pattern BadRange = "yolo" ..= "swag" +//│ ║ l.77: pattern BadRange = "yolo" ..= "swag" //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/.mls b/hkmc2/shared/src/test/mlscript/ups/examples/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/examples/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/BadRecStackParse.mls b/hkmc2/shared/src/test/mlscript/ups/examples/BadRecStackParse.mls index e436d25b5b..527f11a138 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/BadRecStackParse.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/BadRecStackParse.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ups/regex/.mls b/hkmc2/shared/src/test/mlscript/ups/regex/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/regex/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ups/regex/EmptyString.mls b/hkmc2/shared/src/test/mlscript/ups/regex/EmptyString.mls index c04ae45652..00b60978bc 100644 --- a/hkmc2/shared/src/test/mlscript/ups/regex/EmptyString.mls +++ b/hkmc2/shared/src/test/mlscript/ups/regex/EmptyString.mls @@ -1,3 +1,4 @@ +:. :js pattern Oops = "" ~ (Oops | "") diff --git a/hkmc2/shared/src/test/mlscript/ups/regex/Number.mls b/hkmc2/shared/src/test/mlscript/ups/regex/Number.mls index 603d9342c9..9d095efa10 100644 --- a/hkmc2/shared/src/test/mlscript/ups/regex/Number.mls +++ b/hkmc2/shared/src/test/mlscript/ups/regex/Number.mls @@ -1,3 +1,4 @@ +:. :js pattern Digits = ("0"..="9") ~ (Digits | "") diff --git a/hkmc2/shared/src/test/mlscript/ups/regex/Simplification.mls b/hkmc2/shared/src/test/mlscript/ups/regex/Simplification.mls index 2f4bebc00d..986f05b541 100644 --- a/hkmc2/shared/src/test/mlscript/ups/regex/Simplification.mls +++ b/hkmc2/shared/src/test/mlscript/ups/regex/Simplification.mls @@ -1,3 +1,4 @@ +:. :js pattern X = "x" diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/CrossCompilation.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/CrossCompilation.mls index 4561923e4c..7dce3a4882 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/CrossCompilation.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/CrossCompilation.mls @@ -1,3 +1,4 @@ +:. :js open annotations diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/Declaration.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/Declaration.mls index 7fbd5ff943..05422a2b9b 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/Declaration.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/Declaration.mls @@ -1,13 +1,14 @@ +:. :js :e pattern Foo(val T) = null | T //│ ╔══[COMPILATION ERROR] Unexpected pattern parameter T with modifiers: val -//│ ║ l.5: pattern Foo(val T) = null | T +//│ ║ l.6: pattern Foo(val T) = null | T //│ ╙── ^ -//│ ╔══[COMPILATION ERROR] Pattern name not found: T. -//│ ║ l.5: pattern Foo(val T) = null | T +//│ ╔══[COMPILATION ERROR] Name not found: T +//│ ║ l.6: pattern Foo(val T) = null | T //│ ╙── ^ @@ -17,7 +18,7 @@ pattern Foo(pattern T) = null | T :checkIR pattern Foo(pattern T) = null | T //│ ╔══[INTERNAL ERROR] [BlockChecker] Invalid IR: symbol T⁰ is bound more than once -//│ ║ l.18: pattern Foo(pattern T) = null | T +//│ ║ l.19: pattern Foo(pattern T) = null | T //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls index fd3e8e8143..8d2440050f 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls @@ -1,3 +1,4 @@ +:. :ucs ups // The right binding power of `=>` is lower than the left binding power of `|`. diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls index 4f3f6a5364..9da8f34786 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls @@ -1,3 +1,4 @@ +:. :js class Foo(val bar: Int) @@ -15,17 +16,17 @@ pattern Baz(pattern A) = A :e pattern Baz(pattern A) = A() //│ ╔══[COMPILATION ERROR] `A` is a pattern parameter. -//│ ║ l.16: pattern Baz(pattern A) = A() +//│ ║ l.17: pattern Baz(pattern A) = A() //│ ║ ^ //│ ╟── It cannot be applied. -//│ ║ l.16: pattern Baz(pattern A) = A() -//│ ╙── ^ +//│ ║ l.17: pattern Baz(pattern A) = A() +//│ ╙── ^^^ :e pattern Baz(pattern A) = A(1, 2, 3) //│ ╔══[COMPILATION ERROR] `A` is a pattern parameter. -//│ ║ l.25: pattern Baz(pattern A) = A(1, 2, 3) +//│ ║ l.26: pattern Baz(pattern A) = A(1, 2, 3) //│ ║ ^ //│ ╟── It cannot be applied to any arguments. -//│ ║ l.25: pattern Baz(pattern A) = A(1, 2, 3) -//│ ╙── ^^^^^^^^^ +//│ ║ l.26: pattern Baz(pattern A) = A(1, 2, 3) +//│ ╙── ^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls index e45fe7be13..1c9453794a 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls @@ -1,15 +1,18 @@ +:. :js data class Pair[A, B](val first: A, val second: B) :e pattern LessArity = Pair("meow") -//│ ╔══[COMPILATION ERROR] Expected two arguments, but found only one argument. -//│ ║ l.6: pattern LessArity = Pair("meow") -//│ ╙── ^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.7: pattern LessArity = Pair("meow") +//│ ║ ^^^^^^^^^^^^ +//│ ╙── Class definition 'Pair' expected 2 pattern arguments, but got 1 :e pattern MoreArity = Pair("meow", "woof", "moo") -//│ ╔══[COMPILATION ERROR] Expected two arguments, but found three arguments. -//│ ║ l.12: pattern MoreArity = Pair("meow", "woof", "moo") -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.14: pattern MoreArity = Pair("meow", "woof", "moo") +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Class definition 'Pair' expected 2 pattern arguments, but got 3 diff --git a/hkmc2/shared/src/test/mlscript/ups/transformation/.mls b/hkmc2/shared/src/test/mlscript/ups/transformation/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/transformation/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ups/transformation/BasicTransformations.mls b/hkmc2/shared/src/test/mlscript/ups/transformation/BasicTransformations.mls index b3935c69ac..5b75473255 100644 --- a/hkmc2/shared/src/test/mlscript/ups/transformation/BasicTransformations.mls +++ b/hkmc2/shared/src/test/mlscript/ups/transformation/BasicTransformations.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index d2917ad7be..4f3c2f8683 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm // An `Int32`-annotated `val` binds the unboxed result; `ref.i31` boxes it back into a reference. val k: Int32 = wasm.i32.const(1) @@ -43,27 +42,33 @@ wasm.ref.i31(wasm.i32.add(wasm.i32.const(1), wasm.i32.const(2))) :ge wasm.i32.add(1, 2) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Int32' -//│ ║ l.44: wasm.i32.add(1, 2) +//│ ║ l.43: wasm.i32.add(1, 2) //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Int32' -//│ ║ l.44: wasm.i32.add(1, 2) +//│ ║ l.43: wasm.i32.add(1, 2) //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'i32.add' must be of type 'Int32' -//│ ║ l.44: wasm.i32.add(1, 2) +//│ ║ l.43: wasm.i32.add(1, 2) //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Operand #2 of wasm intrinsic 'i32.add' must be of type 'Int32' -//│ ║ l.44: wasm.i32.add(1, 2) +//│ ║ l.43: wasm.i32.add(1, 2) //│ ╙── ^ // Mismatching arity is reported by both the elaborator and the backend. :ge wasm.i32.const(1, 2) -//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 2 -//│ ║ l.61: wasm.i32.const(1, 2) -//│ ╙── ^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.60: wasm.i32.const(1, 2) +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'const' expected 1 argument, but got 2 +//│ ║ in ../decls/Prelude.mls +//│ ║ l.253: fun +//│ ║ ^^^ +//│ ║ l.254: const(c: Int): Int32 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) -//│ ║ l.61: wasm.i32.const(1, 2) +//│ ║ l.60: wasm.i32.const(1, 2) //│ ╙── ^^^^^^^^^^^^^^^^^^^^ @@ -71,9 +76,7 @@ wasm.i32.const(1, 2) // backend, so it would not test this. :ge fun constOf(x: Int) = wasm.ref.i31(wasm.i32.const(x)) -//│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' expects an integer literal immediate -//│ ║ l.73: fun constOf(x: Int) = wasm.ref.i31(wasm.i32.const(x)) -//│ ╙── ^ +//│ ═══[COMPILATION ERROR] Wasm intrinsic 'i32.const' expects an integer literal immediate // Negative literals are elaborated as a call to `Call(BuiltinSymbol("-"), 1)`. diff --git a/hkmc2/shared/src/test/mlscript/wasm/Casts.mls b/hkmc2/shared/src/test/mlscript/wasm/Casts.mls index 4f24c3a927..fdecf45123 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Casts.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Casts.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm // In this example, the function call is equivalent to `useBox((id(new Box(42) as!! Box)))`. @@ -151,10 +150,10 @@ wasm.ref.i31(wasm.i32.const(7)) :noInline wasm.ref.i31(id(wasm.i32.const(7))) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int32' at an unrelated type 'Unknown' -//│ ║ l.152: wasm.ref.i31(id(wasm.i32.const(7))) +//│ ║ l.151: wasm.ref.i31(id(wasm.i32.const(7))) //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Unknown' at an unrelated type 'Int32' -//│ ║ l.152: wasm.ref.i31(id(wasm.i32.const(7))) +//│ ║ l.151: wasm.ref.i31(id(wasm.i32.const(7))) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ //│ ═══[COMPILATION ERROR] Cannot cast a Wasm value of type `i32` to `(ref null any)` in a different type hierarchy //│ ═══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'ref.i31' must be of type 'Int32' @@ -178,9 +177,7 @@ unpack(toObj()) // where `Unknown` cannot be cast to a primitive and is rejected. :e fun test: Function = id -//│ ╔══[COMPILATION ERROR] Returning function instances is not supported -//│ ║ l.180: fun test: Function = id -//│ ╙── ^^ +//│ ═══[COMPILATION ERROR] Returning function instances is not supported :fixme fun test: Function = new Object @@ -221,7 +218,7 @@ fun test: Function = castToA(123) :e castToA(wasm.i32.const(123)) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int32' at an unrelated type 'Unknown' -//│ ║ l.222: castToA(wasm.i32.const(123)) +//│ ║ l.219: castToA(wasm.i32.const(123)) //│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ Wasm result: //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls b/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls index 94b2c3deaf..2964f1ebfe 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm :wat @@ -176,14 +175,15 @@ c.Counter#inc().Counter#double().Counter#get() :todo let d = Counter(c.Counter#get()) -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Missing field `term:Counter/get` in struct `member:Counter` with type `(type $Counter (sub $Object (struct (field $$typeinfo (mut (ref $TypeInfoBase))) (field $x (mut (ref null any))))))` +//│ /!!!\ Uncaught error: java.lang.StackOverflowError :todo d.Counter#inc().Counter#double().Counter#get() -//│ ╔══[COMPILATION ERROR] Cannot find variable `d` (VarSymbol) in local or global scope. -//│ ║ l.178: let d = Counter(c.Counter#get()) -//│ ╙── ^ -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Missing type definition for symbol `member:Counter` +//│ ╔══[COMPILATION ERROR] Name not found: d +//│ ║ l.181: d.Counter#inc().Counter#double().Counter#get() +//│ ╙── ^ +//│ Wasm result: +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :todo c.Counter#get() @@ -192,7 +192,7 @@ c.Counter#get() class GetterOnly() with fun f = 1 -GetterOnly().f() +GetterOnly().f //│ Wasm result: //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/wasm/ControlFlow.mls b/hkmc2/shared/src/test/mlscript/wasm/ControlFlow.mls index 3f77ba0d91..468cf8a296 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ControlFlow.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ControlFlow.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm if 1 === 2 do 2 + 2 diff --git a/hkmc2/shared/src/test/mlscript/wasm/Exceptions.mls b/hkmc2/shared/src/test/mlscript/wasm/Exceptions.mls index faacff2902..5f1311cab4 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Exceptions.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Exceptions.mls @@ -34,6 +34,4 @@ throw false :ge let x = 1 set x += 1 in x -//│ ═══[COMPILATION ERROR] Unexpected left-hand side in assignment (reference) -//│ ═══[COMPILATION ERROR] Unexpected left-hand side in assignment (reference) //│ ═══[COMPILATION ERROR] WatBuilder::returningTerm for TryBlock(...) not implemented yet diff --git a/hkmc2/shared/src/test/mlscript/wasm/NumericIntrinsicReview.mls b/hkmc2/shared/src/test/mlscript/wasm/NumericIntrinsicReview.mls index f0ad695ab5..de63470ff9 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/NumericIntrinsicReview.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/NumericIntrinsicReview.mls @@ -1,7 +1,6 @@ +:. // [Codex] Unboxed results returned from branches must determine the entry result type. -:global -:wasm // Both branches return i32, so the entry must return i32 too. @@ -24,7 +23,7 @@ if true then wasm.f64.const(1.5) else wasm.f64.const(2.5) fun chooseFloat(b: Bool): Float64 = if b then 1.5 else wasm.f64.const(2.5) //│ ═══[COMPILATION ERROR] Cannot use a value of type 'Num' at an unrelated type 'Float64' //│ ╔══[COMPILATION ERROR] WatBackend::result for Lit expression not implemented yet -//│ ║ l.24: fun chooseFloat(b: Bool): Float64 = if b then 1.5 else wasm.f64.const(2.5) +//│ ║ l.23: fun chooseFloat(b: Bool): Float64 = if b then 1.5 else wasm.f64.const(2.5) //│ ╙── ^^^ // A declared function result keeps the type available when its call is not inlined. @@ -94,7 +93,7 @@ if false do wasm.i32.const(1) class ResultBase class ResultLeft extends ResultBase class ResultRight extends ResultBase -if true then ResultLeft() else ResultRight() +if true then new ResultLeft else new ResultRight //│ Wasm result: //│ = {} @@ -109,14 +108,14 @@ if true then throw 1 else wasm.i64.const(9) // Joining a nullable unit reference with a class reference must keep nullability. // :wat class NullableResult -if false then () else NullableResult() +if false then () else new NullableResult //│ Wasm result: //│ = {} // Distinct reference kinds still have a common Wasm reference supertype. class MixedResult -if true then 7 else MixedResult() +if true then 7 else new MixedResult //│ Wasm result: //│ = 7 diff --git a/hkmc2/shared/src/test/mlscript/wasm/NumericTypes.mls b/hkmc2/shared/src/test/mlscript/wasm/NumericTypes.mls index 6b0dd8da5c..f5de42ff63 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/NumericTypes.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/NumericTypes.mls @@ -1,8 +1,7 @@ +:. // This file covers the three unboxed numeric types other than `Int32`: `Int64`, `Float32` and `Float64`. // See `BuiltinOperators.mls` for the `Int32` intrinsics, whose shape these mirror. -:global -:wasm // `Int64` lowers to `i64`, and an `i64` result is handed back to JS as a `BigInt`. @@ -32,7 +31,7 @@ wasm.i64.const(9223372036854775807) :ge wasm.i64.const(9223372036854775808) //│ ╔══[COMPILATION ERROR] Immediate #1 of wasm intrinsic 'i64.const' is outside the signed 64-bit range -//│ ║ l.33: wasm.i64.const(9223372036854775808) +//│ ║ l.32: wasm.i64.const(9223372036854775808) //│ ╙── ^^^^^^^^^^^^^^^^^^^ @@ -98,10 +97,10 @@ wasm.i64.eqz(wasm.i64.const(0)) :ge wasm.i64.add(wasm.i32.const(1), wasm.i32.const(2)) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int32' at an unrelated type 'Int64' -//│ ║ l.99: wasm.i64.add(wasm.i32.const(1), wasm.i32.const(2)) +//│ ║ l.98: wasm.i64.add(wasm.i32.const(1), wasm.i32.const(2)) //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int32' at an unrelated type 'Int64' -//│ ║ l.99: wasm.i64.add(wasm.i32.const(1), wasm.i32.const(2)) +//│ ║ l.98: wasm.i64.add(wasm.i32.const(1), wasm.i32.const(2)) //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ═══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'i64.add' must be of type 'Int64' //│ ═══[COMPILATION ERROR] Operand #2 of wasm intrinsic 'i64.add' must be of type 'Int64' @@ -211,7 +210,7 @@ x :ge wasm.f32.const(1.0e39) //│ ╔══[COMPILATION ERROR] Immediate #1 of wasm intrinsic 'f32.const' is outside the f32 range -//│ ║ l.212: wasm.f32.const(1.0e39) +//│ ║ l.211: wasm.f32.const(1.0e39) //│ ╙── ^^^^^^ @@ -219,26 +218,24 @@ wasm.f32.const(1.0e39) :ge wasm.f64.const(1.0e400) //│ ╔══[COMPILATION ERROR] Immediate #1 of wasm intrinsic 'f64.const' is outside the f64 range -//│ ║ l.220: wasm.f64.const(1.0e400) +//│ ║ l.219: wasm.f64.const(1.0e400) //│ ╙── ^^^^^^^ // Immediates must be written as literals for floats as much as for integers. :ge fun constOf(x: Num): Float64 = wasm.f64.const(x) -//│ ╔══[COMPILATION ERROR] Wasm intrinsic 'f64.const' expects a numeric literal immediate -//│ ║ l.228: fun constOf(x: Num): Float64 = wasm.f64.const(x) -//│ ╙── ^ +//│ ═══[COMPILATION ERROR] Wasm intrinsic 'f64.const' expects a numeric literal immediate // `Float32` and `Float64` are as distinct as the two integer types are. :ge wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Float32' at an unrelated type 'Float64' -//│ ║ l.236: wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) +//│ ║ l.233: wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) //│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Float32' at an unrelated type 'Float64' -//│ ║ l.236: wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) +//│ ║ l.233: wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) //│ ╙── ^^^^^^^^^^^^^^^^^^^^ //│ ═══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'f64.add' must be of type 'Float64' //│ ═══[COMPILATION ERROR] Operand #2 of wasm intrinsic 'f64.add' must be of type 'Float64' diff --git a/hkmc2/shared/src/test/mlscript/wasm/ReplImports.mls b/hkmc2/shared/src/test/mlscript/wasm/ReplImports.mls index b5f8486fd3..2101e73d44 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ReplImports.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ReplImports.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm object Foo with val x = 1 diff --git a/hkmc2/shared/src/test/mlscript/wasm/TailRecOptCasts.mls b/hkmc2/shared/src/test/mlscript/wasm/TailRecOptCasts.mls index e5d5033b96..f206cb43e8 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/TailRecOptCasts.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/TailRecOptCasts.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm // Fixtures shared by the coercion tests below. @@ -273,27 +272,27 @@ fun sccF(): Int31 = wasm.ref.i31(sccG()) fun sccG(): Int32 = wasm.i31.get_s(sccF()) + wasm.i32.const(1) //│ ═══[COMPILATION ERROR] Cannot use a value of type 'Unknown' at an unrelated type 'Int32' //│ ╔══[COMPILATION ERROR] Types 'Int31' and 'Int32' have no common representation -//│ ║ l.273: fun sccG(): Int32 = wasm.i31.get_s(sccF()) + wasm.i32.const(1) +//│ ║ l.272: fun sccG(): Int32 = wasm.i31.get_s(sccF()) + wasm.i32.const(1) //│ ╙── ^^^^ //│ ═══[COMPILATION ERROR] Types 'Int31' and 'Int32' have no common representation //│ ╔══[COMPILATION ERROR] Types 'Int31' and 'Int32' have no common representation -//│ ║ l.272: fun sccF(): Int31 = wasm.ref.i31(sccG()) +//│ ║ l.271: fun sccF(): Int31 = wasm.ref.i31(sccG()) //│ ╙── ^^^^ //│ ═══[COMPILATION ERROR] Types 'Int31' and 'Int32' have no common representation //│ ╔══[COMPILATION ERROR] Types 'Int31' and 'Int32' have no common representation -//│ ║ l.272: fun sccF(): Int31 = wasm.ref.i31(sccG()) +//│ ║ l.271: fun sccF(): Int31 = wasm.ref.i31(sccG()) //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Types 'Int31' and 'Int32' have no common representation -//│ ║ l.273: fun sccG(): Int32 = wasm.i31.get_s(sccF()) + wasm.i32.const(1) +//│ ║ l.272: fun sccG(): Int32 = wasm.i31.get_s(sccF()) + wasm.i32.const(1) //│ ╙── ^^^^ //│ ═══[COMPILATION ERROR] Types 'Int31' and 'Int32' have no common representation //│ ═══[COMPILATION ERROR] Types 'Int31' and 'Int32' have no common representation //│ ═══[COMPILATION ERROR] Cannot cast a Wasm value of type `(ref null any)` to `i32` in a different type hierarchy //│ ╔══[COMPILATION ERROR] Wasm function `sccF` expects result to have type `(ref null i31)`, but got `(ref null any)` -//│ ║ l.272: fun sccF(): Int31 = wasm.ref.i31(sccG()) +//│ ║ l.271: fun sccF(): Int31 = wasm.ref.i31(sccG()) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm function `sccG` expects result to have type `i32`, but got `(ref null any)` -//│ ║ l.273: fun sccG(): Int32 = wasm.i31.get_s(sccF()) + wasm.i32.const(1) +//│ ║ l.272: fun sccG(): Int32 = wasm.i31.get_s(sccF()) + wasm.i32.const(1) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -310,7 +309,5 @@ fun foo(n: Int31) = fun bar(m: Int32) = foo(wasm.ref.i31(m)) //│ ═══[COMPILATION ERROR] Cannot cast a Wasm value of type `i32` to `(ref null any)` in a different type hierarchy -//│ ╔══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'ref.i31' must be of type 'Int32' -//│ ║ l.311: foo(wasm.ref.i31(m)) -//│ ╙── ^ +//│ ═══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'ref.i31' must be of type 'Int32' //│ ═══[COMPILATION ERROR] Cannot cast a Wasm value of type `i32` to `(ref null any)` in a different type hierarchy diff --git a/hkmc2/shared/src/test/mlscript/wasm/Tuples.mls b/hkmc2/shared/src/test/mlscript/wasm/Tuples.mls index 001ec4af34..4dfc9ab194 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Tuples.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Tuples.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm [] //│ Wasm result: diff --git a/hkmc2/shared/src/test/mlscript/wasm/TypeResolution.mls b/hkmc2/shared/src/test/mlscript/wasm/TypeResolution.mls new file mode 100644 index 0000000000..e5872581fa --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/wasm/TypeResolution.mls @@ -0,0 +1,10 @@ +:. + +// The annotation precedes both the alias and its unboxed representation. +fun increment(n: Number): Number = wasm.i32.add(n, wasm.i32.const(1)) +type Number = LaterNumber +type LaterNumber = Int32 +increment(wasm.i32.const(41)) +//│ Wasm result: +//│ = 42 + diff --git a/hkmc2/shared/src/test/mlscript/wasm/VirtualMethods.mls b/hkmc2/shared/src/test/mlscript/wasm/VirtualMethods.mls index 62cb45d746..c883f42639 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/VirtualMethods.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/VirtualMethods.mls @@ -1,6 +1,5 @@ +:. -:global -:wasm class A() with @@ -306,10 +305,10 @@ class M2() extends M1() with fun f(m: Int32): Int = 0 M2().f(1) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Int32' -//│ ║ l.307: M2().f(1) +//│ ║ l.306: M2().f(1) //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Overriding method `f` declares result as `(ref null i31)`, but the method it overrides declares `i32` -//│ ║ l.306: fun f(m: Int32): Int = 0 +//│ ║ l.305: fun f(m: Int32): Int = 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -322,7 +321,7 @@ class N2() extends N1() with N2().g(1) //│ ═══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Int32' //│ ╔══[COMPILATION ERROR] Overriding method `g` declares parameter 1 as `(ref null any)`, but the method it overrides declares `i32` -//│ ║ l.321: fun g(m): Int32 = 0 +//│ ║ l.320: fun g(m): Int32 = 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala index e253af323a..b12226c564 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala @@ -40,7 +40,7 @@ abstract class InvalMLDiffMaker extends JSBackendDiffMaker: given Elaborator.Ctx = curCtx given codegen.Lowering = new codegen.Lowering() // Typing may lower patterns even when executable code generation is disabled. - summon[codegen.Lowering].classHeaders(trm) + summon[codegen.Lowering].prepareTypes(trm) if invalmlTyper.isEmpty then invalmlTyper = S(InvalTyper()) given hkmc2.invalml.InvalCtx = invalCtx.copy(raise = summon) From f405ae4d68df5467fa1bf51a29991d52064fc002 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 18:31:49 +0800 Subject: [PATCH 031/228] Preserve resolution when cloning terms during lowering --- .../main/scala/hkmc2/codegen/Lowering.scala | 8 +- .../scala/hkmc2/semantics/Elaborator.scala | 4 +- .../main/scala/hkmc2/semantics/Resolver.scala | 5 -- .../main/scala/hkmc2/semantics/Shape.scala | 3 +- .../scala/hkmc2/semantics/SimpleSplit.scala | 2 +- .../main/scala/hkmc2/semantics/Split.scala | 4 +- .../src/main/scala/hkmc2/semantics/Term.scala | 80 ++++++++++++++----- .../hkmc2/semantics/flow/FlowAnalysis.scala | 1 + .../hkmc2/semantics/ucs/FlatPattern.scala | 2 +- .../scala/hkmc2/semantics/ups/Compiler.scala | 4 +- .../mlscript/newres/ClonedConstructors.mls | 31 +++++++ .../mlscript/newres/DynamicInstantiation.mls | 11 +++ 12 files changed, 117 insertions(+), 38 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/DynamicInstantiation.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 89db3adda4..46091ca27b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1286,12 +1286,14 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): k(DynSelect(p, f, ai)) - case nw @ (_: New | _: DynNew | Mut(_: New | _: DynNew)) => + case DynNew(cls, args) => + subTerm(cls)(sr => lowerMultiInstantiate(false, sr, args, annots)(k)) + case Mut(DynNew(cls, args)) => + subTerm(cls)(sr => lowerMultiInstantiate(true, sr, args, annots)(k)) + case nw @ (_: New | Mut(_: New)) => val (mut, cls, as, rft, nwtrm) = nw match case nwtrm @ New(c, a, r) => (false, c, a, r, nwtrm) case Mut(nwtrm @ New(c, a, r)) => (true, c, a, r, nwtrm) - case nwtrm @ DynNew(c, a) => (false, c, a, N, nwtrm) - case Mut(nwtrm @ DynNew(c, a)) => (true, c, a, N, nwtrm) case _ => spuriousWarning classOf(cls, nwtrm): sr => rft match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 6b96db3f56..a3ded17c62 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -214,12 +214,12 @@ object Elaborator: case f: OuterCtx.Function => f.isGenerator case _: OuterCtx.LocalScope => parent.fold(false)(_.inGenerator) - case _: (OuterCtx.LambdaOrHandlerBlock.type | OuterCtx.InnerScope | OuterCtx.NonReturnContext.type) => false + case _: (OuterCtx.LambdaOrHandlerBlock.type | OuterCtx.InnerScope | OuterCtx.NonReturnContext) => false def inAsync: Bool = outer match case f: OuterCtx.Function => f.isAsync case _: OuterCtx.LocalScope => parent.fold(false)(_.inAsync) - case _: (OuterCtx.LambdaOrHandlerBlock.type | OuterCtx.InnerScope | OuterCtx.NonReturnContext.type) => false + case _: (OuterCtx.LambdaOrHandlerBlock.type | OuterCtx.InnerScope | OuterCtx.NonReturnContext) => false def potentiallyInstrumented(using Config): Bool = config.effectHandlers.isDefined || inAsync // * Invariant: We expect that the top-level context only contain hard-coded symbols like `globalThis` diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index 6697ca77d5..5df92e427c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -571,11 +571,6 @@ class Resolver(tl: TraceLogger) cls.foreach(traverse(_, expect = Class(N))) (t.callableDefn, ictx) - case Term.DynNew(cls, args) => - traverse(cls, expect = NonModule(N)) - args.foreach(traverse(_, expect = NonModule(N))) - resolveType(t, prefer = prefer) - (t.callableDefn, ictx) case Term.New(cls, args, rft) => // Term.New has only a type, but does not have a symbol. traverse(cls, expect = Class(S("The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation."))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 4289204565..a262216f1a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -20,6 +20,8 @@ sealed trait Shape extends ShapeLike: case ns: SymShape => s"SymShape(${ns.sym.showDbg})" case ns: NewShape => s"NewShape(${ns.cls.showDbg}, ${ns.argss.map(_.showDbg).mkString(", ")})" case is: IntroShape => s"IntroShape(${is.trm.showDbg})" + case bs: BaseShape => s"BaseShape(${bs.defn.sym.showDbg})" + case es: ErrShape => es.describe sealed trait NonMarkedShape extends TermShape sealed trait NonAppTermShape extends NonMarkedShape @@ -308,7 +310,6 @@ class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTer defn.tsym match case _: ClassCtorSymbol => "class constructor " case s => s.k.desc + " " - case _ => "" }'${defn.bsym.nme}'" // override def toString: String = s"DefnShape(${defn.describe} ${defn.bsym.nme})" override def toString: String = s"DefnShape(${defn.describe})" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala index 459c6de930..4d004d1a56 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala @@ -96,7 +96,7 @@ enum SimpleSplit extends AutoLocated with ProductWithTail: _expandedSplit = S(split) split - def mkClone(using State): SimpleSplit = this match + def mkClone(using State, codegen.Lowering): SimpleSplit = this match case Cons(head, tail) => Cons(head match case Head.Match(scrutinee, pattern, consequent) => Head.Match(scrutinee.mkClone.asInstanceOf, pattern, consequent.mkClone) // TODO: clone `pattern`? diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index faeea86f6a..c92da10b8a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -5,7 +5,7 @@ import hkmc2.utils.*, shorthands.* import syntax.*, Elaborator.State, ucs.FlatPattern final case class Branch(scrutinee: Term.Ref, pattern: FlatPattern, continuation: Split) extends AutoLocated: - def mkClone(using State): Branch = + def mkClone(using State, codegen.Lowering): Branch = val scrutineeClone = new Term.Ref(scrutinee.sym) (Tree.Ident(scrutinee.tree.name), scrutinee.typ) Branch(scrutineeClone, pattern.mkClone, continuation.mkClone) @@ -31,7 +31,7 @@ enum Split extends AutoLocated with ProductWithTail: inline def ~:(head: Branch): Split = Split.Cons(head, this) - def mkClone(using State): Split = this match + def mkClone(using State, codegen.Lowering): Split = this match case Cons(head, tail) => Cons(head.mkClone, tail.mkClone) case Let(sym, term, tail) => Let(sym, term.mkClone, tail.mkClone) case Else(default) => Else(default.mkClone) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 247d83c265..b2d41dd339 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -77,7 +77,7 @@ enum Annot extends AutoLocated: case Trm(trm) => doc"@${trm.show}" case Config(_) => doc"@config(...)" - def mkClone(using State): Annot = this match + def mkClone(using State, codegen.Lowering): Annot = this match case Untyped => Untyped case Modifier(mod) => Modifier(mod) case Trm(trm) => Trm(trm.mkClone) @@ -335,7 +335,7 @@ case class SrcScope(outer: Elaborator.OuterCtx, parent: Opt[SrcScope]): case S(par) => val (base, path) = par.outermostAcessibleBase (base, inner :: path) - case _: (Function | LocalScope) | LambdaOrHandlerBlock | NonReturnContext => + case _: (Function | LocalScope | NonReturnContext) | LambdaOrHandlerBlock => (this, Nil) object SrcScope: @@ -441,7 +441,7 @@ enum Term extends Statement, ShapePublisher: case Unquoted(body: Term) case New(cls: Term, args: Ls[Term], rft: Opt[ClassSymbol -> ObjBody]) (val resSym: FlowSymbol, val typ: Opt[Type]) extends Term, ResolvableImpl - case DynNew(cls: Term, args: Ls[Term]) extends Term, ResolvableImpl + case DynNew(cls: Term, args: Ls[Term]) case Asc(term: Term, ty: Term) case CompType(lhs: Term, rhs: Term, pol: Bool) case Neg(rhs: Term) @@ -603,7 +603,24 @@ enum Term extends Statement, ShapePublisher: App(this, Tup(args.toList.map(PlainFld(_)))(Tree.DummyTup)) (Tree.App(Tree.Dummy, Tree.Dummy), N, FlowSymbol("")) - override def mkClone(using State): Term = + /** Duplicate an elaborated expression after resolution, retaining its semantic + * identity and completed results. Listeners belong to elaboration and are not copied. + */ + override def mkClone(using State, codegen.Lowering): Term = + def copyShapes[T <: ShapeHost](source: ShapeHost, copy: T): T = + copy.shapes ++= source.shapes + copy + def copyResolution[T <: Resolvable](source: Resolvable, copy: T): T = + copy.isErroneous = source.isErroneous + source.expansion.foreach(expansion => copy.expand(expansion.map(_.mkClone))) + copyShapes(source, copy) + def copySelection[T <: Term & AnySel](source: Term & AnySel, copy: T): T = + copy.resolvedTargets = source.resolvedTargets + copyResolution(source, copy) + def copyNewResolution[T <: NewResolvable](source: NewResolvable, copy: T): T = + copy.resolvedTargets = source.resolvedTargets + copy.isErroneous = source.isErroneous + copy val that = this match case Error() => Error() case UnitVal() => UnitVal() @@ -613,13 +630,36 @@ enum Term extends Statement, ShapePublisher: case Lit(Tree.DecLit(value)) => Lit(Tree.DecLit(value)) case Lit(Tree.BoolLit(value)) => Lit(Tree.BoolLit(value)) case Lit(Tree.UnitLit(value)) => Lit(Tree.UnitLit(value)) - case term @ Resolved(t, sym) => Resolved(t.mkClone, sym)(term.typ) - case term @ Ref(sym) => Ref(sym)(Tree.Ident(term.tree.name), term.typ) - case term @ App(lhs, rhs) => App(lhs.mkClone, rhs.mkClone)(term.tree, term.typ, term.resSym) - case term @ TyApp(lhs, targs) => TyApp(lhs.mkClone, targs.map(_.mkClone))(term.typ) - case term @ Sel(prefix, nme) => Sel(prefix.mkClone, Tree.Ident(nme.name))(term.sym, term.resSym, term.typ, term.originalCtx) - case term @ SynthSel(prefix, nme) => SynthSel(prefix.mkClone, Tree.Ident(nme.name))(term.sym, term.resSym, term.typ, term.originalCtx) - case term @ LeadingDotSel(nme) => LeadingDotSel(Tree.Ident(nme.name))(term.originalCtx) + case term @ SimpleRef(sym) => SimpleRef(sym)(term.tree) + case term @ SelfRef(sym) => SelfRef(sym)(term.tree) + case term @ MemberRef(sym) => copyNewResolution(term, MemberRef(sym)(term.tree, term.resSym)) + case term @ NewSel(prefix, id, cls) => + val copy = NewSel(prefix.mkClone, id, cls.map(_.mkClone))(term.resSym) + copy.resolvedMembers = term.resolvedMembers + copy.resolvedClasses = term.resolvedClasses + copyNewResolution(term, copyShapes(term, copy)) + case term @ UnresolvedRef(prefixes, id) => + val clonedPrefixes = prefixes.map(_.mkClone) + val copy = UnresolvedRef(clonedPrefixes, id)(term.resSym) + copy.resolvedMembers = term.resolvedMembers.map: (receiver, member) => + val index = prefixes.indexWhere(_ is receiver) + assert(index >= 0, "A wildcard candidate must belong to an opened prefix") + clonedPrefixes(index) -> member + copyNewResolution(term, copyShapes(term, copy)) + case Capture(base, thru) => Capture(base.mkClone, thru) + case term @ Resolved(t, sym) => copyResolution(term, Resolved(t.mkClone, sym)(term.typ)) + case term @ Ref(sym) => copyResolution(term, Ref(sym)(Tree.Ident(term.tree.name), term.typ)) + case term @ App(lhs, rhs) => + val copy = App(lhs.mkClone, rhs.mkClone)(term.tree, term.typ, term.resSym) + copy.resolvedTargets = term.resolvedTargets + copyResolution(term, copy) + case term @ TyApp(lhs, targs) => copyResolution(term, TyApp(lhs.mkClone, targs.map(_.mkClone))(term.typ)) + case term @ Sel(prefix, nme) => copySelection(term, Sel(prefix.mkClone, Tree.Ident(nme.name))(term.sym, term.resSym, term.typ, term.originalCtx)) + case term @ SynthSel(prefix, nme) => copySelection(term, SynthSel(prefix.mkClone, Tree.Ident(nme.name))(term.sym, term.resSym, term.typ, term.originalCtx)) + case term @ LeadingDotSel(nme) => + val copy = LeadingDotSel(Tree.Ident(nme.name))(term.originalCtx) + copy.resolvedTargets = term.resolvedTargets + copyResolution(term, copy) case DynSel(prefix, fld, arrayIdx) => DynSel(prefix.mkClone, fld.mkClone, arrayIdx) case term @ Tup(fields) => Tup(fields.map { case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) @@ -630,7 +670,7 @@ enum Term extends Statement, ShapePublisher: case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) case s: Spd => s.copy(term = s.term.mkClone) })(term.tree) - case IfLike(kw, form, split) => IfLike(kw, form, split.mkClone) + case term @ IfLike(kw, form, split) => copyShapes(term, IfLike(kw, form, split.mkClone)) case SynthIf(split) => SynthIf(split.mkClone) case SynthWhile(split) => SynthWhile(split.mkClone) case Lam(params, body) => Lam(params, body.mkClone) @@ -643,10 +683,10 @@ enum Term extends Statement, ShapePublisher: case Quoted(body) => Quoted(body.mkClone) case Unquoted(body) => Unquoted(body.mkClone) case term @ New(cls, args, rft) => - New(cls.mkClone, args.map(_.mkClone), rft.map { case (cs, ob) => cs -> ObjBody(ob.blk.mkBlkClone) })(term.resSym, term.typ) + copyResolution(term, New(cls.mkClone, args.map(_.mkClone), rft.map { case (cs, ob) => cs -> ObjBody(ob.blk.mkBlkClone) })(term.resSym, term.typ)) case DynNew(cls, args) => DynNew(cls.mkClone, args.map(_.mkClone)) case term @ SelProj(prefix, cls, proj) => - SelProj(prefix.mkClone, cls.mkClone, Tree.Ident(proj.name))(term.sym, term.resSym, term.typ, term.originalCtx) + copySelection(term, SelProj(prefix.mkClone, cls.mkClone, Tree.Ident(proj.name))(term.sym, term.resSym, term.typ, term.originalCtx)) case Asc(term, ty) => Asc(term.mkClone, ty.mkClone) case CompType(lhs, rhs, pol) => CompType(lhs.mkClone, rhs.mkClone, pol) case Neg(rhs) => Neg(rhs.mkClone) @@ -665,11 +705,9 @@ enum Term extends Statement, ShapePublisher: case Annotated(annot, target) => Annotated(annot, target.mkClone) case Handle(lhs, rhs, args, derivedClsSym, defs, body) => Handle(lhs, rhs.mkClone, args.map(_.mkClone), derivedClsSym, defs, body.mkClone) - (this, that) match - case (self: Resolvable, that: Resolvable) if self.expansion.isDefined => - that.expand(self.expansion.get.map(_.mkClone)) - case _ => - that + that.withLocOf(this) + that.typeInterpretation = typeInterpretation + that // // private[semantics] val reslListeners: Buffer[Resolution] = MutSet.empty // private[semantics] val shapeListeners: Buffer[Shape => Unit] = Buffer.empty @@ -756,7 +794,7 @@ trait Describable: sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: - def mkClone(using State): Statement = this match + def mkClone(using State, codegen.Lowering): Statement = this match case t: Term => lastWords(s"overridden implementation") case d: Definition => ??? case imp: Import => Import(imp.sym, imp.str, imp.file) @@ -1681,7 +1719,7 @@ object Apps: trait BlkImpl: this: Blk => - def mkBlkClone(using State): Blk = Blk(stats.map(_.mkClone), res.mkClone) + def mkBlkClone(using State, codegen.Lowering): Blk = Blk(stats.map(_.mkClone), res.mkClone) def showTopLevel(using Scope, ShowCfg, Raise): Document = (stats ::: (res match case Lit(Tree.UnitLit(false)) => Nil diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala index 28fb864d11..190c29b243 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -308,6 +308,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): :: targets.map: case ObjectMember(sym) => msg"object member ${sym.nme}" -> sym.toLoc case CompanionMember(_, sym) => msg"companion member ${sym.nme}" -> sym.toLoc + case target: SelectionTarget.Err => msg"${target.describe}" -> target.loc leadingDotSelsToExpand.foreach: sel => log(s"Resolved targets for ${sel.showDbg}: ${sel.resolvedTargets.mkString(", ")}") assert(sel.expansion.isEmpty) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala index db1baaa295..1f3df539d0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -37,7 +37,7 @@ enum FlatPattern extends AutoLocated: case Record(entries: List[(Ident -> LocalVarSymbol)]) - def mkClone(using State): FlatPattern = this match + def mkClone(using State, codegen.Lowering): FlatPattern = this match case Lit(literal) => Lit(literal) case pattern @ ClassLike(constructor, symbol, arguments, refined) => ClassLike(constructor.mkClone, symbol, arguments, refined)(Tree.Dummy) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala index 8a0f127bd8..b05efb12ad 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -18,7 +18,7 @@ import collection.mutable.{Queue, Map as MutMap, LinkedHashMap}, collection.immu * a few matcher functions. Each matcher function matches a set of patterns * and returns a record that contains the results of each pattern. */ -class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends TermSynthesizer: +class Compiler(using Context)(using codegen.Lowering)(using tl: TL)(using Ctx, State, Raise) extends TermSynthesizer: import Compiler.*, tl.* import Pattern.* @@ -573,7 +573,7 @@ object Compiler: /** Clone a source-level constructor reference for insertion into synthesized * matcher code, preserving the path chosen by source resolution. */ - def preservedReference(term: Term)(using State): Term = + def preservedReference(term: Term)(using State, codegen.Lowering): Term = /** To make `Lowering` happy about the terms. */ def fillImplicitArgs(term: Term): Term = term match case ref: Ref => diff --git a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls new file mode 100644 index 0000000000..ac9c16db6b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls @@ -0,0 +1,31 @@ +:js + +open annotations + +// Compiled patterns clone both direct and qualified constructor references. +data class Direct(val value) +module Nested with + data class Wrapped(val value) + +fun direct(x) = if x is @compile Direct(3) then true else false +fun qualified(x) = if x is @compile Nested.Wrapped(3) then true else false + +:expect [true, false, true, false] +[direct(Direct(3)), direct(Direct(4)), qualified(Nested.Wrapped(3)), qualified(Nested.Wrapped(4))] +//│ = [true, false, true, false] + +// Wildcard candidates must keep the receiver selected by resolution. +open Nested +fun opened(x) = if x is @compile Wrapped(3) then true else false + +:expect [true, false] +[opened(Nested.Wrapped(3)), opened(Nested.Wrapped(4))] +//│ = [true, false] + +// Preserve a captured module parameter in a cloned constructor path. +fun captured(module Inner: Nested, x) = + if x is @compile Inner.Wrapped(3) then true else false + +:expect [true, false] +[captured(Nested, Nested.Wrapped(3)), captured(Nested, Nested.Wrapped(4))] +//│ = [true, false] diff --git a/hkmc2/shared/src/test/mlscript/newres/DynamicInstantiation.mls b/hkmc2/shared/src/test/mlscript/newres/DynamicInstantiation.mls new file mode 100644 index 0000000000..70d8d68e6e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/DynamicInstantiation.mls @@ -0,0 +1,11 @@ +:js + +class First +class Second + +fun construct(c) = new! c +fun constructMutable(c) = new! mut c + +:expect [First, Second, First, Second] +[construct(First), construct(Second), constructMutable(First), constructMutable(Second)] +//│ = [First, Second, First, Second] From 5d960f5848918868e8fe11a3a0037f3487e19857 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 18:33:59 +0800 Subject: [PATCH 032/228] Reject legacy synthetic selections in new resolution --- .../scala/hkmc2/semantics/NewResolver.scala | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 9b9a13aa68..60a40cb8b2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -597,23 +597,8 @@ class NewResolver: log(s"listen: trm = ${trm.showDbg}") trm.shapeListeners += listener trm match - // * Synthetic selections are not really selections from the POV of the resolver. - // * Eg: a plain member reference or plain reference to imported symbol - // * Later, we should make Terms more closely aligned wiht the source and remove SynthSel - case ss: SynthSel => - ss.sym match - case S(ts: TermSymbol) => - ??? - ts.defn.get.body match - case S(body) => - listenTerm(body)(listener) - case N => - ??? // TODO error? use sig - case S(bms: BlockMemberSymbol) => - // TODO: add mark - fromBMS(bms, ss.resSym, Nil//TODO? - , listener, trm, _ => ()) - case N => ??? + case _: SynthSel => + lastWords("Synthetic selections must not enter new resolution") case TyApp(underlying, _) => listen(underlying, discardMarks)(listener) case Mut(underlying) => listenTerm(underlying)(listener) case intro: IntroTerm => From 3a32f6f0d49735bb58de58f0144e250f36e7e628 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 22 Sep 2026 18:36:50 +0800 Subject: [PATCH 033/228] Fail fast on builtin SimpleRef in new resolution --- hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 60a40cb8b2..5c690b3494 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -615,6 +615,8 @@ class NewResolver: case loc: LocalSymbol => loc.shapes.foreach(listener) loc.shapeListeners += listener + case _: BuiltinSymbol => + lastWords("Builtin symbols must not enter new resolution as SimpleRef") case SelfRef(sym) => // A receiver can be referenced before its body is complete or after its // definition has already been published. The definition listener handles both. From 444498f1bcc6584a187a734a5e0e1d377937f546 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 22 Sep 2026 21:46:16 +0800 Subject: [PATCH 034/228] Minor --- hkmc2/shared/src/test/mlscript/.mls | 1 - hkmc2/shared/src/test/mlscript/apps/.mls | 1 - hkmc2/shared/src/test/mlscript/basics/.mls | 1 - .../mlscript/basics/BadMemberProjections.mls | 2 +- hkmc2/shared/src/test/mlscript/codegen/.mls | 1 - .../test/mlscript/codegen/DelayedLetInit.mls | 20 ++++++++--------- .../src/test/mlscript/codegen/Functions.mls | 4 ++-- .../shared/src/test/mlscript/codegen/Repl.mls | 22 +++++++++---------- .../test/mlscript/codegen/TraceLogIndent.mls | 12 +++++----- hkmc2/shared/src/test/mlscript/newres/.mls | 1 + hkmc2/shared/src/test/mlscript/ucs/.mls | 1 - .../normalization/ExcessiveDeduplication.mls | 2 +- hkmc2/shared/src/test/mlscript/ups/.mls | 1 - hkmc2/shared/src/test/mlscript/wasm/.mls | 1 - 14 files changed, 32 insertions(+), 38 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/.mls b/hkmc2/shared/src/test/mlscript/.mls index f0f0319735..54926c1b7d 100644 --- a/hkmc2/shared/src/test/mlscript/.mls +++ b/hkmc2/shared/src/test/mlscript/.mls @@ -1,2 +1 @@ -:js #lang(0.3.x) diff --git a/hkmc2/shared/src/test/mlscript/apps/.mls b/hkmc2/shared/src/test/mlscript/apps/.mls index cc02430a79..411541472f 100644 --- a/hkmc2/shared/src/test/mlscript/apps/.mls +++ b/hkmc2/shared/src/test/mlscript/apps/.mls @@ -1,3 +1,2 @@ :.. -:!js #lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/basics/.mls b/hkmc2/shared/src/test/mlscript/basics/.mls index cc02430a79..411541472f 100644 --- a/hkmc2/shared/src/test/mlscript/basics/.mls +++ b/hkmc2/shared/src/test/mlscript/basics/.mls @@ -1,3 +1,2 @@ :.. -:!js #lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls b/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls index 0106d3f60f..48da72d385 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls @@ -42,7 +42,7 @@ //│ runtime.checkArgs("", 1, false, arguments.length); //│ throw "This code cannot be run as its compilation yielded an error." //│ }); -//│ block$res3 = runtime.safeCall(lambda1()); +//│ block$res2 = runtime.safeCall(lambda1()); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] Error: Function expected at least 1 argument but got 0 diff --git a/hkmc2/shared/src/test/mlscript/codegen/.mls b/hkmc2/shared/src/test/mlscript/codegen/.mls index cc02430a79..411541472f 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/.mls @@ -1,3 +1,2 @@ :.. -:!js #lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls index 0cef4522d0..4f9fec780a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls @@ -14,7 +14,7 @@ let x print(x) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res3 = runtime.checkCall(Predef.print(x)); +//│ block$res2 = runtime.checkCall(Predef.print(x)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > undefined @@ -25,7 +25,7 @@ x = 1 x //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res5 = x; +//│ block$res4 = x; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 @@ -37,7 +37,7 @@ x = 2 x //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res7 = x; +//│ block$res6 = x; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 2 @@ -71,7 +71,7 @@ fun f() = 1 f //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res11 = f; +//│ block$res10 = f; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun f @@ -90,7 +90,7 @@ f(x) = x + 1 f(1) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res13 = runtime.safeCall(f1(1)); +//│ block$res12 = runtime.safeCall(f1(1)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 2 @@ -114,7 +114,7 @@ else foo = 1 //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo1; //│ if (true === true) { -//│ block$res15 = foo1; +//│ block$res14 = foo1; //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ foo = undefined @@ -129,8 +129,8 @@ else //│ let foo2; //│ if (true === true) { //│ foo2 = 0; -//│ block$res16 = runtime.Unit; -//│ } else { foo2 = 1; block$res16 = runtime.Unit; } +//│ block$res15 = runtime.Unit; +//│ } else { foo2 = 1; block$res15 = runtime.Unit; } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ foo = 0 @@ -148,12 +148,12 @@ fun f() = f() //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ foo2 = 42; block$res18 = runtime.Unit; +//│ foo2 = 42; block$res17 = runtime.Unit; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— foo //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res19 = foo2; +//│ block$res18 = foo2; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls index 1cdbde8230..4240a29969 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls @@ -88,7 +88,7 @@ outerfun(100)(200) :showRepl fun test1(x) = test2(x) fun test2(y) = y + 1 -//│ REPL> Sending: block$res19 = undefined +//│ REPL> Sending: block$res18 = undefined //│ REPL> Collected: //│ > undefined //│ REPL> Sending: let test2, test1;try { test1 = function test1(x2) { runtime.checkArgs("test1", 1, true, arguments.length); return x2 + 1 }; test2 = function test2(y2) { runtime.checkArgs("test2", 1, true, arguments.length); return y2 + 1 };; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } @@ -96,7 +96,7 @@ fun test2(y) = y + 1 //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res19));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res18));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ > undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/Repl.mls b/hkmc2/shared/src/test/mlscript/codegen/Repl.mls index a388005b62..7deec19bb2 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Repl.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Repl.mls @@ -8,7 +8,7 @@ val res: Int :showRepl fun res() = 1 -//│ REPL> Sending: block$res4 = undefined +//│ REPL> Sending: block$res3 = undefined //│ REPL> Collected: //│ > undefined //│ REPL> Sending: let res2;try { res2 = function res() { runtime.checkArgs("res", 0, true, arguments.length); return 1 };; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } @@ -16,7 +16,7 @@ fun res() = 1 //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res4));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res3));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ > undefined @@ -26,15 +26,15 @@ fun res() = 1 :showRepl 2 + 2 -//│ REPL> Sending: block$res5 = undefined +//│ REPL> Sending: block$res4 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: try { block$res5 = 4;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { block$res4 = 4;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res5));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res4));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 4 //│ > undefined @@ -45,15 +45,15 @@ fun res() = 1 :showRepl res -//│ REPL> Sending: block$res6 = undefined +//│ REPL> Sending: block$res5 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: try { block$res6 = res2;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { block$res5 = res2;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res6));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res5));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > fun res //│ > undefined @@ -65,10 +65,10 @@ res :showRepl let x = 1, print(x), x -//│ REPL> Sending: block$res7 = undefined +//│ REPL> Sending: block$res6 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: let x;try { x = 1; runtime.checkCall(Predef.print(1)); block$res7 = 1;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: let x;try { x = 1; runtime.checkCall(Predef.print(1)); block$res6 = 1;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 1 //│ > undefined @@ -76,7 +76,7 @@ let x = 1, print(x), x //│ > 1 //│ > undefined //│ > 1 -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res7));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res6));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 1 //│ > undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls b/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls index dee099a7cf..565a88a1a3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls @@ -68,10 +68,10 @@ f(1,2)(-3) //│ REPL> Sending: runtime.TraceLogger.enabled = true; runtime.TraceLogger.resetIndent(0) //│ REPL> Collected: //│ > Unit {} -//│ REPL> Sending: block$res10 = undefined +//│ REPL> Sending: block$res9 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: let tmp2;try { tmp2 = runtime.checkCall(g(-3)); block$res10 = 3 + tmp2;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: let tmp2;try { tmp2 = runtime.checkCall(g(-3)); block$res9 = 3 + tmp2;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > CALL g(-3) //│ > | CALL g(-2) @@ -97,7 +97,7 @@ f(1,2)(-3) //│ REPL> Sending: runtime.TraceLogger.enabled = false //│ REPL> Collected: //│ > false -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res10));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res9));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 45 //│ > undefined @@ -108,15 +108,15 @@ f(1,2)(-3) :showRepl f(1,2)(-4) -//│ REPL> Sending: block$res11 = undefined +//│ REPL> Sending: block$res10 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: let tmp3;try { tmp3 = runtime.checkCall(g(-4)); block$res11 = 3 + tmp3;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: let tmp3;try { tmp3 = runtime.checkCall(g(-4)); block$res10 = 3 + tmp3;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ REPL> Parsed: //│ > undefined -//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res11));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: try { runtime.checkCall(runtime.printRaw(block$res10));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > 45 //│ > undefined diff --git a/hkmc2/shared/src/test/mlscript/newres/.mls b/hkmc2/shared/src/test/mlscript/newres/.mls index 411541472f..6525534110 100644 --- a/hkmc2/shared/src/test/mlscript/newres/.mls +++ b/hkmc2/shared/src/test/mlscript/newres/.mls @@ -1,2 +1,3 @@ :.. +:js #lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/ucs/.mls b/hkmc2/shared/src/test/mlscript/ucs/.mls index cc02430a79..411541472f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/.mls @@ -1,3 +1,2 @@ :.. -:!js #lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls index d50888862c..fda62c0ce0 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls @@ -95,7 +95,7 @@ if //│ tmp = 1 + element0$; //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ } -//│ block$res5 = tmp; +//│ block$res4 = tmp; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/ups/.mls b/hkmc2/shared/src/test/mlscript/ups/.mls index cc02430a79..411541472f 100644 --- a/hkmc2/shared/src/test/mlscript/ups/.mls +++ b/hkmc2/shared/src/test/mlscript/ups/.mls @@ -1,3 +1,2 @@ :.. -:!js #lang(strictResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/wasm/.mls b/hkmc2/shared/src/test/mlscript/wasm/.mls index ba01bcfae7..b1b8e2f06a 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/.mls @@ -1,5 +1,4 @@ :.. -:!js :wasm #lang(strictResolution: true) //│ Wasm result: From 996092bbccc30d50f4ea732ef330c44357188003 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Wed, 23 Sep 2026 16:06:12 +0800 Subject: [PATCH 035/228] Attach runtime symbols to synthetic helper selections --- .../src/main/scala/hkmc2/CompilerCtx.scala | 11 +++++++---- .../main/scala/hkmc2/codegen/Lowering.scala | 2 +- .../scala/hkmc2/semantics/Elaborator.scala | 13 +++++++++++-- .../src/main/scala/hkmc2/semantics/Term.scala | 4 ++++ .../shared/src/test/mlscript/basics/Assert.mls | 18 ++++++++++++++---- .../handlers/NonLocalLabelControlFlow.mls | 2 +- 6 files changed, 38 insertions(+), 12 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala index a4c830f151..590eb26a68 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala @@ -105,12 +105,15 @@ class CompilerCtx( case sym: BlockMemberSymbol => sym .toSet case _ => Set.empty - val (blk0, _) = elaborationConfig.givenIn(elab.importFrom(parsed)) - if file.toString === paths.runtimeSourceFile.toString then - state.initRuntimeSymbolsFromBlock(blk0) - else + val isRuntime = file.toString === paths.runtimeSourceFile.toString + // Elaboration synthesizes references to runtime definitions (e.g. assertFail). + // The runtime itself supplies these symbols once its own body is elaborated. + if !isRuntime then state.initRuntimeSymbolsFromFile(paths.runtimeSourceFile, prelude)( using tl, summon[Raise], artifactCtx) + val (blk0, _) = elaborationConfig.givenIn(elab.importFrom(parsed)) + if isRuntime then + state.initRuntimeSymbolsFromBlock(blk0) val artifactConfig = Config.extractConfigFromStats(blk0) if !artifactConfig.language.useNewResolution then artifactConfig.givenIn: diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 46091ca27b..2ad5ca4245 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -846,7 +846,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): N, ) val rewritten = st.App( - st.SynthSel(State.runtimeSymbol.ref(), Tree.Ident("toJsAsync"))(N, FlowSymbol.sel("toJsAsync"), N, N), + State.runtimeSymbol.ref().synthSel(State.toJsAsyncSymbol), st.Tup(PlainFld(st.Blk(td :: Nil, bms.ref(ident).resolved(dsym))) :: Nil)(Tree.DummyTup) )(Tree.DummyApp, N, FlowSymbol.app()) extractAnnots(rewritten, acc) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index a3ded17c62..de67e80698 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -475,6 +475,9 @@ object Elaborator: tuple: ModuleOrObjectSymbol, str: ModuleOrObjectSymbol, unreachable: TermSymbol, + assertFail: TermSymbol, + continue: ModuleOrObjectSymbol, + toJsAsync: TermSymbol, tupleGet: TermSymbol, tupleSlice: TermSymbol, tupleLazySlice: TermSymbol, @@ -523,6 +526,9 @@ object Elaborator: tuple = tuple, str = str, unreachable = term("unreachable"), + assertFail = term("assertFail"), + continue = modOrObj("Continue"), + toJsAsync = term("toJsAsync"), tupleGet = moduleMember(tuple, "get"), tupleSlice = moduleMember(tuple, "slice"), tupleLazySlice = moduleMember(tuple, "lazySlice"), @@ -627,6 +633,9 @@ object Elaborator: val id = new Ident("ret") BlockMemberSymbol(id.name, Nil, true) def unreachableSymbol: TermSymbol = runtimeSymbols.unreachable + def assertFailSymbol: TermSymbol = runtimeSymbols.assertFail + def continueSymbol: ModuleOrObjectSymbol = runtimeSymbols.continue + def toJsAsyncSymbol: TermSymbol = runtimeSymbols.toJsAsync def tupleGetSymbol: TermSymbol = runtimeSymbols.tupleGet def tupleSliceSymbol: TermSymbol = runtimeSymbols.tupleSlice def tupleLazySliceSymbol: TermSymbol = runtimeSymbols.tupleLazySlice @@ -778,7 +787,7 @@ extends Importer: /** Use a synthesized selection so the sentinel object can be referenced without field-access sanity checks. */ private def nonLocalContinueSentinel(using Ctx): Term = - State.runtimeSymbol.ref().selNoSym("Continue", synth = true) + State.runtimeSymbol.ref().synthSel(State.continueSymbol) /** Build an effect handler around `body` for non-local control flow. */ private def mkEffectHandleAbortive( @@ -1614,7 +1623,7 @@ extends Importer: (org.startLineNum + org.fph.getLineColAt(loc.spanStart)._1).toString) case N => ("‹unknown›", "‹unknown›") val elsPart = els.fold(PrefixApp(Keywrd(Keyword.`else`), Tree.Trm( - State.runtimeSymbol.ref().selNoSym("assertFail", synth = true) + State.runtimeSymbol.ref().synthSel(State.assertFailSymbol) .app(Term.Lit(StrLit(fl)), Term.Lit(StrLit(ln))) )))(PrefixApp.apply.tupled) subterm: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index b2d41dd339..70b7cf063b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -593,6 +593,10 @@ enum Term extends Statement, ShapePublisher: def sel(id: Tree.Ident, sym: Opt[MemberSymbol])(using State, Elaborator.Ctx): Sel = Sel(this, id)(sym, FlowSymbol.sel(id.name), N, S(summon)) + /** Select a known synthetic member without performing source-level lookup. */ + def synthSel(sym: MemberSymbol)(using State): SynthSel = + SynthSel(this, Tree.Ident(sym.nme))(S(sym), FlowSymbol.synthSel(sym.nme), N, N) + def selNoSym(nme: Str, synth: Bool = false)(using State, Elaborator.Ctx): Sel | SynthSel = val id = new Tree.Ident(nme) if synth diff --git a/hkmc2/shared/src/test/mlscript/basics/Assert.mls b/hkmc2/shared/src/test/mlscript/basics/Assert.mls index 82aabf0f80..63fc8288fc 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Assert.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Assert.mls @@ -5,18 +5,28 @@ let xs = [1, 2, 3] //│ xs = [1, 2, 3] +:sir :sjs assert true +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let scrut; +//│ set scrut = true; +//│ match scrut +//│ true => +//│ return runtime⁰.Unit⁰ +//│ else +//│ return runtime⁰.assertFail⁰("Assert.mls", "11") +//│ end //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ if (true === true) { //│ return runtime.Unit //│ } -//│ return runtime.safeCall(runtime.assertFail("Assert.mls", "10")); +//│ throw runtime.safeCall(globalThis.Error("Assertion failed (Assert.mls:11)")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :re assert false -//│ ═══[RUNTIME ERROR] Error: Assertion failed (Assert.mls:19) +//│ ═══[RUNTIME ERROR] Error: Assertion failed (Assert.mls:29) assert xs is Array @@ -29,9 +39,9 @@ assert(true); 123 //│ if (123 === true) { //│ return runtime.Unit //│ } -//│ return runtime.safeCall(runtime.assertFail("Assert.mls", "28")); +//│ throw runtime.safeCall(globalThis.Error("Assertion failed (Assert.mls:38)")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] Error: Assertion failed (Assert.mls:28) +//│ ═══[RUNTIME ERROR] Error: Assertion failed (Assert.mls:38) // * FIXME: the `else` is parsed as part of the `assert`! :sjs diff --git a/hkmc2/shared/src/test/mlscript/handlers/NonLocalLabelControlFlow.mls b/hkmc2/shared/src/test/mlscript/handlers/NonLocalLabelControlFlow.mls index 7858130ddb..70af738d81 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NonLocalLabelControlFlow.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NonLocalLabelControlFlow.mls @@ -71,7 +71,7 @@ continueFromNestedFunctionWithEffects() //│ let loop, handleBlock$, NonLocalLabelEffectbreak, NonLocalLabelEffectcontinue, NonLocalLabelEffect, Capture$scope0, handleBlock$$, NonLocalLabelEffectbreak$, tmp; //│ do runtime⁰.resetEffects﹖(); //│ define NonLocalLabelEffectcontinue as fun NonLocalLabelEffectcontinue⁰(resume) { -//│ return runtime⁰.Continue﹖ +//│ return runtime⁰.Continue⁰ //│ }; //│ @inline //│ define NonLocalLabelEffectbreak$ as fun NonLocalLabelEffectbreak$⁰(value)(resume) { From c511dd20691f5f210be040406de3ae8b79b0e1b8 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Wed, 23 Sep 2026 16:15:14 +0800 Subject: [PATCH 036/228] Misc --- doc/new-resolution-suite-migration.md | 5 ----- .../src/main/scala/hkmc2/semantics/Shape.scala | 1 + hkmc2/shared/src/test/mlscript/basics/Declare.mls | 4 ++-- .../src/test/mlscript/codegen/ImportConflicts.mls | 1 + .../src/test/mlscript/newres/BareClasses.mls | 6 +++--- .../src/test/mlscript/newres/ParamClasses.mls | 4 ++-- .../src/test/mlscript/newres/ReslScratch.mls | 15 +++++++++++++++ 7 files changed, 24 insertions(+), 12 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index e12ecacbe8..c30dbcfe72 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -194,11 +194,6 @@ errors when a selected interpretation lacks the required capability. ## Remaining implementation work, not new semantic decisions -- Port the old modulefulness, generic-arity, and implicit-argument checks to - completed shape/signature subscriptions. `basics/BadModuleUses.mls`, - `GenericClasses.mls`, `BadTypeClasses.mls`, and `codegen/NoModuleCheck.mls` - currently lose expected errors. Do not remove their expectations to turn the - migration green. - Implement deferred validation for calls, spread distribution, curried constructors, and returned function shapes. Distinguish an unknown-length spread from a proven arity mismatch; the remaining spread diagnostics still diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index a262216f1a..e793abbf70 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -305,6 +305,7 @@ class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTer // s"${defn.describe}" // s"${defn.bsym.asCls.fold("")(_.defn.get.kind.desc+" ")}'${defn.bsym.nme}'" s"${defn match + case defn: ClassDef => return defn.kind.desc + " value" case defn: TypeLikeDef => defn.kind.desc + " " case defn: TermDefinition => defn.tsym match diff --git a/hkmc2/shared/src/test/mlscript/basics/Declare.mls b/hkmc2/shared/src/test/mlscript/basics/Declare.mls index f7a3349f7e..68d65535c9 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Declare.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Declare.mls @@ -75,7 +75,7 @@ C.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.74: C.oops //│ ║ ^^^^^^ -//│ ╟── Class 'C' does not contain member 'oops' +//│ ╟── Class value does not contain member 'oops' //│ ║ l.38: declare class C //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -85,7 +85,7 @@ M.D.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.84: M.D.oops //│ ║ ^^^^^^^^ -//│ ╟── Class 'D' does not contain member 'oops' +//│ ╟── Class value does not contain member 'oops' //│ ║ l.48: declare class D //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportConflicts.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportConflicts.mls index e3cc903583..813e461d1e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportConflicts.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportConflicts.mls @@ -46,6 +46,7 @@ module Option5 with { fun Some5 = 5 } fun Some5 = "555" fun bar = open Option5 + // Note that the wildcard import does not shadow names that are already bound in scope Some5 bar //│ = "555" diff --git a/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls index eac7cfac84..f269a2ebf9 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls @@ -50,7 +50,7 @@ Foo.b //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.49: Foo.b //│ ║ ^^^^^ -//│ ╟── Class 'Foo' does not contain member 'b' +//│ ╟── Class value does not contain member 'b' //│ ║ l.30: class Foo { //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -63,7 +63,7 @@ f.b //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.62: f.b //│ ║ ^^^ -//│ ╟── Class 'Foo' does not contain member 'b' +//│ ╟── Class value does not contain member 'b' //│ ║ l.30: class Foo { //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -125,7 +125,7 @@ let f = Foo(1) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.124: let f = Foo(1) //│ ║ ^^^^^^ -//│ ╟── Class 'Foo' cannot receive more argument lists. +//│ ╟── Class value cannot receive more argument lists. //│ ║ l.114: class Foo { //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo6 cannot be invoked without 'new' diff --git a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls index 107fea674b..2ed9cc8c11 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls @@ -238,7 +238,7 @@ g(2) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.237: g(2) //│ ║ ^^^^ -//│ ╟── Instance of class 'Foo' cannot receive more argument lists. +//│ ╟── Instance of class value cannot receive more argument lists. //│ ║ l.233: let g = f(1) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] TypeError: g is not a function @@ -248,7 +248,7 @@ let f = new Foo() //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.247: let f = new Foo() //│ ║ ^^^^^^^^^ -//│ ╟── Class 'Foo' expected 1 argument, but got 0 +//│ ╟── Class value expected 1 argument, but got 0 //│ ║ l.84: class Foo(val a) { //│ ╙── ^^^ //│ f = Foo(undefined) diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 9fa1021230..fbef927636 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -181,3 +181,18 @@ fun foo(x) = //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// :de +:sr +{a: 1} +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ TODO[show:Rcd](Rcd(false,List(LetDecl(a,List()), DefineVar(a,Lit(IntLit(1))), RcdField(Lit(StrLit(a)),Ref(a))))) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = {a: 1} + +:fixme +{a: 1}.a +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + + + From 924110a4ea7e6a6cf02fab61f3a734d81849f404 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Wed, 23 Sep 2026 16:34:37 +0800 Subject: [PATCH 037/228] Complete semantic term and synthetic split printing --- .../main/scala/hkmc2/semantics/Split.scala | 11 ++++ .../src/main/scala/hkmc2/semantics/Term.scala | 59 ++++++++++++++++++- .../hkmc2/semantics/ucs/FlatPattern.scala | 11 ++++ .../src/test/mlscript/newres/ReslScratch.mls | 2 +- 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index c92da10b8a..9eb39494c1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -126,6 +126,17 @@ enum Split extends AutoLocated with ProductWithTail: (sym.body.freeSplitSyms ++ tail.freeSplitSyms) - sym case Split.UseSplit(sym) => sym.body.freeSplitSyms + sym + def show(using utils.Scope, ShowCfg, Raise): document.Document = + import document.*, Document.* + this match + case Cons(Branch(scrutinee, pattern, continuation), tail) => + doc"${scrutinee.show} is ${pattern.show} then { #{ # ${continuation.show} #} # } # ${tail.show}" + case Let(sym, term, tail) => doc"let ${sym.showName} = ${term.show}; # ${tail.show}" + case Else(default) => doc"else ${default.show}" + case End => doc"" + case LetSplit(sym, tail) => doc"let-split ${sym.showName} = { #{ # ${sym.body.show} #} # }; # ${tail.show}" + case UseSplit(sym) => doc"$${${sym.showName}}" + final def showDbg(using DebugPrinter): String = this match case Split.Cons(head, tail) => s"${head.showDbg}; ${tail.showDbg}" case Split.Let(name, term, tail) => s"let ${name} = ${term.showDbg}; ${tail.showDbg}" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 70b7cf063b..1b840730ca 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -1070,8 +1070,63 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: given IfLikeForm = form doc"${form.headStr} { #{ # ${split.show} #} # }" // val fs = fomr match - case _ => - doc"TODO[show:${getClass.getSimpleName}](${toString})" + case Missing => doc"‹missing›" + case TyApp(lhs, targs) => doc"${lhs.show}[${targs.map(_.show).mkDocument(", ")}]" + case DynSel(prefix, field, arrayIdx) => + if arrayIdx then doc"${prefix.show}.[${field.show}]" + else doc"${prefix.show}.(${field.show})" + case SelProj(prefix, cls, field) => doc"${prefix.show}.${cls.show}#${field.name}" + case Mut(underlying) => doc"mut ${underlying.show}" + case CtxTup(fields) => doc"using (${fields.map(_.show).mkDocument(", ")})" + case SynthIf(split) => doc"if { #{ # ${split.show} #} # }" + case SynthWhile(split) => doc"while { #{ # ${split.show} #} # }" + case FunTy(lhs, rhs, eff) => + doc"(${lhs.showAsParams} ->${eff.fold(doc"")(e => doc"{${e.show}}")} ${rhs.show})" + case Forall(tvs, outer, body) => + val variables = tvs.map: tv => + tv.sym.showName + :: tv.lb.fold(doc"")(b => doc" :> ${b.show}") + :: tv.ub.fold(doc"")(b => doc" <: ${b.show}") + doc"forall ${variables.mkDocument(", ")}${outer.fold(doc"")(v => doc", outer ${v.showName}")}: ${body.show}" + case Constrained(constraints, body) => + val bounds = constraints.map(c => doc"${c.lhs.show} ${c.dir.showDbg} ${c.rhs.show}") + doc"[${bounds.mkDocument(", ")}] => ${body.show}" + case WildcardTy(in, out) => + doc"in ${in.fold(doc"⊥")(_.show)} out ${out.fold(doc"⊤")(_.show)}" + case Rcd(mut, stats) => + (if mut then doc"mut " else doc"") :: braced: + doc" # " :: stats.map(_.show).mkDocument(doc", # ") + case RcdField(field, rhs) => doc"${field.show}: ${rhs.show}" + case RcdSpread(record) => doc"...${record.show}" + case Quoted(body) => doc"""code"${body.show}"""" + case Unquoted(body) => doc"$${${body.show}}" + case DynNew(cls, args) => doc"new! ${cls.show}${args.map(_.showAsParams).mkDocument()}" + case Asc(term, ty) => doc"(${term.show}: ${ty.show})" + case CompType(lhs, rhs, pol) => doc"(${lhs.show} ${if pol then "|" else "&"} ${rhs.show})" + case Neg(rhs) => doc"~(${rhs.show})" + case Region(name, body) => doc"region ${name.showName} in ${body.show}" + case RegRef(reg, value) => doc"(${reg.show}).ref ${value.show}" + case Assgn(lhs, rhs) => doc"${lhs.show} := ${rhs.show}" + case SetRef(ref, value) => doc"${ref.show} := ${value.show}" + case Drop(term) => doc"drop ${term.show}" + case Deref(ref) => doc"!(${ref.show})" + case Ret(result) => doc"return ${result.show}" + case Throw(result) => doc"throw ${result.show}" + case Label(label, _, body, _) => doc"do ${label.showName}: ${body.show}" + case Break(label, _, value) => doc"${label.showName}.break${value.fold(doc"")(v => doc" ${v.show}")}" + case Continue(label) => doc"${label.showName}.continue" + case Try(body, finallyDo) => doc"try ${body.show} finally ${finallyDo.show}" + case Annotated(annot, target) => doc"${annot.show} ${target.show}" + case Handle(lhs, rhs, args, _, defs, body) => + doc"handle ${lhs.showName} = ${rhs.show}${args.map(_.showAsParams).mkDocument()} with { #{ # ${ + defs.map(_.td.show).mkDocument(doc" # ")} #} # } in ${body.show}" + case td: TypeDef => + td.annotations.map(_.show :: " ").mkDocument() + :: doc"type ${td.bsym.showName}::${td.sym.showName}" + :: (if td.tparams.isEmpty then doc"" + else doc"[${td.tparams.map(_.sym.showName).mkDocument(", ")}]") + :: td.rhs.fold(doc"")(rhs => doc" = ${rhs.show}") + case SetConfig(_) => doc"#config(...)" this match case t: Resolvable => t.expansion match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala index 1f3df539d0..ea17c2efb0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -57,6 +57,17 @@ enum FlatPattern extends AutoLocated: case (nme, als) => Vector(nme, als) .toVector + def show(using utils.Scope, ShowCfg, Raise): document.Document = + import document.*, Document.* + this match + case Lit(literal) => doc"${literal.idStr}" + case ClassLike(ctor, _, args, refined) => + (if refined then doc"refined " else doc"") :: ctor.show :: + args.fold(doc"")(as => doc"(${as.map(_._1.showName).mkDocument(", ")})") + case Tuple(size, inf) => doc"[]${if inf then ">=" else "="}$size" + case Record(entries) => + doc"{${entries.map((id, sym) => doc"${id.name}: ${sym.showName}").mkDocument(", ")}}" + def showDbg(using DebugPrinter): Str = (this match case Lit(literal) => literal.idStr diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index fbef927636..39e869a0c5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -186,7 +186,7 @@ fun foo(x) = :sr {a: 1} //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ TODO[show:Rcd](Rcd(false,List(LetDecl(a,List()), DefineVar(a,Lit(IntLit(1))), RcdField(Lit(StrLit(a)),Ref(a))))) +//│ { let a³, a³ = 1, "a": a³ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = {a: 1} From c5dec62384e553557c9c65e954c7ec13ff3505d5 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Wed, 23 Sep 2026 16:37:52 +0800 Subject: [PATCH 038/228] Verify deferred calls and narrow the remaining migration work --- doc/new-resolution-suite-migration.md | 17 ++++++-- .../src/main/scala/hkmc2/semantics/Term.scala | 1 + .../mlscript/newres/ClonedConstructors.mls | 2 +- .../test/mlscript/newres/DeferredCalls.mls | 39 +++++++++++++++++++ .../mlscript/newres/DynamicInstantiation.mls | 2 +- 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/DeferredCalls.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index c30dbcfe72..ed8ed5fbcb 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -194,10 +194,19 @@ errors when a selected interpretation lacks the required capability. ## Remaining implementation work, not new semantic decisions -- Implement deferred validation for calls, spread distribution, curried - constructors, and returned function shapes. Distinguish an unknown-length - spread from a proven arity mismatch; the remaining spread diagnostics still - expose this gap. +- Complete argument-spread distribution in the existing deferred call resolver. + Callee validation already runs from shape listeners; saturated calls subscribe + to their bodies for returned shapes, and curried constructors retain their + remaining parameter lists. `newres/DeferredCalls.mls` covers forward calls, + delayed arity errors, returned lambdas, and curried constructor field access. + The concrete gap is `zipArgs`: it counts syntactic spread nodes as single + arguments and reports an arity mismatch when a spread meets a fixed parameter. + Expand known tuple spreads through listeners, preserve capture marks on each + argument segment (including residual rest arguments), and distinguish an + unknown-length spread from a proven arity mismatch. Test nested and delayed + spreads, fixed arguments following spreads, rest parameters, and spreads in + each list of a curried call. Further constructor/returned-function fixes should + be driven by specific failures, rather than replacing the existing deferral. - Audit assignments to member symbols and definition initializers. The trial reaches unimplemented direct member-reference shape cases and missing assignment lowering in several mutation tests. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 1b840730ca..61be2b9a5e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -840,6 +840,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case Quoted(term) => "quoted term" case Unquoted(term) => "unquoted term" case New(cls, args, rft) => "object instantiation" + case DynNew(cls, args) => "dynamic object instantiation" case SelProj(pre, cls, proj) => "field selection" case Asc(term, ty) => "type ascription" case CompType(lhs, rhs, pol) => if pol then "alternation" else "composition" diff --git a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls index ac9c16db6b..4a27d6e44a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls @@ -1,4 +1,4 @@ -:js +:. open annotations diff --git a/hkmc2/shared/src/test/mlscript/newres/DeferredCalls.mls b/hkmc2/shared/src/test/mlscript/newres/DeferredCalls.mls new file mode 100644 index 0000000000..50552d70f7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/DeferredCalls.mls @@ -0,0 +1,39 @@ +:. + +// The callee's definition becomes available after the call is elaborated. +:expect Box(1) +class Box(val value) +fun forward(x) = later(x) +fun later(x) = Box(x) +forward(1) +//│ = Box(1) + +// A returned lambda must publish its result shape to the next application. +:expect 2 +fun makeBox() = x => Box(x) +makeBox()(2).value +//│ = 2 + +// Curried constructor applications retain both parameter lists and field shapes. +:expect [3, 4, 5, 6] +class Pair(val left)(val right) +fun buildPair(x)(y) = Pair(x)(y) +let partial = buildPair(3) +let built = partial(4) +let direct = new Pair(5)(6) +[built.left, built.right, direct.left, direct.right] +//│ = [3, 4, 5, 6] +//│ built = Pair(3) +//│ direct = Pair(5) +//│ partial = fun + +// Validation waits for a forward callee, but still diagnoses a proven mismatch. +:e +fun bad(x) = laterArity(x) +fun laterArity(x, y) = x +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.32: fun bad(x) = laterArity(x) +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Function 'laterArity' expected 2 arguments, but got 1 +//│ ║ l.33: fun laterArity(x, y) = x +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/DynamicInstantiation.mls b/hkmc2/shared/src/test/mlscript/newres/DynamicInstantiation.mls index 70d8d68e6e..9c5eb33627 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DynamicInstantiation.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DynamicInstantiation.mls @@ -1,4 +1,4 @@ -:js +:. class First class Second From 6c984dc52d69e1225cb3aa8410f9b5cb59faf793 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 17:21:42 +0800 Subject: [PATCH 039/228] Resolve tuple spreads through deferred shape candidates --- doc/new-resolution-suite-migration.md | 37 +++-- .../scala/hkmc2/semantics/NewResolver.scala | 139 +++++++++------- .../main/scala/hkmc2/semantics/Shape.scala | 52 +++++- .../src/test/mlscript/basics/LazySpreads.mls | 22 ++- .../src/test/mlscript/codegen/BadSpreads.mls | 13 +- .../test/mlscript/newres/CallSiteShapes.mls | 17 ++ .../src/test/mlscript/newres/Functions.mls | 12 +- .../src/test/mlscript/newres/SpreadCalls.mls | 152 ++++++++++++++++++ 8 files changed, 357 insertions(+), 87 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index ed8ed5fbcb..c8b2742e5f 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -73,10 +73,10 @@ explicitly bound `Some5` wins over a wildcard open, yielding `"555"` rather than ### 1. Structural members and opaque values Evidence: `basics/LiteralSelection.mls` fails on `arr.1` and `obj.a` because -`IntroShape.getMemberImpl` has unimplemented tuple and record cases. The current -`MemberInfo` requires a `BlockMemberSymbol`, which a structural field need not -have. `codegen/ImportJSModule.mls`, `Pwd.mls`, and many app worksheets instead -have opaque external results with no member shapes at all. +tuple and record member lookup remains unimplemented in `TupleShape` and +`IntroShape`. The current `MemberInfo` requires a `BlockMemberSymbol`, which a +structural field need not have. `codegen/ImportJSModule.mls`, `Pwd.mls`, and many +app worksheets instead have opaque external results with no member shapes at all. **Question:** should ordinary member selection on an opaque external value be accepted dynamically, or require a declaration/explicit dynamic selection? @@ -194,19 +194,22 @@ errors when a selected interpretation lacks the required capability. ## Remaining implementation work, not new semantic decisions -- Complete argument-spread distribution in the existing deferred call resolver. - Callee validation already runs from shape listeners; saturated calls subscribe - to their bodies for returned shapes, and curried constructors retain their - remaining parameter lists. `newres/DeferredCalls.mls` covers forward calls, - delayed arity errors, returned lambdas, and curried constructor field access. - The concrete gap is `zipArgs`: it counts syntactic spread nodes as single - arguments and reports an arity mismatch when a spread meets a fixed parameter. - Expand known tuple spreads through listeners, preserve capture marks on each - argument segment (including residual rest arguments), and distinguish an - unknown-length spread from a proven arity mismatch. Test nested and delayed - spreads, fixed arguments following spreads, rest parameters, and spreads in - each list of a curried call. Further constructor/returned-function fixes should - be driven by specific failures, rather than replacing the existing deferral. +- Argument-spread distribution now uses `TupleShape` candidates with selected + subshapes for each spread. Tuple producers listen for all combinations; `zipArgs` + checks their expanded counts and preserves capture marks for fixed arguments and + residual rest tuples. Marks accumulate on each segment and are applied when a + consumer accesses its fields, as with member selection. + `newres/SpreadCalls.mls` covers nested and delayed spreads, multiple alternatives, + fixed arguments after spreads, rest forwarding, captures, curried calls and + constructors, and recursive forwarding. Repeated producers in the same spread + context widen that spread to an explicit unknown-length tuple shape, preserving + known surrounding fields; `basics/LazySpreads.mls` covers recursive lazy tuples. + Unknown lengths get a distinct diagnostic instead of a fabricated arity mismatch. + Functions can consume a known prefix and forward an unknown-length remainder to + a rest parameter. The broader call-site correlation limitation remains tracked + in `newres/CallSiteShapes.mls`, including a rest-forwarding case. Further + constructor/returned-function fixes should be driven by specific failures, + rather than replacing existing deferral. - Audit assignments to member symbols and definition initializers. The trial reaches unimplemented direct member-reference shape cases and missing assignment lowering in several mutation tests. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 5c690b3494..d2de8c15f8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -46,6 +46,8 @@ class NewResolver: val newShapes: mutable.Map[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = mutable.Map.empty val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty + // One producer per tuple syntax node, installed before following its spreads. + private val tupleShapes: mutable.Map[Tup, mutable.LinkedHashSet[TermShape]] = mutable.Map.empty private val selfShapes: mutable.Map[InnerSymbol, BaseShape] = mutable.Map.empty val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty @@ -119,49 +121,52 @@ class NewResolver: def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])]): Unit = raise: ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) - def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Ls[Elem], src: Term, funSh: TermShape, argumentMarks: Ls[Marks]): Unit = + /** Subscribe once to complete argument-tuple candidates. A pending spread is not + * an argument, nor evidence of an arity mismatch; each resolved combination is. + */ + def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Term, src: Term, funSh: TermShape): Unit = val expectedCount = ps.length - val providedCount = args.length - // Keep the original arity for diagnostics while consuming matched arguments. - def loop(ps: Ls[Param], args: Ls[Elem]): Unit = - (ps, args) match - case (Nil, Nil) if r.isEmpty => () - // case (Nil, Spd(k, t) :: args) => - // zip(Nil, r, args) - case (Nil, args) => - r match - case N => - resolError(src, - msg"${funSh.describe.capitalize} expected ${expectedCount} ${ - "argument".pluralized(expectedCount)}, but got ${providedCount}" -> funSh.toLoc :: Nil) - case S(p) => - val packaged = new Tup(args)(Tree.DummyTup).withLoc(Loc.mk(args.iterator.flatMap(_.toLoc))) - IntroShape(packaged).exit(argumentMarks).enter(mss) match - case NoShape => () - case sh: TermShape => - if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then p.sym.shapeListeners.foreach(_(sh)) - case (p :: ps, Fld(fls, trm, asc) :: args) if p.sign.nonEmpty => - loop(ps, args) - case (p :: ps, Fld(fls, trm, asc) :: args) => - // case ((p, mss) :: ps, Fld(fls, trm, asc) :: args) => - listenTerm(trm): sh0 => - // val sh = sh0.enter(mss) - // log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg}") - // if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then - // p.sym.shapeListeners.foreach(listener => listener(sh)) - sh0.exit(argumentMarks).enter(mss) match - case NoShape => - case sh: TermShape => - log(s"zipArg: p = ${p.showDbg}, trm = ${trm.showDbg}, sh = ${sh.shwDbg} ${isOwnedSym(p.sym)}, ${p.sym.shapes.contains(sh)}") - if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then - // log(s"!!!! ${p.sym.shapeListeners}") - p.sym.shapeListeners.foreach(listener => listener(sh)) - loop(ps, args) - case _ => - resolError(src, - msg"${funSh.describe.capitalize} expected ${expectedCount} ${ - "argument".pluralized(expectedCount)}, but got ${providedCount}" -> funSh.toLoc :: Nil) - loop(ps, args) + val reportedCounts = mutable.Set.empty[Int] + var reportedUnknownLength = false + def reportUnknownLength(): Unit = if !reportedUnknownLength then + reportedUnknownLength = true + resolError(src, msg"Cannot determine the length of the argument tuple." -> args.toLoc :: Nil) + def publish(p: Param, shape: TermShape | NoShape): Unit = shape match + case NoShape => () + case sh: TermShape => + if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then p.sym.shapeListeners.foreach(_(sh)) + listenTerm(args): + case Marked(tuple: TupleShape, marks) => + val segments = tuple.segments + val fields = segments.collect { case field: TupleShape.Field => field } + val unknownLength = fields.length != segments.length + val providedCount = fields.length + val arityMismatch = (!unknownLength && providedCount < expectedCount) || (r.isEmpty && providedCount > expectedCount) + if arityMismatch then + if reportedCounts.add(providedCount) then + val count = if unknownLength then msg"at least ${providedCount}" else msg"${providedCount}" + resolError(src, + msg"${funSh.describe.capitalize} expected ${expectedCount} ${ + "argument".pluralized(expectedCount)}, but got ${count}" -> funSh.toLoc :: Nil) + // Positions before the first unknown segment are definite. A rest parameter + // can receive the remaining structure without knowing its total length. + val prefix = segments.takeWhile(_.isInstanceOf[TupleShape.Field]).collect: + case field: TupleShape.Field => field + ps.lazyZip(prefix).foreach: (p, arg) => + if p.sign.isEmpty then listenTerm(arg.field.term): sh => + publish(p, sh.exit(arg.marks).exit(marks).enter(mss)) + if unknownLength && !arityMismatch && (prefix.length < expectedCount || r.isEmpty) then + reportUnknownLength() + r.filter(_ => prefix.length >= expectedCount).foreach: p => + // A residual tuple can mix fields from several captured spreads and the + // caller. Keep their individual marks when forwarding it to another call. + val rest = if ps.isEmpty then tuple + else TupleShape(tuple.source, TupleShape.Rest(tuple, expectedCount) :: Nil) + publish(p, rest.exit(marks).enter(mss)) + case sh @ Marked(_: UnknownTupleShape, _) => + if ps.isEmpty && r.nonEmpty then r.foreach(p => publish(p, sh.enter(mss))) + else reportUnknownLength() + case _ => resolError(src, msg"Expected an argument tuple." -> args.toLoc :: Nil) /** Resolve every constructor pattern through the same symbolic interpretation. * A class overload takes precedence over its term companion in this context. @@ -287,18 +292,7 @@ class NewResolver: lhs.unappliedParams match case Nil => () case (ps, mss) :: pss => - args match - case args: Tup => - log(s"Zipping ${ps} (${mss.map(_.showDbg)}) with ${args.fields}") - zipArgs(mss, ps.params, ps.restParam, args.fields, res, lhs, Nil) - case _ => - // Member projections forward their rest-argument tuple. Its shape can - // arrive after the method, and carries the caller's capture marks. - listenTerm(args): - case Marked(sh: IntroShape, marks) => sh.trm match - case tuple: Tup => zipArgs(mss, ps.params, ps.restParam, tuple.fields, res, lhs, marks :: Nil) - case _ => resolError(res, msg"Expected an argument tuple." -> args.toLoc :: Nil) - case _ => resolError(res, msg"Expected an argument tuple." -> args.toLoc :: Nil) + zipArgs(mss, ps.params, ps.restParam, args, res, lhs) log(s"appShape isSaturated? ${sh.isSaturated}; head? ${sh.applicationHead}") def register = if res.shapes.add(sh) then res.shapeListeners.foreach(listener => listener(sh)) @@ -441,9 +435,7 @@ class NewResolver: val sh = newShapes.getOrElseUpdate((cd.sym, ss.markss, nw.resSym), { dsh.unappliedParams.lazyZip(nw.args).foreach: case ((ps, mss), args) => - args match - case args: Tup => zipArgs(mss, ps.params, ps.restParam, args.fields, nw, dsh, Nil) - case _ => ??? + zipArgs(mss, ps.params, ps.restParam, args, nw, dsh) NewShape(dsh, cd.sym, ss.markss, nw.args, nw) }) if nw.shapes.add(sh) then nw.shapeListeners.foreach(_(sh)) @@ -601,6 +593,39 @@ class NewResolver: lastWords("Synthetic selections must not enter new resolution") case TyApp(underlying, _) => listen(underlying, discardMarks)(listener) case Mut(underlying) => listenTerm(underlying)(listener) + case tuple: Tup => + tupleShapes.get(tuple) match + case S(shapes) => shapes.toList.foreach(listener) + case N => + val shapes = mutable.LinkedHashSet.empty[TermShape] + // Install before subscribing: recursive references must reuse this producer. + tupleShapes(tuple) = shapes + def publish(shape: TermShape): Unit = + if shapes.add(shape) then tuple.shapeListeners.foreach(_(shape)) + def expand(elems: Ls[Elem], reversed: Ls[TupleShape.Element]): Unit = elems match + case Nil => + // A sole spread preserves its operand's shape and context exactly. + // Besides avoiding wrappers, this lets recursive rest forwarding + // reach the same fixed point as forwarding an ordinary parameter. + val shape = reversed match + case TupleShape.Spread(shape, NoMarks) :: Nil => shape + case TupleShape.Spread(shape, marks: SomeMarks) :: Nil => MarkedShape(shape, marks) + case _ => TupleShape(tuple, reversed.reverse) + publish(shape) + case (field: Fld) :: rest => expand(rest, TupleShape.Field(field, Nil) :: reversed) + case Spd(_, term) :: rest => + val seen = mutable.Set.empty[TermShape] + listenTerm(term): sh => + if seen.add(sh) then sh match + case Marked(shape: TupleShape, marks) => + val spread: TupleShape | UnknownTupleShape = + if shape.containsSpread(shape.source, marks) then UnknownTupleShape(shape.source) else shape + expand(rest, TupleShape.Spread(spread, marks) :: reversed) + case Marked(shape: UnknownTupleShape, marks) => + expand(rest, TupleShape.Spread(shape, marks) :: reversed) + case _ => + resolError(tuple, msg"Cannot determine the length of this spread." -> term.toLoc :: Nil) + expand(tuple.fields, Nil) case intro: IntroTerm => val sh = introShapes.getOrElseUpdate(intro, { log(s"introShape: intro = $intro") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index e793abbf70..ee7f9cb28a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -20,6 +20,8 @@ sealed trait Shape extends ShapeLike: case ns: SymShape => s"SymShape(${ns.sym.showDbg})" case ns: NewShape => s"NewShape(${ns.cls.showDbg}, ${ns.argss.map(_.showDbg).mkString(", ")})" case is: IntroShape => s"IntroShape(${is.trm.showDbg})" + case ts: UnknownTupleShape => s"UnknownTupleShape(${ts.source.showDbg})" + case ts: TupleShape => s"TupleShape(${ts.source.showDbg})" case bs: BaseShape => s"BaseShape(${bs.defn.sym.showDbg})" case es: ErrShape => es.describe @@ -330,14 +332,56 @@ class RefinedShape(val base: TermShape, val refinements: Ls[Str -> Term]) extend // nme -> BlockMemberSymbol(nme, trm) ??? */ -type IntroTerm = Term.Lit | Term.UnitVal | Term.Tup | Term.Lam | Term.Rcd //| Term.New +/** A tuple candidate selects a value shape for each spread. Ordinary fields stay + * lazy. Recursive spreads can have unknown length; retain the known fields around + * them instead of discarding either those fields or the unresolved possibilities. + */ +final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) extends NonAppTermShape: + lazy val segments: Ls[TupleShape.Segment] = elements.flatMap: + case field: TupleShape.Field => field :: Nil + case TupleShape.Rest(shape, count) => shape.segments.drop(count) + case TupleShape.Spread(shape: UnknownTupleShape, marks) => TupleShape.Unknown(shape, marks :: Nil) :: Nil + case TupleShape.Spread(shape: TupleShape, marks) => shape.segments.map: + case TupleShape.Field(field, inner) => TupleShape.Field(field, inner ::: marks :: Nil) + case TupleShape.Unknown(shape, inner) => TupleShape.Unknown(shape, inner ::: marks :: Nil) + /** Re-entering the same producer through the same spread context can generate + * arbitrarily many tuple lengths. Widen that spread, retaining surrounding fields. + */ + def containsSpread(source: Term, marks: Marks): Bool = elements.exists: + case _: TupleShape.Field => false + case TupleShape.Rest(shape, _) => shape.containsSpread(source, marks) + case TupleShape.Spread(shape: TupleShape, inner) => + ((shape.source is source) && inner == marks) || shape.containsSpread(source, marks) + case TupleShape.Spread(_: UnknownTupleShape, _) => false + def describe: Str = "tuple literal" + def toLoc: Opt[Loc] = source.toLoc + protected def getMemberImpl(name: Str): Opt[MemberInfo] = ??? // Structural tuple members are not implemented yet. + +/** A recursive producer can denote unbounded tuple lengths. This shape represents + * all remaining lengths and element values, rather than dropping their candidates. + */ +final case class UnknownTupleShape(source: Term) extends NonAppTermShape: + def describe: Str = "tuple of unknown length" + def toLoc: Opt[Loc] = source.toLoc + protected def getMemberImpl(name: Str): Opt[MemberInfo] = ??? // Structural tuple members are not implemented yet. + +object TupleShape: + sealed trait Element + sealed trait Segment + final case class Field(field: Fld, marks: Ls[Marks]) extends Element, Segment + final case class Unknown(shape: UnknownTupleShape, marks: Ls[Marks]) extends Segment + final case class Spread(shape: TupleShape | UnknownTupleShape, marks: Marks) extends Element + // Keep a view of the original candidate so slicing preserves spread provenance. + final case class Rest(shape: TupleShape, count: Int) extends Element: + require(count >= 0 && shape.segments.take(count).size == count) + require(shape.segments.take(count).forall(_.isInstanceOf[Field])) + + +type IntroTerm = Term.Lit | Term.UnitVal | Term.Lam | Term.Rcd //| Term.New class IntroShape(val trm: IntroTerm) extends NonAppTermShape: def describe: Str = trm.describe protected def getMemberImpl(name: Str): Opt[MemberInfo] = trm match case _: Term.Lit | _: Term.UnitVal => N // TODO: methods on literals - case tup: Term.Tup => - // tup.fields.iterator.map: - ??? case lam: Term.Lam => N // TODO: methods on lambdas case rcd: Term.Rcd => // rcd.stats.iterator.collect: diff --git a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls index 08ab088c96..1d6015100f 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls @@ -87,7 +87,27 @@ sum2(..arr) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.86: sum2(..arr) //│ ║ ^^^^^^^^^^^ -//│ ╟── Function literal expected 1 argument, but got 1 +//│ ╟── Function literal expected 1 argument, but got 0 +//│ ║ l.47: fun sum2 = case +//│ ║ ^^^^ +//│ ║ l.48: [] then 0 +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.49: [x, ..xs, y] then x + y + sum2(xs) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.86: sum2(..arr) +//│ ║ ^^^^^^^^^^^ +//│ ╟── Function literal expected 1 argument, but got 2 +//│ ║ l.47: fun sum2 = case +//│ ║ ^^^^ +//│ ║ l.48: [] then 0 +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.49: [x, ..xs, y] then x + y + sum2(xs) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.86: sum2(..arr) +//│ ║ ^^^^^^^^^^^ +//│ ╟── Function literal expected 1 argument, but got 4 //│ ║ l.47: fun sum2 = case //│ ║ ^^^^ //│ ║ l.48: [] then 0 diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls b/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls index a7cca186f9..b96d12774c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls @@ -34,8 +34,13 @@ fun foo(a, ...b, c) = [a, ...b] //│ ║ l.32: fun foo(a, ...b, c) = [a, ...b] //│ ╙── ^^^^ +:e :re foo(1, 2, 3) +//│ ╔══[COMPILATION ERROR] Resolution error in tuple literal +//│ ║ l.32: fun foo(a, ...b, c) = [a, ...b] +//│ ║ ^^^^^^^^^ +//│ ╙── Cannot determine the length of this spread. //│ ═══[RUNTIME ERROR] TypeError: 2 is not iterable @@ -45,10 +50,10 @@ fun foo(a, ...b) = [a, ...b] :re foo() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.46: foo() +//│ ║ l.51: foo() //│ ║ ^^^^^ //│ ╟── Function 'foo' expected 1 argument, but got 0 -//│ ║ l.42: fun foo(a, ...b) = [a, ...b] +//│ ║ l.47: fun foo(a, ...b) = [a, ...b] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'foo' expected at least 1 argument but got 0 @@ -56,13 +61,13 @@ foo() :e fun foo(1) = 2 //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found integer literal -//│ ║ l.57: fun foo(1) = 2 +//│ ║ l.62: fun foo(1) = 2 //│ ╙── ^ :e fun foo(...1) = 2 //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found spread -//│ ║ l.63: fun foo(...1) = 2 +//│ ║ l.68: fun foo(...1) = 2 //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls index 7bea6b5e20..23fb4f50fc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls @@ -40,3 +40,20 @@ apply(id, new A).get() + apply(id, new B).get() //│ ═══[RUNTIME ERROR] Expected: '40', got: 'undefined' //│ apply = fun //│ id = Id + +// Rest-argument forwarding has the same call-site correlation limitation. +:fixme +:expect 40 +fun restIdentity(ignored, ...args) = identity(...args) +restIdentity(0, new A).get() + restIdentity(0, new B).get() +//│ ╔══[COMPILATION ERROR] This selection of member 'get' is ambiguous, as it has multiple resolved targets +//│ ║ l.48: restIdentity(0, new A).get() + restIdentity(0, new B).get() +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── target: function 'get' in class 'B' +//│ ║ l.7: class B { fun get() = 23 } +//│ ║ ^^^ +//│ ╟── target: function 'get' in class 'A' +//│ ║ l.6: class A { fun get() = 17 } +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '40', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 660e519b6d..ff68748507 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -104,18 +104,20 @@ callid1(Predef) //│ | = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | PUSH MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | appShape: lhs = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ), args = [member:Predef⁰], res = member:callid1⁰(member:Predef⁰) -//│ | Zipping ParamList(‹›,List(Param(‹›,p,None,Modulefulness(None))),None) (List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ)) with List(Fld(‹›,MemberRef(member:Predef),None)) +//│ | listenTerm: trm = [member:Predef⁰] +//│ | listen: trm = [member:Predef⁰] //│ | listenTerm: trm = member:Predef⁰ //│ | listen: trm = member:Predef⁰ //│ | listenBMS: bms = module //│ | listenedBMS: bms = module //│ | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() //│ | PUSH module:Predef¹ +//│ | .exit module:Predef¹ (ϵ) +//│ | = module:Predef¹ //│ | .enter module:Predef¹ (↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | | .enter MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) //│ | | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | zipArg: p = p⁰, trm = member:Predef⁰, sh = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) true, false //│ | newSel: sel = p⁰.id, shape = MarkedShape(module:Predef¹, ↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | newSel member: bms = member:id⁰, mss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | listenBMS: bms = function @@ -141,10 +143,13 @@ callid1(Predef) //│ | = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | PUSH MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | appShape: lhs = MarkedShape(member:id⁰, ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ), args = [1], res = p⁰.id(1) -//│ | Zipping ParamList(‹›,List(Param(‹›,x,None,Modulefulness(None))),None) (List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ)) with List(Fld(‹›,Lit(IntLit(1)),None)) +//│ | listenTerm: trm = [1] +//│ | listen: trm = [1] //│ | listenTerm: trm = 1 //│ | listen: trm = 1 //│ | introShape: intro = Lit(IntLit(1)) +//│ | .exit IntroShape(1) (ϵ) +//│ | = IntroShape(1) //│ | .enter IntroShape(1) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | .exit MarkedShape (IntroShape(1), term:callid1², callid1¹) //│ | | | class hkmc2.semantics.TermSymbol None @@ -154,7 +159,6 @@ callid1(Predef) //│ | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) -//│ | zipArg: p = x⁰, trm = 1, sh = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) false, false //│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(term:Predef/id,Some(⋅id),NoMarks)))) //│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) //│ | appShape: td.body = Some(Ref(x)) diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls new file mode 100644 index 0000000000..a6062a43f8 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -0,0 +1,152 @@ +:. + +// Multiple spreads, nested spreads, empty spreads, and fixed arguments after them. +:expect [1, 2, 3, 4] +fun four(a, b, c, d) = [a, b, c, d] +four(...[], ...[1, ...[2]], 3, ...[4], ...[]) +//│ = [1, 2, 3, 4] + +// Both spread producers become available after the caller is elaborated. +:expect [5, 6, 7, 8] +fun delayed() = four(...left(), ...right()) +fun left() = [5, 6] +fun right() = [7, 8] +delayed() +//│ = [5, 6, 7, 8] + +// Returned tuples preserve captured values through nested spreads and rest forwarding. +:expect [11, 12, 21, 22] +class Box(val value) +fun pair(x, y) = [x, ...[y]] +fun second(a, b) = b +fun forward(head, ...tail) = second(...tail) +fun captured(x, y) = () => forward(0, ...pair(x, y)) +let first = captured(Box(10), Box(11)) +let next = captured(Box(20), Box(21)) +[first().value, captured(Box(0), Box(12))().value, next().value, captured(Box(0), Box(22))().value] +//│ = [11, 12, 21, 22] +//│ first = fun +//│ next = fun + +// Residual fields from distinct spreads and direct arguments keep separate marks. +:expect 32 +fun mixed(x, y) = forward(...[0, x], y) +mixed(Box(31), Box(32)).value +//│ = 32 + +// An empty rest tuple can itself be spread into a zero-argument function. +:expect 40 +fun zero() = 40 +fun empty(head, ...tail) = zero(...tail) +empty(0) +//│ = 40 + +// Each list of a curried function or constructor is checked independently. +:expect [51, 52, 53, 54] +class Pair(val left)(val right) +fun curried(x)(y) = Pair(x)(y) +let built = curried(...[Box(51)])(...[Box(52)]) +let direct = new Pair(...[Box(53)])(...[Box(54)]) +[built.left.value, built.right.value, direct.left.value, direct.right.value] +//│ = [51, 52, 53, 54] +//│ built = Pair(Box(51)) +//│ direct = Pair(Box(53)) + +// Plain tuple fields need no shapes for the tuple's arity to be known. +:expect 60 +fun typed(x: Int) = second(...[x, 60]) +typed(0) +//│ = 60 + +// A delayed concrete spread establishes a real mismatch, including nested fields. +:e +fun tooShort() = four(...short()) +fun short() = [1, ...[2]] +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.63: fun tooShort() = four(...short()) +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Function 'four' expected 4 arguments, but got 2 +//│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:e +four(...[1, 2], ...[3, 4], 5) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.73: four(...[1, 2], ...[3, 4], 5) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'four' expected 4 arguments, but got 5 +//│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] Error: Function 'four' expected 4 arguments but got 5 + +// An unknown-length value is not reported as a proven argument count. +:e +four(...Box(1)) +//│ ╔══[COMPILATION ERROR] Resolution error in tuple literal +//│ ║ l.84: four(...Box(1)) +//│ ║ ^^^^^^^^^^^ +//│ ╟── Cannot determine the length of this spread. +//│ ║ l.84: four(...Box(1)) +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function + +// Multiple alternatives for each spread are retained as their shapes arrive. +:expect [[1, 2, 3, 4], [5, 6, 7, 8]] +fun combine(xs, ys) = four(...xs, ...ys) +[combine([1, 2], [3, 4]), combine([5, 6], [7, 8])] +//│ = [[1, 2, 3, 4], [5, 6, 7, 8]] + +// Direct tuple returns preserve the field shapes of distinct call sites. +:expect [71, 72] +class Left(val l) +class Right(val r) +[second(...pair(0, Left(71))).l, second(...pair(0, Right(72))).r] +//│ = [71, 72] + +// Returning a rest tuple preserves its caller's capture marks. +:expect [81, 82] +fun packed(...xs) = xs +[second(...packed(0, Left(81))).l, second(...packed(0, Right(82))).r] +//│ = [81, 82] + +// Nested applications of the same tuple producer remain distinct. +:expect [0, 0, 1] +fun nested(xs) = [0, ...xs] +nested(nested([1])) +//│ = [0, 0, 1] + +// Recursive rest forwarding should reach a fixed point. +fun cycle(...xs) = cycle(...xs) +fun seed() = cycle(1) + +// Slicing a rest tuple retains both known fields and approximated spread segments. +:expect [0, 0, 1] +fun nestedRest(ignored, ...xs) = [0, ...xs] +nestedRest(9, ...nestedRest(9, 1)) +//│ = [0, 0, 1] + +// A later alternative must be checked even after a valid combination was seen. +:e +fun varying(xs, ys) = four(...xs, ...ys) +fun knownGood() = varying([1], [2, 3, 4]) +fun laterBad() = varying([5], [6, 7]) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.130: fun varying(xs, ys) = four(...xs, ...ys) +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'four' expected 4 arguments, but got 3 +//│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// Recursive tuple growth has unknown length, but a rest-only function can forward it. +:expect [3, 2, 1] +fun growing = case + 0 then [] + n then [n, ...growing(n - 1)] +packed(...growing(3)) +//│ = [3, 2, 1] + +// Known fields surrounding an unknown-length spread survive rest forwarding. +:expect [9, 3, 2, 1, 8] +fun keep(head, ...tail) = [head, ...tail] +keep(9, ...growing(3), 8) +//│ = [9, 3, 2, 1, 8] From dc2e933a253e2775b6718adce8bf8ea0814008e8 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 17:47:19 +0800 Subject: [PATCH 040/228] Permit unknown-length spread arguments --- doc/new-resolution-suite-migration.md | 16 --- .../scala/hkmc2/semantics/NewResolver.scala | 116 +++++++++++------- .../main/scala/hkmc2/semantics/Shape.scala | 43 +++---- .../src/test/mlscript/codegen/BadSpreads.mls | 13 +- .../src/test/mlscript/newres/SpreadCalls.mls | 105 ++++++++++++++-- 5 files changed, 197 insertions(+), 96 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index c8b2742e5f..7afef95ab2 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -194,22 +194,6 @@ errors when a selected interpretation lacks the required capability. ## Remaining implementation work, not new semantic decisions -- Argument-spread distribution now uses `TupleShape` candidates with selected - subshapes for each spread. Tuple producers listen for all combinations; `zipArgs` - checks their expanded counts and preserves capture marks for fixed arguments and - residual rest tuples. Marks accumulate on each segment and are applied when a - consumer accesses its fields, as with member selection. - `newres/SpreadCalls.mls` covers nested and delayed spreads, multiple alternatives, - fixed arguments after spreads, rest forwarding, captures, curried calls and - constructors, and recursive forwarding. Repeated producers in the same spread - context widen that spread to an explicit unknown-length tuple shape, preserving - known surrounding fields; `basics/LazySpreads.mls` covers recursive lazy tuples. - Unknown lengths get a distinct diagnostic instead of a fabricated arity mismatch. - Functions can consume a known prefix and forward an unknown-length remainder to - a rest parameter. The broader call-site correlation limitation remains tracked - in `newres/CallSiteShapes.mls`, including a rest-forwarding case. Further - constructor/returned-function fixes should be driven by specific failures, - rather than replacing existing deferral. - Audit assignments to member symbols and definition initializers. The trial reaches unimplemented direct member-reference shape cases and missing assignment lowering in several mutation tests. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index d2de8c15f8..c9ea10ca1e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -127,45 +127,69 @@ class NewResolver: def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Term, src: Term, funSh: TermShape): Unit = val expectedCount = ps.length val reportedCounts = mutable.Set.empty[Int] - var reportedUnknownLength = false - def reportUnknownLength(): Unit = if !reportedUnknownLength then - reportedUnknownLength = true - resolError(src, msg"Cannot determine the length of the argument tuple." -> args.toLoc :: Nil) def publish(p: Param, shape: TermShape | NoShape): Unit = shape match case NoShape => () case sh: TermShape => if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then p.sym.shapeListeners.foreach(_(sh)) - listenTerm(args): - case Marked(tuple: TupleShape, marks) => - val segments = tuple.segments - val fields = segments.collect { case field: TupleShape.Field => field } - val unknownLength = fields.length != segments.length - val providedCount = fields.length - val arityMismatch = (!unknownLength && providedCount < expectedCount) || (r.isEmpty && providedCount > expectedCount) - if arityMismatch then - if reportedCounts.add(providedCount) then - val count = if unknownLength then msg"at least ${providedCount}" else msg"${providedCount}" - resolError(src, - msg"${funSh.describe.capitalize} expected ${expectedCount} ${ - "argument".pluralized(expectedCount)}, but got ${count}" -> funSh.toLoc :: Nil) - // Positions before the first unknown segment are definite. A rest parameter - // can receive the remaining structure without knowing its total length. - val prefix = segments.takeWhile(_.isInstanceOf[TupleShape.Field]).collect: - case field: TupleShape.Field => field - ps.lazyZip(prefix).foreach: (p, arg) => - if p.sign.isEmpty then listenTerm(arg.field.term): sh => - publish(p, sh.exit(arg.marks).exit(marks).enter(mss)) - if unknownLength && !arityMismatch && (prefix.length < expectedCount || r.isEmpty) then - reportUnknownLength() - r.filter(_ => prefix.length >= expectedCount).foreach: p => - // A residual tuple can mix fields from several captured spreads and the - // caller. Keep their individual marks when forwarding it to another call. - val rest = if ps.isEmpty then tuple - else TupleShape(tuple.source, TupleShape.Rest(tuple, expectedCount) :: Nil) + def matchSegments(tuple: TupleShape, marks: Marks): Unit = + val segments = tuple.segments + val knownCount = segments.count(_.isInstanceOf[TupleShape.Field]) + val unknownLength = knownCount != segments.length + val arityMismatch = (!unknownLength && knownCount < expectedCount) || (r.isEmpty && knownCount > expectedCount) + if arityMismatch && reportedCounts.add(knownCount) then + val count = if unknownLength then msg"at least ${knownCount}" else msg"${knownCount}" + resolError(src, + msg"${funSh.describe.capitalize} expected ${expectedCount} ${ + "argument".pluralized(expectedCount)}, but got ${count}" -> funSh.toLoc :: Nil) + // Distribute only over successful argument counts. With no rest parameter, + // the total arity anchors known suffix fields even after an unknown spread. + // With a rest parameter, positions after an unknown spread have no upper + // bound; consider every remaining fixed position and retain every rest tail. + if !arityMismatch then + val extra = (expectedCount - knownCount).max(0) + def assign(segment: TupleShape.Segment, positions: Range): Unit = + positions.foreach: index => + val p = ps(index) + if p.sign.isEmpty then segment match + case TupleShape.Field(field, inner) => listenTerm(field.term): sh => + publish(p, sh.exit(inner).exit(marks).enter(mss)) + case TupleShape.Unknown(source, inner) => + publish(p, UnknownValueShape(source).exit(inner).exit(marks).enter(mss)) + def loop(rest: Ls[TupleShape.Segment], before: Int, unknownBefore: Bool): Unit = rest match + case Nil => () + case (field: TupleShape.Field) :: tail => + val unknownAfter = tail.exists(_.isInstanceOf[TupleShape.Unknown]) + // With no later unknown segment, preceding spreads must supply all + // missing fixed arguments, even when the function has a rest parameter. + val first = if unknownBefore && !unknownAfter then before + extra else before + val last = if !unknownBefore then before + else if r.nonEmpty then expectedCount - 1 else before + extra + assign(field, first.max(0) until (last + 1).min(expectedCount)) + loop(tail, before + 1, unknownBefore) + case (unknown: TupleShape.Unknown) :: tail => + val end = if r.nonEmpty then expectedCount else before + extra + assign(unknown, before until end) + loop(tail, before, true) + loop(segments, 0, false) + r.foreach: p => + // If consumption reaches an unknown segment, some subsequent fields + // may have been consumed too. Approximate that optional prefix with an + // unknown segment, retaining the suffix that must remain. Publishing + // separate concrete tails here would turn uncertainty into false arity + // failures when the rest tuple is spread into another call. + def drop(xs: Ls[TupleShape.Segment], count: Int, approximate: Bool): Ls[TupleShape.Segment] = + if count == 0 then xs + else xs match + case Nil => Nil + case (_: TupleShape.Field) :: rest => drop(rest, count - 1, approximate) + case (_: TupleShape.Unknown) :: rest => + val suffix = drop(rest, count, false) + if approximate then TupleShape.Unknown(tuple.source, Nil) :: suffix else suffix + val remaining = drop(segments, expectedCount, true) + val rest = if ps.isEmpty then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil) publish(p, rest.exit(marks).enter(mss)) - case sh @ Marked(_: UnknownTupleShape, _) => - if ps.isEmpty && r.nonEmpty then r.foreach(p => publish(p, sh.enter(mss))) - else reportUnknownLength() + listenTerm(args): + case Marked(tuple: TupleShape, marks) => matchSegments(tuple, marks) case _ => resolError(src, msg"Expected an argument tuple." -> args.toLoc :: Nil) /** Resolve every constructor pattern through the same symbolic interpretation. @@ -284,6 +308,13 @@ class NewResolver: listenTerm(scrutinee)(sh => matchShapePat(sh, pattern)(_ => ())) def appShape(lhs: TermShape, args: Term, res: App): Unit = + // An unknown element used as a callee stays a dynamic call. Propagate its + // unknown result rather than inferring callability from a different candidate. + lhs match + case Marked(_: UnknownValueShape, _) => + if res.shapes.add(lhs) then res.shapeListeners.foreach(_(lhs)) + return + case _ => () // log(s"appShape? lhs = $lhs, args = $args, res = $res") val sh = appShapes.getOrElseUpdate((lhs, res.resSym), { log(s"appShape: lhs = ${lhs.shwDbg}, args = ${args.showDbg}, res = ${res.showDbg}") @@ -407,7 +438,11 @@ class NewResolver: sel.cls match case N => listenTerm(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") - member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc) + shape match + case Marked(_: UnknownValueShape, _) => + sel.isErroneous = true + resolError(sel, msg"Cannot resolve member '${sel.id.name}' of a value with unknown shape." -> shape.toLoc :: Nil) + case _ => member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc) case S(cls) => listenClass(cls)((cd, marks) => val candidate = cd.sym -> marks @@ -618,13 +653,12 @@ class NewResolver: listenTerm(term): sh => if seen.add(sh) then sh match case Marked(shape: TupleShape, marks) => - val spread: TupleShape | UnknownTupleShape = - if shape.containsSpread(shape.source, marks) then UnknownTupleShape(shape.source) else shape + val spread = if shape.containsSpread(shape.source, marks) then TupleShape.unknown(shape.source) else shape expand(rest, TupleShape.Spread(spread, marks) :: reversed) - case Marked(shape: UnknownTupleShape, marks) => - expand(rest, TupleShape.Spread(shape, marks) :: reversed) - case _ => - resolError(tuple, msg"Cannot determine the length of this spread." -> term.toLoc :: Nil) + case Marked(_, marks) => + // Opaque iterables (e.g. external Arrays) have no resolved + // element layout. Their runtime spread is still permitted. + expand(rest, TupleShape.Spread(TupleShape.unknown(term), marks) :: reversed) expand(tuple.fields, Nil) case intro: IntroTerm => val sh = introShapes.getOrElseUpdate(intro, { diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index ee7f9cb28a..83fe73226d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -20,7 +20,7 @@ sealed trait Shape extends ShapeLike: case ns: SymShape => s"SymShape(${ns.sym.showDbg})" case ns: NewShape => s"NewShape(${ns.cls.showDbg}, ${ns.argss.map(_.showDbg).mkString(", ")})" case is: IntroShape => s"IntroShape(${is.trm.showDbg})" - case ts: UnknownTupleShape => s"UnknownTupleShape(${ts.source.showDbg})" + case us: UnknownValueShape => s"UnknownValueShape(${us.source.showDbg})" case ts: TupleShape => s"TupleShape(${ts.source.showDbg})" case bs: BaseShape => s"BaseShape(${bs.defn.sym.showDbg})" case es: ErrShape => es.describe @@ -338,43 +338,44 @@ class RefinedShape(val base: TermShape, val refinements: Ls[Str -> Term]) extend */ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) extends NonAppTermShape: lazy val segments: Ls[TupleShape.Segment] = elements.flatMap: - case field: TupleShape.Field => field :: Nil - case TupleShape.Rest(shape, count) => shape.segments.drop(count) - case TupleShape.Spread(shape: UnknownTupleShape, marks) => TupleShape.Unknown(shape, marks :: Nil) :: Nil - case TupleShape.Spread(shape: TupleShape, marks) => shape.segments.map: + case segment: TupleShape.Segment => segment :: Nil + case TupleShape.Rest(_, segments) => segments + case TupleShape.Spread(shape, marks) => shape.segments.map: case TupleShape.Field(field, inner) => TupleShape.Field(field, inner ::: marks :: Nil) case TupleShape.Unknown(shape, inner) => TupleShape.Unknown(shape, inner ::: marks :: Nil) /** Re-entering the same producer through the same spread context can generate * arbitrarily many tuple lengths. Widen that spread, retaining surrounding fields. */ def containsSpread(source: Term, marks: Marks): Bool = elements.exists: - case _: TupleShape.Field => false + case _: TupleShape.Segment => false case TupleShape.Rest(shape, _) => shape.containsSpread(source, marks) - case TupleShape.Spread(shape: TupleShape, inner) => + case TupleShape.Spread(shape, inner) => ((shape.source is source) && inner == marks) || shape.containsSpread(source, marks) - case TupleShape.Spread(_: UnknownTupleShape, _) => false def describe: Str = "tuple literal" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str): Opt[MemberInfo] = ??? // Structural tuple members are not implemented yet. -/** A recursive producer can denote unbounded tuple lengths. This shape represents - * all remaining lengths and element values, rather than dropping their candidates. +/** The element of an opaque or widened spread can be any value. Keep this + * alternative in the flow graph so other, known arguments cannot silently make + * an unresolved member selection appear to have a unique static target. */ -final case class UnknownTupleShape(source: Term) extends NonAppTermShape: - def describe: Str = "tuple of unknown length" +final case class UnknownValueShape(source: Term) extends NonAppTermShape: + def describe: Str = "value of unknown shape" def toLoc: Opt[Loc] = source.toLoc - protected def getMemberImpl(name: Str): Opt[MemberInfo] = ??? // Structural tuple members are not implemented yet. + protected def getMemberImpl(name: Str): Opt[MemberInfo] = N object TupleShape: sealed trait Element - sealed trait Segment - final case class Field(field: Fld, marks: Ls[Marks]) extends Element, Segment - final case class Unknown(shape: UnknownTupleShape, marks: Ls[Marks]) extends Segment - final case class Spread(shape: TupleShape | UnknownTupleShape, marks: Marks) extends Element - // Keep a view of the original candidate so slicing preserves spread provenance. - final case class Rest(shape: TupleShape, count: Int) extends Element: - require(count >= 0 && shape.segments.take(count).size == count) - require(shape.segments.take(count).forall(_.isInstanceOf[Field])) + sealed trait Segment extends Element + final case class Field(field: Fld, marks: Ls[Marks]) extends Segment + // An arbitrary number of arbitrary values. Use this for opaque layouts and + // recursive widening, never for a spread whose shape has not arrived yet. + final case class Unknown(source: Term, marks: Ls[Marks]) extends Segment + final case class Spread(shape: TupleShape, marks: Marks) extends Element + def unknown(source: Term): TupleShape = TupleShape(source, Unknown(source, Nil) :: Nil) + // Retain the original candidate as well as the selected residual segments: + // flattening away the parent would hide recursive producer dependencies. + final case class Rest(shape: TupleShape, segments: Ls[Segment]) extends Element type IntroTerm = Term.Lit | Term.UnitVal | Term.Lam | Term.Rcd //| Term.New diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls b/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls index b96d12774c..a7cca186f9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadSpreads.mls @@ -34,13 +34,8 @@ fun foo(a, ...b, c) = [a, ...b] //│ ║ l.32: fun foo(a, ...b, c) = [a, ...b] //│ ╙── ^^^^ -:e :re foo(1, 2, 3) -//│ ╔══[COMPILATION ERROR] Resolution error in tuple literal -//│ ║ l.32: fun foo(a, ...b, c) = [a, ...b] -//│ ║ ^^^^^^^^^ -//│ ╙── Cannot determine the length of this spread. //│ ═══[RUNTIME ERROR] TypeError: 2 is not iterable @@ -50,10 +45,10 @@ fun foo(a, ...b) = [a, ...b] :re foo() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.51: foo() +//│ ║ l.46: foo() //│ ║ ^^^^^ //│ ╟── Function 'foo' expected 1 argument, but got 0 -//│ ║ l.47: fun foo(a, ...b) = [a, ...b] +//│ ║ l.42: fun foo(a, ...b) = [a, ...b] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'foo' expected at least 1 argument but got 0 @@ -61,13 +56,13 @@ foo() :e fun foo(1) = 2 //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found integer literal -//│ ║ l.62: fun foo(1) = 2 +//│ ║ l.57: fun foo(1) = 2 //│ ╙── ^ :e fun foo(...1) = 2 //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found spread -//│ ║ l.68: fun foo(...1) = 2 +//│ ║ l.63: fun foo(...1) = 2 //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index a6062a43f8..c3aa375dc7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -79,15 +79,9 @@ four(...[1, 2], ...[3, 4], 5) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'four' expected 4 arguments but got 5 -// An unknown-length value is not reported as a proven argument count. -:e +// Opaque values are left to the runtime spread operation to validate. +:re four(...Box(1)) -//│ ╔══[COMPILATION ERROR] Resolution error in tuple literal -//│ ║ l.84: four(...Box(1)) -//│ ║ ^^^^^^^^^^^ -//│ ╟── Cannot determine the length of this spread. -//│ ║ l.84: four(...Box(1)) -//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function // Multiple alternatives for each spread are retained as their shapes arrive. @@ -131,7 +125,7 @@ fun varying(xs, ys) = four(...xs, ...ys) fun knownGood() = varying([1], [2, 3, 4]) fun laterBad() = varying([5], [6, 7]) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.130: fun varying(xs, ys) = four(...xs, ...ys) +//│ ║ l.124: fun varying(xs, ys) = four(...xs, ...ys) //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got 3 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -150,3 +144,96 @@ packed(...growing(3)) fun keep(head, ...tail) = [head, ...tail] keep(9, ...growing(3), 8) //│ = [9, 3, 2, 1, 8] + + +let arr = new Array(1,2,3,4) +//│ arr = [1, 2, 3, 4] + +:sr +[...arr] +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ [ ...arr⁰ ] +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = [1, 2, 3, 4] + +four(...arr) +//│ = [1, 2, 3, 4] + + +let arr = if maybe then [1, 2] else [1, 2, 3, 4] +//│ arr = [1, 2] + +:e +four(...arr) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.167: four(...arr) +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Function 'four' expected 4 arguments, but got 2 +//│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] Error: Function 'four' expected 4 arguments but got 2 + + +// Exact call arity anchors known suffixes and prefixes around opaque spreads. +:expect [91, 92, 93] +fun lastOfTwo(a, b) = b +fun firstOfTwo(a, b) = a +fun onlyOne(a) = a +[lastOfTwo(...new Array("ignored"), Box(91)).value, + firstOfTwo(Box(92), ...new Array("ignored")).value, + onlyOne(...new Array(), Box(93)).value] +//│ = [91, 92, 93] + +// Every unknown segment can supply the remaining argument slots. +:expect [1, 2, 3, 4] +four(...new Array(1, 2), ...new Array(3, 4)) +//│ = [1, 2, 3, 4] + +// Rest consumption across an unknown segment stays unknown on subsequent calls. +:expect [1, 2, 3, 4] +fun dropTwo(a, b, ...tail) = tail +four(...dropTwo(...new Array(0, 0, 1, 2, 3), 4)) +//│ = [1, 2, 3, 4] + +// Known fields that must remain in the rest tail retain their value shapes. +:expect 94 +fun afterOne(a, ...tail) = tail +fun tailLast(a, b, c) = c +tailLast(...afterOne(...new Array("ignored"), 1, 2, Box(94))).value +//│ = 94 + +// Unknown elements used as callees remain dynamic calls. +:expect 95 +fun invokeUnknown(f) = f() +invokeUnknown(...new Array(() => 95)) +//│ = 95 + +// Unknown elements must not silently resolve against another known candidate. +:e +fun inspectUnknown(x) = x.value +inspectUnknown(Box(96)) +inspectUnknown(...new Array(Box(96))) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.213: fun inspectUnknown(x) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.215: inspectUnknown(...new Array(Box(96))) +//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Too many known fields remain a proven mismatch even with an unknown spread. +:e +four(...new Array(), 1, 2, 3, 4, 5) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.226: four(...new Array(), 1, 2, 3, 4, 5) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'four' expected 4 arguments, but got at least 5 +//│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] Error: Function 'four' expected 4 arguments but got 5 + +// Successful rest calls still require enough arguments before a known suffix. +:expect 97 +fun invokeRest(f, x, ...tail) = f() +invokeRest(...new Array(() => 97), Box(0)) +//│ = 97 From 8f9155c36765aba36b72e28fb5f30fde1b1badbf Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 18:08:31 +0800 Subject: [PATCH 041/228] Host tuple shapes on their syntax nodes --- .../scala/hkmc2/semantics/NewResolver.scala | 62 +++++++++---------- .../src/main/scala/hkmc2/semantics/Term.scala | 20 ++++-- .../src/test/mlscript/newres/SpreadCalls.mls | 9 +++ 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index c9ea10ca1e..9c79885f2e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -46,8 +46,6 @@ class NewResolver: val newShapes: mutable.Map[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = mutable.Map.empty val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty - // One producer per tuple syntax node, installed before following its spreads. - private val tupleShapes: mutable.Map[Tup, mutable.LinkedHashSet[TermShape]] = mutable.Map.empty private val selfShapes: mutable.Map[InnerSymbol, BaseShape] = mutable.Map.empty val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty @@ -629,37 +627,35 @@ class NewResolver: case TyApp(underlying, _) => listen(underlying, discardMarks)(listener) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => - tupleShapes.get(tuple) match - case S(shapes) => shapes.toList.foreach(listener) - case N => - val shapes = mutable.LinkedHashSet.empty[TermShape] - // Install before subscribing: recursive references must reuse this producer. - tupleShapes(tuple) = shapes - def publish(shape: TermShape): Unit = - if shapes.add(shape) then tuple.shapeListeners.foreach(_(shape)) - def expand(elems: Ls[Elem], reversed: Ls[TupleShape.Element]): Unit = elems match - case Nil => - // A sole spread preserves its operand's shape and context exactly. - // Besides avoiding wrappers, this lets recursive rest forwarding - // reach the same fixed point as forwarding an ordinary parameter. - val shape = reversed match - case TupleShape.Spread(shape, NoMarks) :: Nil => shape - case TupleShape.Spread(shape, marks: SomeMarks) :: Nil => MarkedShape(shape, marks) - case _ => TupleShape(tuple, reversed.reverse) - publish(shape) - case (field: Fld) :: rest => expand(rest, TupleShape.Field(field, Nil) :: reversed) - case Spd(_, term) :: rest => - val seen = mutable.Set.empty[TermShape] - listenTerm(term): sh => - if seen.add(sh) then sh match - case Marked(shape: TupleShape, marks) => - val spread = if shape.containsSpread(shape.source, marks) then TupleShape.unknown(shape.source) else shape - expand(rest, TupleShape.Spread(spread, marks) :: reversed) - case Marked(_, marks) => - // Opaque iterables (e.g. external Arrays) have no resolved - // element layout. Their runtime spread is still permitted. - expand(rest, TupleShape.Spread(TupleShape.unknown(term), marks) :: reversed) - expand(tuple.fields, Nil) + if tuple.shapeProducerStarted then tuple.shapes.foreach(listener) + else + // Start before subscribing: recursive listeners must reuse this host. + tuple.shapeProducerStarted = true + def publish(shape: TermShape): Unit = + if tuple.shapes.add(shape) then tuple.shapeListeners.foreach(_(shape)) + def expand(elems: Ls[Elem], reversed: Ls[TupleShape.Element]): Unit = elems match + case Nil => + // A sole spread preserves its operand's shape and context exactly. + // Besides avoiding wrappers, this lets recursive rest forwarding + // reach the same fixed point as forwarding an ordinary parameter. + val shape = reversed match + case TupleShape.Spread(shape, NoMarks) :: Nil => shape + case TupleShape.Spread(shape, marks: SomeMarks) :: Nil => MarkedShape(shape, marks) + case _ => TupleShape(tuple, reversed.reverse) + publish(shape) + case (field: Fld) :: rest => expand(rest, TupleShape.Field(field, Nil) :: reversed) + case Spd(_, term) :: rest => + val seen = mutable.Set.empty[TermShape] + listenTerm(term): sh => + if seen.add(sh) then sh match + case Marked(shape: TupleShape, marks) => + val spread = if shape.containsSpread(shape.source, marks) then TupleShape.unknown(shape.source) else shape + expand(rest, TupleShape.Spread(spread, marks) :: reversed) + case Marked(_, marks) => + // Opaque iterables (e.g. external Arrays) have no resolved + // element layout. Their runtime spread is still permitted. + expand(rest, TupleShape.Spread(TupleShape.unknown(term), marks) :: reversed) + expand(tuple.fields, Nil) case intro: IntroTerm => val sh = introShapes.getOrElseUpdate(intro, { log(s"introShape: intro = $intro") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 61be2b9a5e..e36143042c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -129,6 +129,13 @@ object AnySel: end AnySel +sealed trait TupImpl extends ShapeHost: + self: Term.Tup => + /** Set by NewResolver before subscribing to spreads. An empty host can be + * pending, so its candidate set cannot indicate whether production started. + */ + private[semantics] var shapeProducerStarted: Bool = false + sealed trait AppImpl extends ResolvableImpl: self: Term.App => var resolvedTargets: Ls[flow.AppTarget] = Nil // * filled during flow analysis @@ -415,7 +422,7 @@ enum Term extends Statement, ShapePublisher: case TyApp(lhs: Term, targs: Ls[Term]) (val typ: Opt[Type]) extends Term, ResolvableImpl case DynSel(prefix: Term, fld: Term, arrayIdx: Bool) - case Tup(fields: Ls[Elem])(val tree: Tree.Tup) + case Tup(fields: Ls[Elem])(val tree: Tree.Tup) extends Term, TupImpl case Mut(underlying: Tup | Rcd | New | DynNew) case CtxTup(fields: Ls[Elem])(val tree: Tree.Tup) case IfLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit) extends Term, ShapeHost @@ -665,10 +672,13 @@ enum Term extends Statement, ShapePublisher: copy.resolvedTargets = term.resolvedTargets copyResolution(term, copy) case DynSel(prefix, fld, arrayIdx) => DynSel(prefix.mkClone, fld.mkClone, arrayIdx) - case term @ Tup(fields) => Tup(fields.map { - case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) - case s: Spd => s.copy(term = s.term.mkClone) - })(term.tree) + case term @ Tup(fields) => + val copy = Tup(fields.map { + case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) + case s: Spd => s.copy(term = s.term.mkClone) + })(term.tree) + copy.shapeProducerStarted = term.shapeProducerStarted + copyShapes(term, copy) case Mut(underlying) => Mut(underlying.mkClone.asInstanceOf[Tup | Rcd | New | DynNew]) case term @ CtxTup(fields) => CtxTup(fields.map { case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index c3aa375dc7..1147bb00de 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -237,3 +237,12 @@ four(...new Array(), 1, 2, 3, 4, 5) fun invokeRest(f, x, ...tail) = f() invokeRest(...new Array(() => 97), Box(0)) //│ = 97 + +// Structurally equal tuple nodes must each publish delayed spread candidates. +:expect [98, 98] +fun repeatedTuples(xs) = + let a = [0, ...xs] + let b = [0, ...xs] + [second(...a).value, second(...b).value] +repeatedTuples([Box(98)]) +//│ = [98, 98] From 02d6b250847e30493ed00fed0e67f8c4308c7927 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Wed, 23 Sep 2026 18:13:12 +0800 Subject: [PATCH 042/228] Doc --- .../scala/hkmc2/semantics/NewResolver.scala | 12 +++++++++- .../main/scala/hkmc2/semantics/Shape.scala | 22 ++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 9c79885f2e..d0f4890f58 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -649,7 +649,17 @@ class NewResolver: listenTerm(term): sh => if seen.add(sh) then sh match case Marked(shape: TupleShape, marks) => - val spread = if shape.containsSpread(shape.source, marks) then TupleShape.unknown(shape.source) else shape + // Widen an incoming candidate that already contains its own + // producer in this context. For `fun growing(n) = ‹...› [n, ...growing(n - 1)] ‹...›`, + // this replaces the recursive operand with an arbitrary sequence, + // retaining the surrounding `n` field and the spread's marks. + // Further feedback in the same context produces the same widened + // candidate, so the host's deduplication stops that expansion. + // Earlier candidates remain valid alternatives. A pending spread + // never reaches this branch: it waits for a shape notification. + val spread = if shape.containsSpread(shape.source, marks) + then TupleShape.unknown(shape.source) + else shape expand(rest, TupleShape.Spread(spread, marks) :: reversed) case Marked(_, marks) => // Opaque iterables (e.g. external Arrays) have no resolved diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 83fe73226d..7ae0707184 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -343,8 +343,16 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) exte case TupleShape.Spread(shape, marks) => shape.segments.map: case TupleShape.Field(field, inner) => TupleShape.Field(field, inner ::: marks :: Nil) case TupleShape.Unknown(shape, inner) => TupleShape.Unknown(shape, inner ::: marks :: Nil) - /** Re-entering the same producer through the same spread context can generate - * arbitrarily many tuple lengths. Widen that spread, retaining surrounding fields. + /** Does this candidate already depend on the given producer in the given context? + * `source` identifies the producer by syntax-node identity; `marks` distinguish + * its spread contexts. Inspect the selected dependency tree, not flattened + * segments, since flattening would erase the evidence of recursive production. + * Rest views retain that evidence even when they consume the relevant fields. + * + * The tuple listener uses this to widen repeated dependencies: for a producer + * like `[n, ...growing(n - 1)]`, repeatedly prefixing its own candidates would + * otherwise create unboundedly many shapes. This is an approximation, not a + * proof of runtime recursion: contexts have only the precision of their marks. */ def containsSpread(source: Term, marks: Marks): Bool = elements.exists: case _: TupleShape.Segment => false @@ -368,13 +376,15 @@ object TupleShape: sealed trait Element sealed trait Segment extends Element final case class Field(field: Fld, marks: Ls[Marks]) extends Segment - // An arbitrary number of arbitrary values. Use this for opaque layouts and - // recursive widening, never for a spread whose shape has not arrived yet. + /** An arbitrary number of arbitrary values. Use this for opaque layouts and + * recursive widening, never for a spread whose shape has not arrived yet. + * The `source` field is used for user-facing diagnostic purposes. */ final case class Unknown(source: Term, marks: Ls[Marks]) extends Segment final case class Spread(shape: TupleShape, marks: Marks) extends Element def unknown(source: Term): TupleShape = TupleShape(source, Unknown(source, Nil) :: Nil) - // Retain the original candidate as well as the selected residual segments: - // flattening away the parent would hide recursive producer dependencies. + /** Retain the original candidate as well as the selected residual segments: + * flattening away the parent would hide recursive producer dependencies from + * containsSpread, allowing recursion through rest slicing to evade widening. */ final case class Rest(shape: TupleShape, segments: Ls[Segment]) extends Element From 04bc987598bda334335e6ffd81880b3662da9694 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 18:22:59 +0800 Subject: [PATCH 043/228] Require annotation heads to be known during elaboration --- doc/new-resolution-suite-migration.md | 48 +++--- .../scala/hkmc2/semantics/Elaborator.scala | 4 +- .../scala/hkmc2/semantics/NewResolver.scala | 31 ++++ .../src/test/mlscript/codegen/NoInline.mls | 1 + .../src/test/mlscript/newres/Annotations.mls | 155 ++++++++++++++++++ 5 files changed, 214 insertions(+), 25 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/Annotations.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 7afef95ab2..f1d3af330c 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -10,11 +10,11 @@ exclude the new shared `.mls` configuration files and new regression tests. | Suite | Migrated | Retained on existing configuration | Active files | | --- | ---: | ---: | ---: | | basics | 55 | 35 | 90 | -| codegen | 61 | 64 | 125 | +| codegen | 62 | 63 | 125 | | ucs | 50 | 20 | 70 | | ups | 13 | 58 | 71 | | apps | 5 | 11 | 16 | -| Total | 184 | 188 | 372 | +| Total | 185 | 187 | 372 | Migrated files start with `:.`. Each suite's `.mls` loads the common language configuration and enables strict resolution; nested directories inherit through @@ -97,27 +97,28 @@ field values used as receivers, field assignment, missing fields, and JS imports Decide whether wildcard opens accept structural records as part of this work; do not silently treat an unsupported structural open as an empty module. -### 2. Annotations that affect elaboration - -Evidence: `codegen/NoInline.mls` warns that `@noInline` has no effect and then -inlines `bar`. `Elaborator.annot` still uses eager `trm.symbol`. The trial of -`codegen/Generators.mls` exceeded the 25-second test limit; lost generator -recognition is a concrete suspect, not yet a proven explanation of that timeout. - -**Question:** may elaboration-affecting annotations be obtained through forward -aliases and wildcard opens, just like other symbolic references? - -**Proposal:** yes. Resolve annotation identity through completed symbolic -candidates and schedule dependent elaboration after that prerequisite is known. -Separate annotations needed to elaborate a body (`generator`, `async`) from -metadata only consumed later (`noInline`, `tailrec`). Diagnose ambiguity or a -dependency cycle explicitly. Do not recognize annotations by spelling, inspect -incomplete definitions, or call `resolvedSym` during elaboration. - -Acceptance cases: direct, qualified, explicit-open, wildcard-open, shadowed, -forward, ambiguous, and cyclic annotation references. Check emitted function -kind and optimization behavior, not only final values. This should be addressed -first because it can change valid program behavior without an ordinary error. +### 2. Annotation identity at elaboration (implemented) + +**Decision:** an annotation's main symbol must already be uniquely known when +the annotation is elaborated. Do not defer the annotated body to accommodate +forward annotation lookup. A directly bound symbol is already known, even if +its definition is unfinished; applications require the head's identity, not +the values of their arguments. Qualified and wildcard-open references must +publish one symbol immediately. A distinct symbol arriving later is an error, +since it cannot change an annotation that has already affected elaboration. + +`NewResolver.annotationSymbol` enforces this using symbolic reference candidates. +Built-in annotations are recognized by symbol identity, without inspecting +incomplete definitions or calling `resolvedSym` during elaboration. The same +rule applies to body-affecting annotations and later-consumed metadata. + +`newres/Annotations.mls` covers qualified, direct, explicit-open, wildcard-open, +shadowed, applied, unresolved, and ambiguous heads, including late ambiguity. +Its emitted-code snapshots verify generator/async function kinds and retained +`noInline` calls. `codegen/NoInline.mls` now runs under new resolution and verifies +optimization behavior. The previous `codegen/Generators.mls` timeout remains a +trial observation in the retained inventory; that entire worksheet has not yet +been migrated. ### 3. Pattern transfer and synthesized references @@ -328,7 +329,6 @@ The files themselves retain the pre-trial configuration and output. - `codegen/NestedClasses.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape - `codegen/NestedScoped.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing - `codegen/NoFreeze.mls`: Unexpected compilation error; [COMPILATION ERROR] Assignment requires an unambiguous term member -- `codegen/NoInline.mls`: Unexpected warning; [WARNING] This annotation has no effect. - `codegen/NoModuleCheck.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Resolution error in object instantiation; - `codegen/ObjectMethodDebinding.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'foo' has no resolved target - `codegen/Open.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'length' diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index de67e80698..56d0a4f2a9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -746,7 +746,9 @@ extends Importer: case _ => term(tree, Trm) match case Term.Error() => N case trm => - trm.symbol match + val symbol = if newResolution then annotationSymbol(trm) else trm.symbol + if newResolution && symbol.isEmpty then return N + symbol match case S(sym) => sym match case ctx.builtins.annotations.untyped => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index d0f4890f58..e5dc15ab6f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -605,6 +605,37 @@ class NewResolver: case _: NewResolvable => listenTerm(trm)(_ => ()) case _ => () + /** Annotation identity is needed before elaborating the annotated body. Read + * the main symbol now, without waiting for a value or inspecting unfinished + * definitions. Keep validating selections/opens: a later distinct candidate + * must be an error, since it cannot change an already interpreted annotation. + */ + def annotationSymbol(trm: Term): Opt[Symbol] = trm match + case Capture(base, _) => annotationSymbol(base) + case TyApp(base, _) => annotationSymbol(base) + case App(base, _) => annotationSymbol(base) + case ref: NewRefImpl => S(ref.sym) + case _: NewSel | _: UnresolvedRef => + val symbols = mutable.LinkedHashSet.empty[BlockMemberSymbol] + var collecting = true + var failed = false + def fail(): Unit = if !failed then + failed = true + resolError(trm, msg"An annotation's main symbol must be uniquely known when the annotation is elaborated." -> trm.toLoc :: Nil) + listen(trm): + case sh: SymShape => + if symbols.add(sh.sym) && !collecting then fail() + case _ => fail() + collecting = false + symbols.toList match + case symbol :: Nil if !failed => S(symbol) + case _ => fail(); N + case _ => trm.symbol match + case s @ S(_) => s + case N => + resolError(trm, msg"An annotation must have a known main symbol." -> trm.toLoc :: Nil) + N + def listenTerm(trm: Term)(listener: TermShape => Unit): Unit = log(s"listenTerm: trm = ${trm.showDbg}") listen(trm): diff --git a/hkmc2/shared/src/test/mlscript/codegen/NoInline.mls b/hkmc2/shared/src/test/mlscript/codegen/NoInline.mls index 6542cfc5f3..02da1f5d46 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/NoInline.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/NoInline.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/newres/Annotations.mls b/hkmc2/shared/src/test/mlscript/newres/Annotations.mls new file mode 100644 index 0000000000..b97c59dfe2 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Annotations.mls @@ -0,0 +1,155 @@ +:. + +// Qualified annotations must affect function kind before the body is elaborated. +:sjs +@annotations.generator +fun generated() = yield 1 +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let generated; generated = function* generated() { return yield 1 }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:sjs +@annotations.async +fun asynchronous() = 2 +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let asynchronous, asynchronous1; +//│ asynchronous1 = function asynchronous() { +//│ return () => { +//│ return 2 +//│ } +//│ }; +//│ asynchronous = function asynchronous() { +//│ let tmp; +//│ tmp = asynchronous1(); +//│ return runtime.toJsAsync(tmp) +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// Explicit and wildcard opens identify the same built-in annotation symbol. +:soir +fun explicitAnnotation(x) = + open annotations { noInline } + @noInline + fun inner(y) = y + 1 + inner(x) +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ define explicitAnnotation⁰ as fun explicitAnnotation¹(x) { +//│ let inner; +//│ @noInline +//│ define inner as fun inner⁰(y) { return +⁰(y, 1) }; +//│ return inner⁰(x) +//│ }; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:soir +fun wildcardAnnotation(x) = + open annotations + @noInline + fun inner(y) = y + 1 + inner(x) +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ define wildcardAnnotation⁰ as fun wildcardAnnotation¹(x) { +//│ let inner; +//│ @noInline +//│ define inner as fun inner¹(y) { return +⁰(y, 1) }; +//│ return inner¹(x) +//│ }; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +// Spelling alone must not recognize a shadowing object as a built-in annotation. +:w +fun shadowed() = + object noInline + @noInline + fun inner(x) = x + inner(3) +//│ ═══[WARNING] This annotation has no effect. + +// A known application head is enough; its argument need not have a value shape. +:w +class Custom(value: Int) +fun customAnnotation(arg) = + @Custom(arg) + fun inner() = 4 + inner() +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.74: @Custom(arg) +//│ ╙── ^^^^^^^^^^^^ + +// A qualified annotation cannot wait for its receiver's forward definition. +:e +fun forwardQualified() = + @Later.noInline + fun inner(x) = x + inner(5) +module Later { object noInline } +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.84: @Later.noInline +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. +//│ ║ l.84: @Later.noInline +//│ ╙── ^^^^^^^^^^^^^^^ + +// Nor can an annotation wait for a forward wildcard source or an alias's value. +:e +fun forwardWildcard() = + open Future + @noInline + fun inner(x) = x + inner(6) +module Future { object noInline } +//│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference +//│ ║ l.99: @noInline +//│ ║ ^^^^^^^^^ +//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. +//│ ║ l.99: @noInline +//│ ╙── ^^^^^^^^^ + +:e +fun forwardAlias() = + @alias.noInline + fun inner(x) = x + inner(7) +val alias = Later +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.112: @alias.noInline +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. +//│ ║ l.112: @alias.noInline +//│ ╙── ^^^^^^^^^^^^^^^ +//│ alias = class Later { noInline: noInline } + +// Two immediately available heads are ambiguous. +:e +module First { object marker } +module Second { object marker } +fun ambiguousAnnotation() = + open First + open Second + @marker + fun inner(x) = x + inner(8) +//│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference +//│ ║ l.131: @marker +//│ ║ ^^^^^^^ +//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. +//│ ║ l.131: @marker +//│ ╙── ^^^^^^^ + +// A late candidate must not silently invalidate an already recognized annotation. +:e +fun lateAmbiguity() = + open annotations + open Last + @noInline + fun inner(x) = x + inner(9) +module Last { object noInline } +//│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference +//│ ║ l.146: @noInline +//│ ║ ^^^^^^^^^ +//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. +//│ ║ l.146: @noInline +//│ ╙── ^^^^^^^^^ From bc19d208d3d43296957436de8a957fde18a5e883 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 18:33:52 +0800 Subject: [PATCH 044/228] Tighten annotation fallback and clean up diagnostics --- doc/new-resolution-suite-migration.md | 33 +++---------------- .../scala/hkmc2/semantics/NewResolver.scala | 13 ++++---- .../src/test/mlscript/newres/Annotations.mls | 28 +++++----------- 3 files changed, 21 insertions(+), 53 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index f1d3af330c..cb8e7510bd 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -97,30 +97,7 @@ field values used as receivers, field assignment, missing fields, and JS imports Decide whether wildcard opens accept structural records as part of this work; do not silently treat an unsupported structural open as an empty module. -### 2. Annotation identity at elaboration (implemented) - -**Decision:** an annotation's main symbol must already be uniquely known when -the annotation is elaborated. Do not defer the annotated body to accommodate -forward annotation lookup. A directly bound symbol is already known, even if -its definition is unfinished; applications require the head's identity, not -the values of their arguments. Qualified and wildcard-open references must -publish one symbol immediately. A distinct symbol arriving later is an error, -since it cannot change an annotation that has already affected elaboration. - -`NewResolver.annotationSymbol` enforces this using symbolic reference candidates. -Built-in annotations are recognized by symbol identity, without inspecting -incomplete definitions or calling `resolvedSym` during elaboration. The same -rule applies to body-affecting annotations and later-consumed metadata. - -`newres/Annotations.mls` covers qualified, direct, explicit-open, wildcard-open, -shadowed, applied, unresolved, and ambiguous heads, including late ambiguity. -Its emitted-code snapshots verify generator/async function kinds and retained -`noInline` calls. `codegen/NoInline.mls` now runs under new resolution and verifies -optimization behavior. The previous `codegen/Generators.mls` timeout remains a -trial observation in the retained inventory; that entire worksheet has not yet -been migrated. - -### 3. Pattern transfer and synthesized references +### 2. Pattern transfer and synthesized references Evidence: new shape propagation lacks record, conjunction, negation, string concatenation, and transformation cases. Examples include @@ -150,7 +127,7 @@ constructors, and recursive UPS matchers. Compare results and generated matcher structure with the existing tests. `ups/examples/HindleyMilner.mls` also timed out during the trial and needs an isolated reproducer before assigning its cause. -### 4. Declared interfaces versus inferred values +### 3. Declared interfaces versus inferred values Evidence: the prior WASM `Basics` failure mixes a nominal annotated receiver with an untyped constructor field carrying a different capture context. @@ -171,7 +148,7 @@ Acceptance cases: an unused annotated function, an unannotated constructor field subclass overrides, inherited fields, curried constructors, nested captures, and generic aliases. Coordinate REPL method cases with the other branch. -### 5. Dynamic construction and foreign callable classes +### 4. Dynamic construction and foreign callable classes Evidence: `basics/DynamicInstantiation.mls` and `codegen/ImportJSClass.mls` reach static class-lowering assumptions for explicitly dynamic constructions. @@ -214,8 +191,8 @@ errors when a selected interpretation lacks the required capability. application difftests, and the aggregate suite. Review output changes; keep nonmigrated files intact. WASM `Basics` retains its previous configuration pending the capture fix, keeping this partial migration green. -2. **Fix annotation prerequisites and cycle handling.** Minimize both timeout - cases. Re-enable generator/annotation tests only after checking emitted IR/JS. +2. **Investigate retained timeouts.** Minimize `codegen/Generators.mls` and + `ups/examples/HindleyMilner.mls`; check emitted IR/JS when migrating those worksheets. 3. **Implement structural targets and external signatures.** Agree on opaque selection policy, then migrate records, tuples, mutation, and JS interop in small batches. Re-run affected negative tests as well as successful programs. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index e5dc15ab6f..b3b312a999 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -621,7 +621,7 @@ class NewResolver: var failed = false def fail(): Unit = if !failed then failed = true - resolError(trm, msg"An annotation's main symbol must be uniquely known when the annotation is elaborated." -> trm.toLoc :: Nil) + resolError(trm, msg"An annotation's main symbol must be uniquely known when the annotation is elaborated." -> N :: Nil) listen(trm): case sh: SymShape => if symbols.add(sh.sym) && !collecting then fail() @@ -630,11 +630,12 @@ class NewResolver: symbols.toList match case symbol :: Nil if !failed => S(symbol) case _ => fail(); N - case _ => trm.symbol match - case s @ S(_) => s - case N => - resolError(trm, msg"An annotation must have a known main symbol." -> trm.toLoc :: Nil) - N + // TODO: Ref(sym: BuiltinSymbol) is a legacy representation that still needs + // updating to the new reference forms, even when using new resolution. + case Ref(sym: BuiltinSymbol) => S(sym) + case _ => + resolError(trm, msg"An annotation must have a known main symbol." -> N :: Nil) + N def listenTerm(trm: Term)(listener: TermShape => Unit): Unit = log(s"listenTerm: trm = ${trm.showDbg}") diff --git a/hkmc2/shared/src/test/mlscript/newres/Annotations.mls b/hkmc2/shared/src/test/mlscript/newres/Annotations.mls index b97c59dfe2..d9f0b9a104 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Annotations.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Annotations.mls @@ -88,9 +88,7 @@ module Later { object noInline } //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.84: @Later.noInline //│ ║ ^^^^^^^^^^^^^^^ -//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. -//│ ║ l.84: @Later.noInline -//│ ╙── ^^^^^^^^^^^^^^^ +//│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. // Nor can an annotation wait for a forward wildcard source or an alias's value. :e @@ -101,11 +99,9 @@ fun forwardWildcard() = inner(6) module Future { object noInline } //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.99: @noInline +//│ ║ l.97: @noInline //│ ║ ^^^^^^^^^ -//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. -//│ ║ l.99: @noInline -//│ ╙── ^^^^^^^^^ +//│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. :e fun forwardAlias() = @@ -114,11 +110,9 @@ fun forwardAlias() = inner(7) val alias = Later //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.112: @alias.noInline +//│ ║ l.108: @alias.noInline //│ ║ ^^^^^^^^^^^^^^^ -//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. -//│ ║ l.112: @alias.noInline -//│ ╙── ^^^^^^^^^^^^^^^ +//│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. //│ alias = class Later { noInline: noInline } // Two immediately available heads are ambiguous. @@ -132,11 +126,9 @@ fun ambiguousAnnotation() = fun inner(x) = x inner(8) //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.131: @marker +//│ ║ l.125: @marker //│ ║ ^^^^^^^ -//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. -//│ ║ l.131: @marker -//│ ╙── ^^^^^^^ +//│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. // A late candidate must not silently invalidate an already recognized annotation. :e @@ -148,8 +140,6 @@ fun lateAmbiguity() = inner(9) module Last { object noInline } //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.146: @noInline +//│ ║ l.138: @noInline //│ ║ ^^^^^^^^^ -//│ ╟── An annotation's main symbol must be uniquely known when the annotation is elaborated. -//│ ║ l.146: @noInline -//│ ╙── ^^^^^^^^^ +//│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. From e4a45d890c0eb2b853cc8dbbe79e704d9b2fc364 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 18:38:32 +0800 Subject: [PATCH 045/228] Refresh remaining new-resolution timeout findings --- doc/new-resolution-suite-migration.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index cb8e7510bd..71fdabdb75 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -124,8 +124,9 @@ before trying to fix recursive cases by adding recursion limits. Acceptance cases: bound members after tuple/record extraction, nested constructors, aliases, guards, transformed results, repeated references, qualified imported constructors, and recursive UPS matchers. Compare results and generated matcher -structure with the existing tests. `ups/examples/HindleyMilner.mls` also timed -out during the trial and needs an isolated reproducer before assigning its cause. +structure with the existing tests. `ups/examples/HindleyMilner.mls` still exceeds +the 25-second limit on recheck and needs an isolated reproducer before assigning +its cause. ### 3. Declared interfaces versus inferred values @@ -191,8 +192,9 @@ errors when a selected interpretation lacks the required capability. application difftests, and the aggregate suite. Review output changes; keep nonmigrated files intact. WASM `Basics` retains its previous configuration pending the capture fix, keeping this partial migration green. -2. **Investigate retained timeouts.** Minimize `codegen/Generators.mls` and - `ups/examples/HindleyMilner.mls`; check emitted IR/JS when migrating those worksheets. +2. **Investigate the remaining timeout.** Minimize `ups/examples/HindleyMilner.mls`. + `codegen/Generators.mls` now finishes in about 1.3 seconds under new resolution; + its remaining failures include unresolved `next` selections and runtime errors. 3. **Implement structural targets and external signatures.** Agree on opaque selection policy, then migrate records, tuples, mutation, and JS interop in small batches. Re-run affected negative tests as well as successful programs. @@ -288,7 +290,7 @@ The files themselves retain the pre-trial configuration and output. - `codegen/Do.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'hello' has no resolved target - `codegen/ErasedTypes.mls`: Unexpected warning; [WARNING] This annotation has no effect. - `codegen/FirstClassFunctionTransform.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/Generators.mls`: New-resolution trial exceeded the 25-second runner timeout. +- `codegen/Generators.mls`: Recheck completes in about 1.3 seconds; unresolved `next` selections and runtime errors remain. - `codegen/Getters.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'whoops' has no resolved target - `codegen/Hygiene.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'foo' - `codegen/ImportAlias.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'inc' has no resolved target @@ -381,7 +383,7 @@ The files themselves retain the pre-trial configuration and output. - `ups/examples/EvaluationContext2.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing - `ups/examples/Extraction.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: NewSel(MemberRef(member:Option),Ident(Some),None) (of class hkmc2.semantics.Term$NewSel) - `ups/examples/Flatten.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack), MemberRef(member:annotations)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) -- `ups/examples/HindleyMilner.mls`: New-resolution trial exceeded the 25-second runner timeout. +- `ups/examples/HindleyMilner.mls`: New-resolution recheck still exceeds the 25-second runner timeout. - `ups/examples/ListPredicates.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(list) (of class hkmc2.semantics.Term$SimpleRef) - `ups/examples/Negation.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) - `ups/examples/PrecedenceClimbStackParse.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) From d8a837d789fa8115abb2227ca791ad93d8ccad64 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 20:20:04 +0800 Subject: [PATCH 046/228] Support record field symbols in new resolution --- .../main/scala/hkmc2/codegen/Lowering.scala | 6 +- .../scala/hkmc2/semantics/NewResolver.scala | 67 ++++- .../main/scala/hkmc2/semantics/Shape.scala | 62 ++++- .../src/main/scala/hkmc2/semantics/Term.scala | 45 ++- .../scala/hkmc2/semantics/TypeShape.scala | 2 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 1 + .../src/test/mlscript/newres/Records.mls | 263 ++++++++++++++++++ .../src/test/mlscript/newres/ReslScratch.mls | 4 +- 8 files changed, 415 insertions(+), 35 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/Records.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 2ad5ca4245..5ab2deabd6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -238,7 +238,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case L((mut, flds)) => subTerm(bod): l => blockImpl(stats, L((mut, RcdArg(N, l) :: flds))) - case RcdField(lhs, rhs) :: stats => + case RcdField(lhs, rhs, _) :: stats => res match case R(_) => wat("RcdField in non-Rcd context", res) case L((mut, flds)) => @@ -293,7 +293,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case Annot.Config(modify) => modify(config) Define(FunDefn(td.owner, td.sym, td.tsym, paramLists, bodyBlock)(cfgOverride, td.annotations), blockImpl(stats, res)) - case syntax.LetBind | syntax.HandlerBind => fail: + case syntax.LetBind | syntax.HandlerBind | syntax.RecordField => fail: ErrorReport( msg"Unexpected declaration kind '${td.k.str}' in lowering" -> td.toLoc :: Nil, source = Diagnostic.Source.Compilation) @@ -661,7 +661,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): source = Diagnostic.Source.Compilation) case candidates => fail: ErrorReport(msg"Wildcard-open reference '${ref.id.name}' is ambiguous" -> ref.toLoc :: - candidates.map((prefix, member) => msg"candidate from ${prefix.showDbg}: ${member.describe}" -> member.toLoc), + candidates.map((prefix, member) => msg"candidate from ${prefix.showDbg}: ${member.describeMember}" -> member.toLoc), source = Diagnostic.Source.Compilation) /** A projection must identify its class as well as its member: different diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index b3b312a999..2f3e0d9201 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -289,10 +289,10 @@ class NewResolver: if sh.isSaturated && sh.applicationHead._1.extendsCls(cls) then fs.foreach: (bms, pat) => sh.getMember(bms.nme) match - case S((sym, marks)) => + case S((sym: BlockMemberSymbol, marks)) => val field = symShapes.getOrElseUpdate((sym, resSym, marks), SymShape(sym, resSym, marks)) matchShapePat(field, pat)(_ => ()) - case N => softAssert(false, "Matched constructor is missing its field") + case _ => softAssert(false, "Matched constructor is missing its field") matched(sh) shape match case sh: TermShape => check(sh) @@ -373,20 +373,39 @@ class NewResolver: softAssert(res.isErroneous) else register - private def publishMember(host: ShapeHost, member: BlockMemberSymbol, + private def publishMember(host: NewResolvable & ShapeHost, member: BlockMemberSymbol | RecordMember, flow: FlowSymbol, marks: Ls[Marks]): Unit = - val shape = symShapes.getOrElseUpdate((member, flow, marks), SymShape(member, flow, marks)) - if host.shapes.add(shape) then host.shapeListeners.foreach(_(shape)) + def publish(shape: Shape): Unit = + if host.shapes.add(shape) then host.shapeListeners.foreach(_(shape)) + member match + case member: BlockMemberSymbol => + publish(symShapes.getOrElseUpdate((member, flow, marks), SymShape(member, flow, marks))) + case RecordMember(field, mutable) => + // Selecting the property does not depend on knowing its value yet. + // Unlike a definition, a field introduces no capture boundary of its own. + if !host.resolvedTargets.contains(field.sym) then host.resolvedTargets ::= field.sym + if mutable then publish(UnknownValueShape(field.rhs)) + else listenTerm(field.rhs): shape => + shape.exit(marks) match + case NoShape => () + case shape: TermShape => publish(shape) def unresolvedRef(ref: UnresolvedRef): Unit = ref.prefixes.foreach: prefix => listenTerm(prefix): shape => // A miss in one wildcard source is not an error: another may provide // the name. A reference with no candidates is diagnosed by lowering. - shape.getMember(ref.id.name).foreach: (member, marks) => - val candidate = prefix -> member - if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate - publishMember(ref, member, ref.resSym, marks) + shape match + case Marked(_: UnknownValueShape, _) => + ref.isErroneous = true + resolError(ref, msg"Cannot resolve member '${ref.id.name}' of a value with unknown shape." -> shape.toLoc :: Nil) + case Marked(record: RecordShape, _) if record.hasUnknownMember(ref.id.name) => + ref.isErroneous = true + resolError(ref, msg"Cannot resolve member '${ref.id.name}' across a computed key or unknown record spread." -> record.toLoc :: Nil) + case _ => shape.getMember(ref.id.name).foreach: (member, marks) => + val candidate = prefix -> member.memberSymbol + if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate + publishMember(ref, member, ref.resSym, marks) /** Inspect an overload set only once its definitions have all been published. */ private def completedClass(shape: SymShape)(selected: ClassDef => Unit, absent: () => Unit): Unit = @@ -427,8 +446,8 @@ class NewResolver: log(s"newSel? sel = ${sel.showDbg}") def member(info: Opt[MemberInfo], description: Message, loc: Opt[Loc]): Unit = info match case S((bms, marks)) => - log(s"newSel member: bms = ${bms.showDbg}, mss = ${marks.map(_.showDbg)}") - if !sel.resolvedMembers.contains(bms) then sel.resolvedMembers ::= bms + log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") + if !sel.resolvedMembers.contains(bms.memberSymbol) then sel.resolvedMembers ::= bms.memberSymbol publishMember(sel, bms, sel.resSym, marks) case N => sel.isErroneous = true @@ -440,6 +459,9 @@ class NewResolver: case Marked(_: UnknownValueShape, _) => sel.isErroneous = true resolError(sel, msg"Cannot resolve member '${sel.id.name}' of a value with unknown shape." -> shape.toLoc :: Nil) + case Marked(record: RecordShape, _) if record.hasUnknownMember(sel.id.name) => + sel.isErroneous = true + resolError(sel, msg"Cannot resolve member '${sel.id.name}' across a computed key or unknown record spread." -> record.toLoc :: Nil) case _ => member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc) case S(cls) => listenClass(cls)((cd, marks) => @@ -698,6 +720,29 @@ class NewResolver: // element layout. Their runtime spread is still permitted. expand(rest, TupleShape.Spread(TupleShape.unknown(term), marks) :: reversed) expand(tuple.fields, Nil) + case record: Rcd => + if record.shapeProducerStarted then record.shapes.foreach(listener) + else + record.shapeProducerStarted = true + def expand(stats: Ls[Statement], reversed: Ls[RecordShape.Element]): Unit = stats match + case Nil => + val shape = RecordShape(record, reversed.reverse) + if record.shapes.add(shape) then record.shapeListeners.foreach(_(shape)) + case (field: RcdField) :: rest => expand(rest, RecordShape.Field(field) :: reversed) + case RcdSpread(term) :: rest => + val seen = mutable.Set.empty[TermShape] + listenTerm(term): shape => + if seen.add(shape) then shape match + case Marked(shape: RecordShape, marks) => + // Bound recursive record producers just as for tuple spreads. + // Keep surrounding explicit fields even when the spread widens. + val spread = if shape.containsSpread(shape.source, marks) + then RecordShape(shape.source, RecordShape.Unknown :: Nil) + else shape + expand(rest, RecordShape.Spread(spread, marks) :: reversed) + case _ => expand(rest, RecordShape.Unknown :: reversed) + case _ :: rest => expand(rest, reversed) + expand(record.stats, Nil) case intro: IntroTerm => val sh = introShapes.getOrElseUpdate(intro, { log(s"introShape: intro = $intro") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 7ae0707184..6000d369cb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -22,6 +22,7 @@ sealed trait Shape extends ShapeLike: case is: IntroShape => s"IntroShape(${is.trm.showDbg})" case us: UnknownValueShape => s"UnknownValueShape(${us.source.showDbg})" case ts: TupleShape => s"TupleShape(${ts.source.showDbg})" + case rs: RecordShape => s"RecordShape(${rs.source.showDbg})" case bs: BaseShape => s"BaseShape(${bs.defn.sym.showDbg})" case es: ErrShape => es.describe @@ -225,7 +226,19 @@ end TermShape // case sel: SelShape => s"selection of ${sel.nme.name} from ${sel.receiver.describe}" // case sym: SymShape => s"symbol ${sym.sym.describe}" -type MemberInfo = (BlockMemberSymbol, Ls[Marks]) +// Record properties carry their value term as well as their symbol, avoiding +// mutable definition state on TermSymbol for these non-overloaded members. +type MemberInfo = (BlockMemberSymbol | RecordMember, Ls[Marks]) + +extension (symbol: BlockMemberSymbol | TermSymbol) + def describeMember: Str = symbol match + case symbol: BlockMemberSymbol => symbol.describe + case symbol: TermSymbol => symbol.describeKind + +extension (member: BlockMemberSymbol | RecordMember) + def memberSymbol: BlockMemberSymbol | TermSymbol = member match + case symbol: BlockMemberSymbol => symbol + case member: RecordMember => member.field.sym class ErrShape(val err: ErrorReport) extends NonAppTermShape: def describe: Str = s"error: ${err.mainMsg}" @@ -388,16 +401,53 @@ object TupleShape: final case class Rest(shape: TupleShape, segments: Ls[Segment]) extends Element -type IntroTerm = Term.Lit | Term.UnitVal | Term.Lam | Term.Rcd //| Term.New +/** A property selected from one record candidate. Mutable records expose a + * stable property identity, but their initializer is not a sound value shape. + */ +final case class RecordMember(field: RcdField, mutable: Bool) + +/** Keep spread candidates and their contexts rather than flattening away their + * provenance. Lookup follows runtime's last-write-wins order. Unknown entries + * are barriers: an opaque spread or computed key can overwrite earlier fields. + */ +final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element]) extends NonAppTermShape: + def describe: Str = "record literal" + def toLoc: Opt[Loc] = source.toLoc + private def lookup(name: Str): Either[Unit, Opt[(RecordMember, Ls[Marks])]] = + def loop(rest: Ls[RecordShape.Element]): Either[Unit, Opt[(RecordMember, Ls[Marks])]] = rest match + case Nil => Right(N) + case RecordShape.Field(field) :: rest => field.field match + case Term.Lit(Tree.StrLit(key)) => + if key == name then Right(S(RecordMember(field, source.mut) -> Nil)) else loop(rest) + case _ => Left(()) + case RecordShape.Unknown :: _ => Left(()) + case RecordShape.Spread(shape, marks) :: rest => shape.lookup(name) match + case Left(_) => Left(()) + case Right(N) => loop(rest) + case Right(S((member, inner))) => + Right(S(member.copy(mutable = member.mutable || source.mut) -> (inner ::: marks :: Nil))) + loop(elements.reverse) + def hasUnknownMember(name: Str): Bool = lookup(name).isLeft + protected def getMemberImpl(name: Str): Opt[MemberInfo] = lookup(name).toOption.flatten + def containsSpread(record: Term.Rcd, marks: Marks): Bool = elements.exists: + case RecordShape.Spread(shape, inner) => + ((shape.source is record) && inner == marks) || shape.containsSpread(record, marks) + case _ => false + +object RecordShape: + enum Element: + case Field(field: RcdField) + case Spread(shape: RecordShape, marks: Marks) + case Unknown + export Element.* + + +type IntroTerm = Term.Lit | Term.UnitVal | Term.Lam //| Term.New class IntroShape(val trm: IntroTerm) extends NonAppTermShape: def describe: Str = trm.describe protected def getMemberImpl(name: Str): Opt[MemberInfo] = trm match case _: Term.Lit | _: Term.UnitVal => N // TODO: methods on literals case lam: Term.Lam => N // TODO: methods on lambdas - case rcd: Term.Rcd => - // rcd.stats.iterator.collect: - // TODO: handler RcdField, RcdSpread - ??? // case newTerm: Term.New => // Map.empty // TODO def toLoc: Opt[Loc] = trm.toLoc diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index e36143042c..8db5980389 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -129,8 +129,8 @@ object AnySel: end AnySel -sealed trait TupImpl extends ShapeHost: - self: Term.Tup => +sealed trait AggregateImpl extends ShapeHost: + self: Term.Tup | Term.Rcd => /** Set by NewResolver before subscribing to spreads. An empty host can be * pending, so its candidate set cannot indicate whether production started. */ @@ -367,7 +367,7 @@ sealed trait NewRefImpl extends AnyRefImpl: sealed trait NewSelImpl extends NewResolvableImpl: self: Term.NewSel => - var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution + var resolvedMembers: Ls[BlockMemberSymbol | TermSymbol] = Nil // * filled during resolution // Class identity and captures must survive even when candidates share an inherited member. var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil def hasAmbiguousClass: Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: @@ -379,7 +379,7 @@ sealed trait UnresolvedRefImpl extends NewResolvableImpl: self: Term.UnresolvedRef => // Retain the receiver as well as the definition: two instances can expose // the same member symbol without denoting the same storage location. - var resolvedMembers: Ls[(Term, BlockMemberSymbol)] = Nil + var resolvedMembers: Ls[(Term, BlockMemberSymbol | TermSymbol)] = Nil enum Term extends Statement, ShapePublisher: @@ -422,7 +422,7 @@ enum Term extends Statement, ShapePublisher: case TyApp(lhs: Term, targs: Ls[Term]) (val typ: Opt[Type]) extends Term, ResolvableImpl case DynSel(prefix: Term, fld: Term, arrayIdx: Bool) - case Tup(fields: Ls[Elem])(val tree: Tree.Tup) extends Term, TupImpl + case Tup(fields: Ls[Elem])(val tree: Tree.Tup) extends Term, AggregateImpl case Mut(underlying: Tup | Rcd | New | DynNew) case CtxTup(fields: Ls[Elem])(val tree: Tree.Tup) case IfLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit) extends Term, ShapeHost @@ -443,7 +443,7 @@ enum Term extends Statement, ShapePublisher: case Constrained(constraints: Ls[SubConstraint], body: Term) case WildcardTy(in: Opt[Term], out: Opt[Term]) case Blk(stats: Ls[Statement], res: Term) extends Term, BlkImpl - case Rcd(mut: Bool, stats: Ls[Statement]) + case Rcd(mut: Bool, stats: Ls[Statement]) extends Term, AggregateImpl case Quoted(body: Term) case Unquoted(body: Term) case New(cls: Term, args: Ls[Term], rft: Opt[ClassSymbol -> ObjBody]) @@ -693,7 +693,10 @@ enum Term extends Statement, ShapePublisher: case Constrained(constraints, body) => Constrained(constraints, body.mkClone) case WildcardTy(in, out) => WildcardTy(in.map(_.mkClone), out.map(_.mkClone)) case blk: Blk => blk.mkBlkClone - case Rcd(mut, stats) => Rcd(mut, stats.map(_.mkClone)) + case term @ Rcd(mut, stats) => + val copy = Rcd(mut, stats.map(_.mkClone)) + copy.shapeProducerStarted = term.shapeProducerStarted + copyShapes(term, copy) case Quoted(body) => Quoted(body.mkClone) case Unquoted(body) => Unquoted(body.mkClone) case term @ New(cls, args, rft) => @@ -813,7 +816,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case d: Definition => ??? case imp: Import => Import(imp.sym, imp.str, imp.file) case LetDecl(sym, annotations) => LetDecl(sym, annotations.map(_.mkClone)) - case RcdField(field, rhs) => RcdField(field.mkClone, rhs.mkClone) + case RcdField(field, rhs, sym) => RcdField(field.mkClone, rhs.mkClone, sym) case RcdSpread(rcd) => RcdSpread(rcd.mkClone) case DefineVar(sym, rhs) => DefineVar(sym, rhs.mkClone) case sc: SetConfig => sc @@ -822,6 +825,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: val desc = this match case Error() => "‹error›" case UnitVal() => "unit value" + case _: Rcd => "record literal" case Lit(lit) => lit.describeLit case Ref(sym) => "reference" case Capture(base, thru) => base.describe @@ -900,7 +904,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case Capture(base, thru) => Vector.single(base) case Resolved(t, sym) => Vector.single(t) case App(lhs, rhs) => Vector.double(lhs, rhs) - case RcdField(lhs, rhs) => Vector.double(lhs, rhs) + case RcdField(lhs, rhs, _) => Vector.double(lhs, rhs) case RcdSpread(bod) => Vector.single(bod) case FunTy(lhs, rhs, eff) => Vector.double(lhs, rhs) ++ eff.toVector case TyApp(pre, tarsg) => pre +: tarsg.toVector @@ -1107,7 +1111,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case Rcd(mut, stats) => (if mut then doc"mut " else doc"") :: braced: doc" # " :: stats.map(_.show).mkDocument(doc", # ") - case RcdField(field, rhs) => doc"${field.show}: ${rhs.show}" + case RcdField(field, rhs, _) => doc"${field.show}: ${rhs.show}" case RcdSpread(record) => doc"...${record.show}" case Quoted(body) => doc"""code"${body.show}"""" case Unquoted(body) => doc"$${${body.show}}" @@ -1186,7 +1190,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case r @ SelfRef(sym) => sym.showAsPlain case Capture(base, thru) => s"${base.showDbg}^${thru.showDbg}" case App(lhs, rhs) => s"${lhs.showDbg}${rhs.showDbgAsParams}" - case RcdField(lhs, rhs) => s"${lhs.showDbg}: ${rhs.showDbg}" + case RcdField(lhs, rhs, _) => s"${lhs.showDbg}: ${rhs.showDbg}" case RcdSpread(bod) => s"...${bod.showDbg}" case FunTy(lhs: Tup, rhs, eff) => s"${lhs.fields.map(_.showDbg).mkString(", ")} ->${ @@ -1265,7 +1269,24 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: final case class LetDecl(sym: LocalVarSymbol | TermSymbol, annotations: Ls[Annot]) extends Statement -final case class RcdField(field: Term, rhs: Term) extends Statement +/** The symbol identifies the property, independently of any local binding used + * to evaluate its value. Computed keys also have an identity, but cannot be + * selected statically until their key is known. Cloning preserves this identity. + */ +final case class RcdField(field: Term, rhs: Term, sym: TermSymbol) extends Statement: + require((sym.k is RecordField) && sym.owner.isEmpty) + field match + case Term.Lit(Tree.StrLit(name)) => require(sym.nme == name) + case _ => () + +object RcdField: + def apply(field: Term, rhs: Term)(using State): RcdField = + val name = field match + case Term.Lit(Tree.StrLit(name)) => name + case _ => "computed field" + val id = new Tree.Ident(name) + id.withLocOf(field) + RcdField(field, rhs, TermSymbol(RecordField, N, id, erasedType = N)) final case class RcdSpread(rcd: Term) extends Statement final case class DefineVar(sym: LocalSymbol | TermSymbol, rhs: Term) extends Statement diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index d90bf24e12..deda903f7b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -43,7 +43,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => else if ambiguousReceiver || shapes.sizeCompare(1) > 0 then val candidates = source.withoutCaptures match case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.map: (prefix, member) => - msg"candidate: ${member.describe}" -> member.toLoc + msg"candidate: ${member.describeMember}" -> member.toLoc case _ => shapes.toList.flatMap: case Nominal(defn) => msg"candidate: ${defn.sym.describeKind}" -> defn.toLoc :: Nil case Alias(symbol, _) => msg"candidate: ${symbol.describeKind}" -> symbol.toLoc :: Nil diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 25a40deaec..c6e1074649 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -504,6 +504,7 @@ sealed abstract class ValLike(str: Str, desc: Str)(using Line) extends TermDefKi sealed abstract class Val(str: Str, desc: Str)(using Line) extends ValLike(str, desc) case object ImmutVal extends Val("val", "value") case object MutVal extends Val("mut val", "mutable value") +case object RecordField extends ValLike("field", "record field") case object LetBind extends ValLike("let", "let binding") case object HandlerBind extends TermDefKind("handler", "handler binding") case object Fun extends TermDefKind("fun", "function") diff --git a/hkmc2/shared/src/test/mlscript/newres/Records.mls b/hkmc2/shared/src/test/mlscript/newres/Records.mls new file mode 100644 index 0000000000..d8cd362c7b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Records.mls @@ -0,0 +1,263 @@ +:. + + +:sr +:expect 1 +{a: 1}.a +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ { let a⁰, a⁰ = 1, "a": a⁰ }.a¹ +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 1 + +:expect 2 +{"a": 2}.a +//│ = 2 + +:expect 3 +{a: 1, b: a + 2}.b +//│ = 3 + +:expect 4 +{inner: {answer: 4}}.inner.answer +//│ = 4 + +// Duplicate keys use the last property, including its value shape. +:expect 5 +{a: {wrong: 0}, a: {answer: 5}}.a.answer +//│ = 5 + +// Fields are values; stored lambdas receive ordinary arguments. +:expect 6 +{f: x => x + 1}.f(5) +//│ = 6 + +// The record's context must filter values from other calls to the same producer. +fun wrap(x) = {value: x} + +:expect 7 +wrap({left: 7}).value.left +//│ = 7 + +:expect 8 +wrap({right: 8}).value.right +//│ = 8 + +// Reading the same field through a helper retains the receiver context too. +:expect 9 +fun read(r) = r.value +read(wrap({answer: 9})).answer +//│ = 9 + +:expect 10 +read(wrap({different: 10})).different +//│ = 10 + +// Both selective and wildcard opens can expose record properties. +:expect 11 +let record = {answer: 11} +open record {answer} +answer +//│ = 11 +//│ record = {answer: 11} + +:expect 12 +let record2 = {answer2: 12} +open record2 +answer2 +//│ = 12 +//│ record2 = {answer2: 12} + +:e +{a: 1}.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.71: {a: 1}.missing +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'missing' +//│ ║ l.71: {a: 1}.missing +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +{a: 1}.a() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.81: {a: 1}.a() +//│ ║ ^^^^^^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.81: {a: 1}.a() +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] TypeError: tmp10.a is not a function + +// TODO: Resolve computed keys. Until then, do not look through an unknown overwrite. +:fixme +let key = "a" +{a: {answer: 0}, (key): {other: 1}}.a.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.93: {a: {answer: 0}, (key): {other: 1}}.a.answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. +//│ ║ l.93: {a: {answer: 0}, (key): {other: 1}}.a.answer +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target +//│ ║ l.93: {a: {answer: 0}, (key): {other: 1}}.a.answer +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ key = "a" + +// An explicit field after an unknown key still wins. +:expect 13 +{(key): 0, a: 13}.a +//│ = 13 + + +// Known spreads preserve both field identity and captured values. +:expect 14 +{...{answer: 14}}.answer +//│ = 14 + +:expect 15 +{answer: 0, ...{answer: 15}}.answer +//│ = 15 + +:expect 16 +{...{answer: 0}, answer: 16}.answer +//│ = 16 + +:expect 17 +{answer: 17, ...{other: 0}}.answer +//│ = 17 + +fun spreadWrap(x) = {...{value: x}} + +:expect 18 +spreadWrap({first: 18}).value.first +//│ = 18 + +:expect 19 +spreadWrap({second: 19}).value.second +//│ = 19 + +// Mutable fields retain their identity after writes. +:expect 20 +let mutableRecord = mut {answer: 0} +set mutableRecord.answer = 20 +mutableRecord.answer +//│ = 20 +//│ mutableRecord = {answer: 20} + +// TODO: Track writes to mutable fields. Their initializer must not determine +// static targets after an assignment (even if another receiver has that member). +:fixme +let changing = mut {value: {before: 0}} +set changing.value = {after: 21} +changing.value.after +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.152: changing.value.after +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'after' of a value with unknown shape. +//│ ║ l.150: let changing = mut {value: {before: 0}} +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ changing = {value: {after: 21}} + + +// TODO: Nested helper calls currently lose the outer call-site mark when +// entering the helper through a capture, merging these two value shapes. +:fixme +fun nestedWrap(x) = {...wrap(x)} +nestedWrap({first: 22}).value.first +nestedWrap({second: 23}).value.second +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.167: nestedWrap({first: 22}).value.first +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'first' +//│ ║ l.168: nestedWrap({second: 23}).value.second +//│ ╙── ^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.168: nestedWrap({second: 23}).value.second +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'second' +//│ ║ l.167: nestedWrap({first: 22}).value.first +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +// A missing member in a spread must fall through to earlier fields; an unknown +// member must not. This also holds when wildcard opens offer another candidate. +:fixme +let unknownKeys = {(key): 0} +fun unknownOpen() = + open unknownKeys + let knownKeys = {a: 24} + open knownKeys + a +unknownOpen() +//│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference +//│ ║ l.192: a +//│ ║ ^ +//│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. +//│ ║ l.187: let unknownKeys = {(key): 0} +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ unknownKeys = {a: 0} + +// A later explicit field makes an earlier unknown spread irrelevant to lookup. +:expect 25 +{...unknownKeys, a: 25}.a +//│ = 25 + +// Forward producers remain pending until their body is available. +:expect 26 +let forwardRecord = {...makeRecord()} +fun makeRecord() = {answer: 26} +forwardRecord.answer +//│ = 26 +//│ forwardRecord = {answer: 26} + +// Strict resolution still diagnoses distinct field identities from alternatives. +:e +fun choose(flag) = if flag then {answer: 1} else {answer: 2} +choose(true).answer +//│ ╔══[COMPILATION ERROR] This selection of member 'answer' is ambiguous, as it has multiple resolved targets +//│ ║ l.219: choose(true).answer +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── target: record field 'answer' +//│ ║ l.218: fun choose(flag) = if flag then {answer: 1} else {answer: 2} +//│ ║ ^^^^^^ +//│ ╟── target: record field 'answer' +//│ ║ l.218: fun choose(flag) = if flag then {answer: 1} else {answer: 2} +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Recursive spreads must converge even as their candidate graph grows. +// TODO: Resolve fields through widened recursive spreads. +:fixme +fun recursiveRecord(n) = + if n == 0 then {answer: 27} + else {...recursiveRecord(n - 1)} +recursiveRecord(2).answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.237: recursiveRecord(2).answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' across a computed key or unknown record spread. +//│ ║ l.236: else {...recursiveRecord(n - 1)} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +// Unknown mutable values in an open must not disappear in favor of another source. +:fixme +fun unknownValueOpen() = + let unknownValue = mut {value: {answer3: 0}} + let fallbackValue = {answer3: 1} + let openedValue = unknownValue.value + open openedValue + open fallbackValue + answer3 +unknownValueOpen() +//│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference +//│ ║ l.255: answer3 +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'answer3' of a value with unknown shape. +//│ ║ l.250: let unknownValue = mut {value: {answer3: 0}} +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 39e869a0c5..4d3b269032 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -190,9 +190,9 @@ fun foo(x) = //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = {a: 1} -:fixme +:expect 1 {a: 1}.a -//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +//│ = 1 From 637a928301584d4b7a1242c70ac75bb364e2a19e Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 20:48:13 +0800 Subject: [PATCH 047/228] Keep debug representations out of user-facing diagnostics --- AGENTS.md | 11 ++++++ .../main/scala/hkmc2/codegen/Lowering.scala | 2 +- .../shared/src/test/mlscript/newres/Opens.mls | 12 +++--- .../src/test/mlscript/newres/Records.mls | 39 +++++++++---------- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 226ebd6837..4e89958037 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,6 +74,17 @@ especially if it involves non-obvious decisions/trade-offs or if alternative approaches were considered and rejected. +## User-Facing Diagnostics + +Never use `showDbg`, `shwDbg`, raw AST/IR `toString` output, or other debug-only +representations in user-facing error messages, warnings, or diagnostic notes. +Use source-level names and user-facing descriptions, with source locations to +identify the relevant definitions or expressions. Do not expose internal symbol +IDs, resolution marks, or compiler representation details to users. +Debug representations belong only in internal logs and explicitly requested +debug output, not in ordinary diagnostics. + + ## Editing Style Do not remove existing `end` markers. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 5ab2deabd6..7b3a49f786 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -661,7 +661,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): source = Diagnostic.Source.Compilation) case candidates => fail: ErrorReport(msg"Wildcard-open reference '${ref.id.name}' is ambiguous" -> ref.toLoc :: - candidates.map((prefix, member) => msg"candidate from ${prefix.showDbg}: ${member.describeMember}" -> member.toLoc), + candidates.map((_, member) => msg"candidate: ${member.describeMember} '${member.nme}'" -> member.toLoc), source = Diagnostic.Source.Compilation) /** A projection must identify its class as well as its member: different diff --git a/hkmc2/shared/src/test/mlscript/newres/Opens.mls b/hkmc2/shared/src/test/mlscript/newres/Opens.mls index 7d31c39e28..671f20c26b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Opens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Opens.mls @@ -102,10 +102,10 @@ ambiguous() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous //│ ║ l.99: extra //│ ║ ^^^^^ -//│ ╟── candidate from member:Values⁰^term:ambiguous⁰: value +//│ ╟── candidate: value 'extra' //│ ║ l.8: val extra = 3 //│ ║ ^^^^^^^^^^^^^ -//│ ╟── candidate from member:Selected⁰^term:ambiguous⁰: value +//│ ╟── candidate: value 'extra' //│ ║ l.38: module Selected { val extra = 6 } //│ ╙── ^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -224,10 +224,10 @@ twoInstances() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'value' is ambiguous //│ ║ l.221: value //│ ║ ^^^^^ -//│ ╟── candidate from first⁰^term:twoInstances⁰: value +//│ ╟── candidate: value 'value' //│ ║ l.211: module M { val value = n } //│ ║ ^^^^^^^^^^^^^ -//│ ╟── candidate from second⁰^term:twoInstances⁰: value +//│ ╟── candidate: value 'value' //│ ║ l.211: module M { val value = n } //│ ╙── ^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -301,10 +301,10 @@ ambiguousReversed() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous //│ ║ l.298: extra //│ ║ ^^^^^ -//│ ╟── candidate from member:Selected⁰^term:ambiguousReversed⁰: value +//│ ╟── candidate: value 'extra' //│ ║ l.38: module Selected { val extra = 6 } //│ ║ ^^^^^^^^^^^^^ -//│ ╟── candidate from member:Values⁰^term:ambiguousReversed⁰: value +//│ ╟── candidate: value 'extra' //│ ║ l.8: val extra = 3 //│ ╙── ^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Records.mls b/hkmc2/shared/src/test/mlscript/newres/Records.mls index d8cd362c7b..b3cca6ed14 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Records.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Records.mls @@ -87,18 +87,17 @@ answer2 //│ ╙── ^ //│ ═══[RUNTIME ERROR] TypeError: tmp10.a is not a function -// TODO: Resolve computed keys. Until then, do not look through an unknown overwrite. -:fixme +:todo // TODO: Resolve computed keys. Until then, do not look through an unknown overwrite. let key = "a" {a: {answer: 0}, (key): {other: 1}}.a.answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.93: {a: {answer: 0}, (key): {other: 1}}.a.answer +//│ ║ l.92: {a: {answer: 0}, (key): {other: 1}}.a.answer //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. -//│ ║ l.93: {a: {answer: 0}, (key): {other: 1}}.a.answer +//│ ║ l.92: {a: {answer: 0}, (key): {other: 1}}.a.answer //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target -//│ ║ l.93: {a: {answer: 0}, (key): {other: 1}}.a.answer +//│ ║ l.92: {a: {answer: 0}, (key): {other: 1}}.a.answer //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ key = "a" @@ -151,10 +150,10 @@ let changing = mut {value: {before: 0}} set changing.value = {after: 21} changing.value.after //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.152: changing.value.after +//│ ║ l.151: changing.value.after //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'after' of a value with unknown shape. -//│ ║ l.150: let changing = mut {value: {before: 0}} +//│ ║ l.149: let changing = mut {value: {before: 0}} //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ changing = {value: {after: 21}} @@ -167,16 +166,16 @@ fun nestedWrap(x) = {...wrap(x)} nestedWrap({first: 22}).value.first nestedWrap({second: 23}).value.second //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.167: nestedWrap({first: 22}).value.first +//│ ║ l.166: nestedWrap({first: 22}).value.first //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'first' -//│ ║ l.168: nestedWrap({second: 23}).value.second +//│ ║ l.167: nestedWrap({second: 23}).value.second //│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.168: nestedWrap({second: 23}).value.second +//│ ║ l.167: nestedWrap({second: 23}).value.second //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'second' -//│ ║ l.167: nestedWrap({first: 22}).value.first +//│ ║ l.166: nestedWrap({first: 22}).value.first //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -192,10 +191,10 @@ fun unknownOpen() = a unknownOpen() //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.192: a +//│ ║ l.191: a //│ ║ ^ //│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. -//│ ║ l.187: let unknownKeys = {(key): 0} +//│ ║ l.186: let unknownKeys = {(key): 0} //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ unknownKeys = {a: 0} @@ -218,13 +217,13 @@ forwardRecord.answer fun choose(flag) = if flag then {answer: 1} else {answer: 2} choose(true).answer //│ ╔══[COMPILATION ERROR] This selection of member 'answer' is ambiguous, as it has multiple resolved targets -//│ ║ l.219: choose(true).answer +//│ ║ l.218: choose(true).answer //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── target: record field 'answer' -//│ ║ l.218: fun choose(flag) = if flag then {answer: 1} else {answer: 2} +//│ ║ l.217: fun choose(flag) = if flag then {answer: 1} else {answer: 2} //│ ║ ^^^^^^ //│ ╟── target: record field 'answer' -//│ ║ l.218: fun choose(flag) = if flag then {answer: 1} else {answer: 2} +//│ ║ l.217: fun choose(flag) = if flag then {answer: 1} else {answer: 2} //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -236,10 +235,10 @@ fun recursiveRecord(n) = else {...recursiveRecord(n - 1)} recursiveRecord(2).answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.237: recursiveRecord(2).answer +//│ ║ l.236: recursiveRecord(2).answer //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' across a computed key or unknown record spread. -//│ ║ l.236: else {...recursiveRecord(n - 1)} +//│ ║ l.235: else {...recursiveRecord(n - 1)} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -255,9 +254,9 @@ fun unknownValueOpen() = answer3 unknownValueOpen() //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.255: answer3 +//│ ║ l.254: answer3 //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'answer3' of a value with unknown shape. -//│ ║ l.250: let unknownValue = mut {value: {answer3: 0}} +//│ ║ l.249: let unknownValue = mut {value: {answer3: 0}} //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 03b7d5f7d0c6f3a829c661c1653dd927029cd2af Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 20:52:16 +0800 Subject: [PATCH 048/228] Show definition and opening sites for ambiguous wildcard members --- .../main/scala/hkmc2/codegen/Lowering.scala | 5 +- .../scala/hkmc2/semantics/Elaborator.scala | 21 +++-- .../shared/src/test/mlscript/newres/Opens.mls | 79 +++++++++++++++---- 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 7b3a49f786..60050b756b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -661,7 +661,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): source = Diagnostic.Source.Compilation) case candidates => fail: ErrorReport(msg"Wildcard-open reference '${ref.id.name}' is ambiguous" -> ref.toLoc :: - candidates.map((_, member) => msg"candidate: ${member.describeMember} '${member.nme}'" -> member.toLoc), + candidates.flatMap: (prefix, member) => + (msg"candidate: ${member.describeMember} '${member.nme}' defined here" -> member.toLoc) :: + (msg"Opened here" -> prefix.toLoc) :: Nil + , source = Diagnostic.Source.Compilation) /** A projection must identify its class as well as its member: different diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 56d0a4f2a9..339d8808f7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -99,7 +99,7 @@ object Elaborator: env: Map[Str, Ctx.Elem], mode: Mode, labels: Map[LabelSymbol, LabelBinding], - wildcardOpens: Ls[Ctx.Elem], + wildcardOpens: Ls[Ctx.OpenSource], ): override def toString: Str = s"${parent.fold("")(_.toString+"/")}${outer.showDbg}" @@ -158,10 +158,11 @@ object Elaborator: case _ => inherited else inherited - private lazy val visibleWildcardOpens: Ls[Ctx.Elem] = + private lazy val visibleWildcardOpens: Ls[Ctx.OpenSource] = val inherited = parent.toList.flatMap(_.visibleWildcardOpens) wildcardOpens ::: (outer match - case OuterCtx.NonReturnContext(S(sym)) => inherited.map(Ctx.CaptElem(_, sym)) + case OuterCtx.NonReturnContext(S(sym)) => inherited.map(source => + Ctx.OpenSource(Ctx.CaptElem(source.elem, sym))(source.id)) case _ => inherited) def lookupLabel(name: Str): LabelLookup = @@ -401,6 +402,12 @@ object Elaborator: val primitivelyRepresentedRoots: Set[TypeSymbol] = Set(Num, Str, Bool) object Ctx: + /** The opening site is diagnostic metadata, excluded from equality so + * reopening the same receiver still contributes only one candidate. + * Captures must preserve this site independently of the receiver's definition. + */ + final case class OpenSource(elem: Elem)(val id: Ident) + abstract class Elem: def nme: Str def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term @@ -437,9 +444,11 @@ object Elaborator: else Term.SynthSel(prefix, name)(symOpt, FlowSymbol.synthSel(nme), N, S(summon)) def symbol = symOpt - final case class WildcardElem(nme: Str, sources: Ls[Elem]) extends Elem: + final case class WildcardElem(nme: Str, sources: Ls[OpenSource]) extends Elem: def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term = - val prefixes = sources.map(source => source.ref(Ident(source.nme))) + // Each ref is fresh. Locate its outer capture at the opening site too; + // a chained wildcard reference may already have attached this location. + val prefixes = sources.map(source => source.elem.ref(source.id).withoutLoc.withLocOf(source.id)) val res = new Term.UnresolvedRef(prefixes, id)(FlowSymbol.synthSel(nme)).withLocOf(id) summon[NewResolver].unresolvedRef(res) res @@ -1963,7 +1972,7 @@ extends Importer: case baseId: Ident => ctx.get(baseId.name) match case S(baseElem) if newResolution && importedTrees.isEmpty => - ctx.copy(wildcardOpens = baseElem :: ctx.wildcardOpens).givenIn: + ctx.copy(wildcardOpens = Ctx.OpenSource(baseElem)(baseId) :: ctx.wildcardOpens).givenIn: go(sts, Nil, acc) case S(baseElem) => val importedNames = importedTrees match diff --git a/hkmc2/shared/src/test/mlscript/newres/Opens.mls b/hkmc2/shared/src/test/mlscript/newres/Opens.mls index 671f20c26b..932d19f185 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Opens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Opens.mls @@ -102,12 +102,18 @@ ambiguous() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous //│ ║ l.99: extra //│ ║ ^^^^^ -//│ ╟── candidate: value 'extra' +//│ ╟── candidate: value 'extra' defined here //│ ║ l.8: val extra = 3 //│ ║ ^^^^^^^^^^^^^ -//│ ╟── candidate: value 'extra' +//│ ╟── Opened here +//│ ║ l.97: open Values +//│ ║ ^^^^^^ +//│ ╟── candidate: value 'extra' defined here //│ ║ l.38: module Selected { val extra = 6 } -//│ ╙── ^^^^^^^^^^^^^ +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Opened here +//│ ║ l.98: open Selected +//│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e @@ -117,14 +123,14 @@ fun missing() = { } missing() //│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: absent -//│ ║ l.116: absent +//│ ║ l.122: absent //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e open Later { absent } //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.125: open Later { absent } +//│ ║ l.131: open Later { absent } //│ ║ ^^^^^^ //│ ╟── Module 'Later' does not contain member 'absent' //│ ║ l.62: module Later { val answer = 7 } @@ -222,14 +228,20 @@ fun twoInstances() = { } twoInstances() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'value' is ambiguous -//│ ║ l.221: value +//│ ║ l.227: value //│ ║ ^^^^^ -//│ ╟── candidate: value 'value' -//│ ║ l.211: module M { val value = n } +//│ ╟── candidate: value 'value' defined here +//│ ║ l.217: module M { val value = n } +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Opened here +//│ ║ l.225: open first +//│ ║ ^^^^^ +//│ ╟── candidate: value 'value' defined here +//│ ║ l.217: module M { val value = n } //│ ║ ^^^^^^^^^^^^^ -//│ ╟── candidate: value 'value' -//│ ║ l.211: module M { val value = n } -//│ ╙── ^^^^^^^^^^^^^ +//│ ╟── Opened here +//│ ║ l.226: open second +//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ first = class M { value: 16 } //│ second = class M { value: 17 } @@ -265,10 +277,10 @@ val Shadowed = 20 open ClassSource new Shadowed //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.266: new Shadowed +//│ ║ l.278: new Shadowed //│ ║ ^^^^^^^^^^^^ //│ ╟── Value symbol 'Shadowed' cannot be instantiated with keyword 'new'. -//│ ║ l.264: val Shadowed = 20 +//│ ║ l.276: val Shadowed = 20 //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -299,14 +311,20 @@ fun ambiguousReversed() = { } ambiguousReversed() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous -//│ ║ l.298: extra +//│ ║ l.310: extra //│ ║ ^^^^^ -//│ ╟── candidate: value 'extra' +//│ ╟── candidate: value 'extra' defined here //│ ║ l.38: module Selected { val extra = 6 } //│ ║ ^^^^^^^^^^^^^ -//│ ╟── candidate: value 'extra' +//│ ╟── Opened here +//│ ║ l.308: open Selected +//│ ║ ^^^^^^^^ +//│ ╟── candidate: value 'extra' defined here //│ ║ l.8: val extra = 3 -//│ ╙── ^^^^^^^^^^^^^ +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Opened here +//│ ║ l.309: open Values +//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -350,3 +368,30 @@ fun bareNamed(x) = { [bareWildcard(new BarePatterns.Token), bareWildcard(0), bareNamed(new BarePatterns.Token), bareNamed(0)] //│ = [1, 0, 1, 0] + + +// Opening sites survive capture by a nested function, independently of definitions. +:e +fun inheritedAmbiguity() = { + open Values + open Selected + fun nested() = extra + nested() +} +inheritedAmbiguity() +//│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous +//│ ║ l.378: fun nested() = extra +//│ ║ ^^^^^ +//│ ╟── candidate: value 'extra' defined here +//│ ║ l.8: val extra = 3 +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Opened here +//│ ║ l.376: open Values +//│ ║ ^^^^^^ +//│ ╟── candidate: value 'extra' defined here +//│ ║ l.38: module Selected { val extra = 6 } +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Opened here +//│ ║ l.377: open Selected +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From fa8993a7ef8168d5d74bb671393219c7109cbc3d Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 21:00:10 +0800 Subject: [PATCH 049/228] Make member lookup explicit and guard constructor-pattern invariants --- .../scala/hkmc2/semantics/NewResolver.scala | 70 +++++---- .../main/scala/hkmc2/semantics/Shape.scala | 142 +++++++++++------- .../newres/ConstructorFieldRecovery.mls | 95 ++++++++++++ .../src/test/mlscript/newres/Records.mls | 24 +++ 4 files changed, 252 insertions(+), 79 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 2f3e0d9201..c5a51f9a95 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -226,7 +226,16 @@ class NewResolver: "pattern argument".pluralized(ps.params.length)}, but got ${args.length}" -> cls.toLoc :: Nil) ps.params.lazyZip(args).flatMap: (p, a) => p.fldSym match - case S(fldSym: BlockMemberSymbol) => (fldSym -> a) :: Nil + case S(fldSym: BlockMemberSymbol) => + // withFields reports member/alias conflicts before dropping all + // generated fields for recovery. Parameters retain their fldSym, + // so verify its identity in the recovered body: a body member may + // occupy the same name. Do not publish dangling pattern fields or + // report another error for this already-diagnosed class. + if cls.body.members.get(fldSym.nme).contains(fldSym) then (fldSym -> a) :: Nil + else + res.isErroneous = true + Nil case _ => res.isErroneous = true resolError(res, msg"Pattern argument requires an accessible constructor field." -> p.toLoc :: Nil) @@ -286,13 +295,22 @@ class NewResolver: def listenConstructor(psh: PatternShape): Unit = psh match case CtorPatternShape(cls, fs, _, resSym) => def check(sh: TermShape): Unit = - if sh.isSaturated && sh.applicationHead._1.extendsCls(cls) then + if sh.isInstanceOfClass(cls) then fs.foreach: (bms, pat) => sh.getMember(bms.nme) match - case S((sym: BlockMemberSymbol, marks)) => + case MemberLookup.Found(sym: BlockMemberSymbol, marks) => val field = symShapes.getOrElseUpdate((sym, resSym, marks), SymShape(sym, resSym, marks)) matchShapePat(field, pat)(_ => ()) - case _ => softAssert(false, "Matched constructor is missing its field") + case _ => + // classPattern only publishes fields present in cls.body. + // The nominal test above admits only that class's instances, + // this-values, and subclasses. Their member lookup follows + // the same class bodies and inheritance chain; overrides are + // also BlockMemberSymbols. Record members cannot replace a + // class's own field. Thus this branch is an internal mismatch + // between nominal matching and member lookup, not recovery + // from a malformed class (filtered by classPattern above). + softAssert(false, "Matched constructor is missing its field") matched(sh) shape match case sh: TermShape => check(sh) @@ -390,22 +408,28 @@ class NewResolver: case NoShape => () case shape: TermShape => publish(shape) + private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, loc: Opt[Loc]): Unit = + host.isErroneous = true + val message = reason match + case MemberLookup.Uncertainty.ValueShape => + msg"Cannot resolve member '$name' of a value with unknown shape." + case MemberLookup.Uncertainty.RecordOverwrite => + msg"Cannot resolve member '$name' across a computed key or unknown record spread." + resolError(host, message -> loc :: Nil) + def unresolvedRef(ref: UnresolvedRef): Unit = ref.prefixes.foreach: prefix => listenTerm(prefix): shape => - // A miss in one wildcard source is not an error: another may provide - // the name. A reference with no candidates is diagnosed by lowering. - shape match - case Marked(_: UnknownValueShape, _) => - ref.isErroneous = true - resolError(ref, msg"Cannot resolve member '${ref.id.name}' of a value with unknown shape." -> shape.toLoc :: Nil) - case Marked(record: RecordShape, _) if record.hasUnknownMember(ref.id.name) => - ref.isErroneous = true - resolError(ref, msg"Cannot resolve member '${ref.id.name}' across a computed key or unknown record spread." -> record.toLoc :: Nil) - case _ => shape.getMember(ref.id.name).foreach: (member, marks) => + shape.getMember(ref.id.name) match + case MemberLookup.Found(member, marks) => val candidate = prefix -> member.memberSymbol if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate publishMember(ref, member, ref.resSym, marks) + case MemberLookup.Missing => + // A known miss in one wildcard source is not an error: another may + // provide the name. Lowering diagnoses references with no candidates. + () + case MemberLookup.Unknown(reason, loc) => unknownMember(ref, ref.id.name, reason, loc) /** Inspect an overload set only once its definitions have all been published. */ private def completedClass(shape: SymShape)(selected: ClassDef => Unit, absent: () => Unit): Unit = @@ -444,31 +468,25 @@ class NewResolver: def newSel(sel: NewSel): Unit = log(s"newSel? sel = ${sel.showDbg}") - def member(info: Opt[MemberInfo], description: Message, loc: Opt[Loc]): Unit = info match - case S((bms, marks)) => + def member(info: MemberLookup, description: Message, loc: Opt[Loc]): Unit = info match + case MemberLookup.Found(bms, marks) => log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") if !sel.resolvedMembers.contains(bms.memberSymbol) then sel.resolvedMembers ::= bms.memberSymbol publishMember(sel, bms, sel.resSym, marks) - case N => + case MemberLookup.Missing => sel.isErroneous = true resolError(sel, msg"$description does not contain member '${sel.id.name}'" -> loc :: Nil) + case MemberLookup.Unknown(reason, loc) => unknownMember(sel, sel.id.name, reason, loc) sel.cls match case N => listenTerm(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") - shape match - case Marked(_: UnknownValueShape, _) => - sel.isErroneous = true - resolError(sel, msg"Cannot resolve member '${sel.id.name}' of a value with unknown shape." -> shape.toLoc :: Nil) - case Marked(record: RecordShape, _) if record.hasUnknownMember(sel.id.name) => - sel.isErroneous = true - resolError(sel, msg"Cannot resolve member '${sel.id.name}' across a computed key or unknown record spread." -> record.toLoc :: Nil) - case _ => member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc) + member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc) case S(cls) => listenClass(cls)((cd, marks) => val candidate = cd.sym -> marks if !sel.resolvedClasses.contains(candidate) then sel.resolvedClasses ::= candidate listenExt(cd.ext, ext => - member(DefnShape(cd, ext).getInstanceMember(sel.id.name).map(_.mapSecond(_ ::: marks)), + member(DefnShape(cd, ext).getInstanceMember(sel.id.name).withMarks(marks), msg"Class '${cd.sym.nme}'", cd.toLoc)) , sh => sel.isErroneous = true diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 6000d369cb..43b0f561c1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -69,8 +69,9 @@ object Marked: end Marked case class MarkedShape(sh: NonMarkedShape, mark: SomeMarks) extends TermShape: - protected def getMemberImpl(name: Str): Opt[MemberInfo] = - sh.getMember(name).map(_.mapSecond(_ ::: mark :: Nil)) + protected def getMemberImpl(name: Str): MemberLookup = + sh.getMember(name).withMarks(mark :: Nil) + override def isInstanceOfClass(cls: ClassLikeDef): Bool = sh.isInstanceOfClass(cls) def describe: Str = sh.describe def toLoc: Opt[Loc] = sh.toLoc object MarkedShape: @@ -129,13 +130,15 @@ object MarkedShape: end MarkedShape sealed trait TermShape extends Shape: - // Cache both hits and misses per shape, without materializing all inherited members. - private val membersCache = mutable.Map.empty[Str, Opt[MemberInfo]] - final def getMember(name: Str): Opt[MemberInfo] = + // Cache all lookup outcomes per shape, without materializing all inherited members. + private val membersCache = mutable.Map.empty[Str, MemberLookup] + final def getMember(name: Str): MemberLookup = membersCache.getOrElseUpdate(name, getMemberImpl(name)) - protected def getMemberImpl(name: Str): Opt[MemberInfo] + protected def getMemberImpl(name: Str): MemberLookup - def extendsCls(cls: ClassLikeDef): Bool = false + /** Whether this value is an instance of the nominal class. A saturated class + * value (e.g. a class without parameters) is still not an instance. */ + def isInstanceOfClass(cls: ClassLikeDef): Bool = false lazy val applicationHead: (NonAppTermShape, Ls[Marks]) = this match // case ds: DefnShape => ds @@ -226,9 +229,26 @@ end TermShape // case sel: SelShape => s"selection of ${sel.nme.name} from ${sel.receiver.describe}" // case sym: SymShape => s"symbol ${sym.sym.describe}" -// Record properties carry their value term as well as their symbol, avoiding -// mutable definition state on TermSymbol for these non-overloaded members. -type MemberInfo = (BlockMemberSymbol | RecordMember, Ls[Marks]) +/** Missing means definitely absent; Unknown must not be treated as a miss when + * searching wildcard opens, since it can introduce an additional candidate. */ +enum MemberLookup: + case Found(member: BlockMemberSymbol | RecordMember, marks: Ls[Marks]) + case Missing + case Unknown(reason: MemberLookup.Uncertainty, loc: Opt[Loc]) + + def withMarks(marks: Ls[Marks]): MemberLookup = this match + case Found(member, inner) => Found(member, inner ::: marks) + case _ => this + +object MemberLookup: + enum Uncertainty: + case ValueShape, RecordOverwrite + + /** Own members take precedence over inherited ones, including unknown spreads. */ + def inClass(defn: ClassLikeDef, ext: Opt[TermShape], name: Str): MemberLookup = + defn.body.members.get(name) match + case S(member) => Found(member, Nil) + case N => ext.fold[MemberLookup](Missing)(_.getMember(name)) extension (symbol: BlockMemberSymbol | TermSymbol) def describeMember: Str = symbol match @@ -242,18 +262,22 @@ extension (member: BlockMemberSymbol | RecordMember) class ErrShape(val err: ErrorReport) extends NonAppTermShape: def describe: Str = s"error: ${err.mainMsg}" - protected def getMemberImpl(name: Str): Opt[MemberInfo] = N + protected def getMemberImpl(name: Str): MemberLookup = MemberLookup.Missing def toLoc: Opt[Loc] = N class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using DebugPrinter) extends NonMarkedShape: - protected def getMemberImpl(name: Str): Opt[MemberInfo] = + protected def getMemberImpl(name: Str): MemberLookup = // An unsaturated term definition is just a concrete function shape - if !isSaturated then N + if !isSaturated then MemberLookup.Missing else applicationHead match case (ds: DefnShape, mss) => - ds.getInstanceMember(name).map(_.mapSecond(_ ::: mss)) - case _ => N + ds.getInstanceMember(name).withMarks(mss) + case _ => MemberLookup.Missing + override def isInstanceOfClass(cls: ClassLikeDef): Bool = + isSaturated && (applicationHead._1 match + case ds: DefnShape => ds.classExtends(cls) + case _ => false) def describe: Str = // s"application of ${receiver.describe}" s"instance of ${applicationHead._1.describe}" @@ -262,10 +286,14 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using // def target: Opt[AppTarget] class NewShape(val receiver: TermShape, val cls: ClassLikeSymbol, clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New)(using DebugPrinter) extends NonMarkedShape: - protected def getMemberImpl(name: Str): Opt[MemberInfo] = + protected def getMemberImpl(name: Str): MemberLookup = receiver match - case ds: DefnShape => ds.getInstanceMember(name).map(_.mapSecond(_ ::: clsMarks)) - case _ => N + case ds: DefnShape => ds.getInstanceMember(name).withMarks(clsMarks) + case _ => MemberLookup.Missing + override def isInstanceOfClass(cls: ClassLikeDef): Bool = + isSaturated && (receiver match + case ds: DefnShape => ds.classExtends(cls) + case _ => false) def describe: Str = // s"instantiation of ${receiver.describe}" s"instance of ${cls.defn.get.describeRef}" @@ -290,23 +318,26 @@ class ThisShape(val defn: Definition) extends NonAppTermShape: // TODO: make it not a TermShape? class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppTermShape: - override def extendsCls(cls: ClassLikeDef): Bool = - (defn is cls) || ext.exists(_.applicationHead._1.extendsCls(cls)) + override def isInstanceOfClass(cls: ClassLikeDef): Bool = + (defn is cls) || ext.exists(_.isInstanceOfClass(cls)) def describe: Str = s"${defn.describe}" - protected def getMemberImpl(name: Str): Opt[MemberInfo] = - defn.body.members.get(name).map(_ -> Nil).orElse(ext.flatMap(_.getMember(name))) + protected def getMemberImpl(name: Str): MemberLookup = + MemberLookup.inClass(defn, ext, name) def toLoc: Opt[Loc] = defn.toLoc class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTermShape: /** Instance lookup is shared by constructor calls and explicit `new`. * Inherited members retain their marks before the caller adds its captures. */ - def getInstanceMember(name: Str): Opt[MemberInfo] = defn match - case cd: ClassDef => - cd.body.members.get(name).map(_ -> Nil).orElse(ext.flatMap(_.getMember(name))) - case _: TermDefinition => ext.flatMap(_.getMember(name)) - case _ => N - override def extendsCls(cls: ClassLikeDef): Bool = - clsDef.contains(cls) || ext.exists(_.applicationHead._1.extendsCls(cls)) + def getInstanceMember(name: Str): MemberLookup = defn match + case cd: ClassDef => MemberLookup.inClass(cd, ext, name) + case _: TermDefinition => ext.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)) + case _ => MemberLookup.Missing + /** Nominal ancestry of this definition, independent of whether its value is an instance. */ + def classExtends(cls: ClassLikeDef): Bool = + clsDef.contains(cls) || ext.exists(_.isInstanceOfClass(cls)) + override def isInstanceOfClass(cls: ClassLikeDef): Bool = defn match + case _: ModuleOrObjectDef => classExtends(cls) + case _ => false lazy val clsDef = defn match case defn: ClassLikeDef => S(defn) case defn: TermDefinition => @@ -329,12 +360,12 @@ class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTer }'${defn.bsym.nme}'" // override def toString: String = s"DefnShape(${defn.describe} ${defn.bsym.nme})" override def toString: String = s"DefnShape(${defn.describe})" - protected def getMemberImpl(name: Str): Opt[MemberInfo] = + protected def getMemberImpl(name: Str): MemberLookup = defn match case defn: ModuleOrObjectDef => - defn.body.members.get(name).map(_ -> Nil).orElse(ext.flatMap(_.getMember(name))) + MemberLookup.inClass(defn, ext, name) case defn: TermDefinition => ??? - case _ => N + case _ => MemberLookup.Missing def toLoc: Opt[Loc] = defn.sym.toLoc /* @@ -374,7 +405,7 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) exte ((shape.source is source) && inner == marks) || shape.containsSpread(source, marks) def describe: Str = "tuple literal" def toLoc: Opt[Loc] = source.toLoc - protected def getMemberImpl(name: Str): Opt[MemberInfo] = ??? // Structural tuple members are not implemented yet. + protected def getMemberImpl(name: Str): MemberLookup = ??? // Structural tuple members are not implemented yet. /** The element of an opaque or widened spread can be any value. Keep this * alternative in the flow graph so other, known arguments cannot silently make @@ -383,7 +414,8 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) exte final case class UnknownValueShape(source: Term) extends NonAppTermShape: def describe: Str = "value of unknown shape" def toLoc: Opt[Loc] = source.toLoc - protected def getMemberImpl(name: Str): Opt[MemberInfo] = N + protected def getMemberImpl(name: Str): MemberLookup = + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, toLoc) object TupleShape: sealed trait Element @@ -401,8 +433,9 @@ object TupleShape: final case class Rest(shape: TupleShape, segments: Ls[Segment]) extends Element -/** A property selected from one record candidate. Mutable records expose a - * stable property identity, but their initializer is not a sound value shape. +/** A property selected from one record candidate. Carry its value term alongside + * its symbol rather than adding mutable definition state to TermSymbol. + * Mutable records expose a stable identity, but their initializer is not a sound value shape. */ final case class RecordMember(field: RcdField, mutable: Bool) @@ -413,22 +446,25 @@ final case class RecordMember(field: RcdField, mutable: Bool) final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element]) extends NonAppTermShape: def describe: Str = "record literal" def toLoc: Opt[Loc] = source.toLoc - private def lookup(name: Str): Either[Unit, Opt[(RecordMember, Ls[Marks])]] = - def loop(rest: Ls[RecordShape.Element]): Either[Unit, Opt[(RecordMember, Ls[Marks])]] = rest match - case Nil => Right(N) + protected def getMemberImpl(name: Str): MemberLookup = + import MemberLookup.* + def unknown = Unknown(Uncertainty.RecordOverwrite, toLoc) + def loop(rest: Ls[RecordShape.Element]): MemberLookup = rest match + case Nil => Missing case RecordShape.Field(field) :: rest => field.field match case Term.Lit(Tree.StrLit(key)) => - if key == name then Right(S(RecordMember(field, source.mut) -> Nil)) else loop(rest) - case _ => Left(()) - case RecordShape.Unknown :: _ => Left(()) - case RecordShape.Spread(shape, marks) :: rest => shape.lookup(name) match - case Left(_) => Left(()) - case Right(N) => loop(rest) - case Right(S((member, inner))) => - Right(S(member.copy(mutable = member.mutable || source.mut) -> (inner ::: marks :: Nil))) + if key == name then Found(RecordMember(field, source.mut), Nil) else loop(rest) + case _ => unknown + case RecordShape.Unknown :: _ => unknown + case RecordShape.Spread(shape, marks) :: rest => shape.getMember(name) match + case Unknown(_, _) => unknown + case Missing => loop(rest) + case Found(member: RecordMember, inner) => + Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil) + case Found(_: BlockMemberSymbol, _) => + // Record spreads recursively look up RecordShapes, which only create RecordMembers. + lastWords("Record lookup returned a nominal member") loop(elements.reverse) - def hasUnknownMember(name: Str): Bool = lookup(name).isLeft - protected def getMemberImpl(name: Str): Opt[MemberInfo] = lookup(name).toOption.flatten def containsSpread(record: Term.Rcd, marks: Marks): Bool = elements.exists: case RecordShape.Spread(shape, inner) => ((shape.source is record) && inner == marks) || shape.containsSpread(record, marks) @@ -445,9 +481,9 @@ object RecordShape: type IntroTerm = Term.Lit | Term.UnitVal | Term.Lam //| Term.New class IntroShape(val trm: IntroTerm) extends NonAppTermShape: def describe: Str = trm.describe - protected def getMemberImpl(name: Str): Opt[MemberInfo] = trm match - case _: Term.Lit | _: Term.UnitVal => N // TODO: methods on literals - case lam: Term.Lam => N // TODO: methods on lambdas + protected def getMemberImpl(name: Str): MemberLookup = trm match + case _: Term.Lit | _: Term.UnitVal => MemberLookup.Missing // TODO: methods on literals + case lam: Term.Lam => MemberLookup.Missing // TODO: methods on lambdas // case newTerm: Term.New => // Map.empty // TODO def toLoc: Opt[Loc] = trm.toLoc @@ -455,7 +491,7 @@ class IntroShape(val trm: IntroTerm) extends NonAppTermShape: sealed trait LitShape extends NonAppTermShape: self: Term.Lit => - protected def getMemberImpl(name: Str): Opt[MemberInfo] = N // TODO: methods on literals, e.g. string methods + protected def getMemberImpl(name: Str): MemberLookup = MemberLookup.Missing // TODO: methods on literals, e.g. string methods type ShapePublisher = Publisher[Shape] diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls new file mode 100644 index 0000000000..202df05ce2 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls @@ -0,0 +1,95 @@ +:. + + +// A duplicate body member makes elaboration discard all generated parameter fields. +:e +class Broken(val missing, val clash) { + val clash = 0 +} +if Broken(1, 2) is Broken(x, _) then x else 0 +//│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'clash'. +//│ ╟── Defined at: +//│ ║ l.6: class Broken(val missing, val clash) { +//│ ║ ^^^^^ +//│ ╟── Defined at: +//│ ║ l.7: val clash = 0 +//│ ╙── ^^^^^^^^^^^^^ +//│ = 0 + + +// A same-named body member is not the discarded constructor field either. +:e +class Replaced(val field) { + val field = {other: 0} +} +if Replaced({answer: 1}) is Replaced(x) then x.answer else 0 +//│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'field'. +//│ ╟── Defined at: +//│ ║ l.22: class Replaced(val field) { +//│ ║ ^^^^^ +//│ ╟── Defined at: +//│ ║ l.23: val field = {other: 0} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ = 0 + + +// The same recovery applies to inherited matches and forward pattern resolution. +:e +fun recover(value) = if value is BadBase(x, _) then x else 0 +class BadBase(val missing, val clash) { + val clash = 0 +} +class Child extends BadBase(1, 2) +recover(new Child) +//│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'clash'. +//│ ╟── Defined at: +//│ ║ l.39: class BadBase(val missing, val clash) { +//│ ║ ^^^^^ +//│ ╟── Defined at: +//│ ║ l.40: val clash = 0 +//│ ╙── ^^^^^^^^^^^^^ +//│ = 0 + + +// Valid inherited fields still propagate their value shapes to pattern bindings. +:expect 42 +class GoodBase(val field) +class GoodChild extends GoodBase({answer: 42}) +if (new GoodChild) is GoodBase(x) then x.answer else 0 +//│ = 42 + + +// A bare subclass's class value is not an instance of its base class. +:expect 0 +if GoodChild is GoodBase(x) then 1 else 0 +//│ = 0 + +// An object is an instance and exposes its inherited constructor field. +:expect 43 +class ObjectBase(val field) +object GoodObject extends ObjectBase(43) +if GoodObject is ObjectBase(x) then x else 0 +//│ = 43 + + +// TODO: Constructor-pattern bindings currently merge arguments from different +// subclass instantiations. Retain this limitation separately from recovery tests. +:fixme +class SharedBase(val field) +class LeftChild extends SharedBase({left: 1}) +class RightChild extends SharedBase({right: 2}) +if (new LeftChild) is SharedBase(x) then x.left else 0 +if (new RightChild) is SharedBase(x) then x.right else 0 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.81: if (new LeftChild) is SharedBase(x) then x.left else 0 +//│ ║ ^^^^^^ +//│ ╟── Record literal does not contain member 'left' +//│ ║ l.80: class RightChild extends SharedBase({right: 2}) +//│ ╙── ^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.82: if (new RightChild) is SharedBase(x) then x.right else 0 +//│ ║ ^^^^^^^ +//│ ╟── Record literal does not contain member 'right' +//│ ║ l.79: class LeftChild extends SharedBase({left: 1}) +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Records.mls b/hkmc2/shared/src/test/mlscript/newres/Records.mls index b3cca6ed14..a6662b0ba6 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Records.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Records.mls @@ -260,3 +260,27 @@ unknownValueOpen() //│ ║ l.249: let unknownValue = mut {value: {answer3: 0}} //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +// A known-absent name in one open does not mask a later source that supplies it. +:expect 28 +fun knownMissingOpen() = + let unrelated = {...{other: 0}} + let fallback = {wanted: 28} + open unrelated + open fallback + wanted +knownMissingOpen() +//│ = 28 + +// With no supplying source, a known-absent name still produces an error. +:e +fun allMissingOpen() = + let unrelated = {other: 0} + open unrelated + nonexistent +allMissingOpen() +//│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: nonexistent +//│ ║ l.281: nonexistent +//│ ╙── ^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 129cb17091e3fedcc93c762606beae340738a8b3 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 21:05:49 +0800 Subject: [PATCH 050/228] Document constructor-pattern context mixing as a migration blocker --- doc/new-resolution-suite-migration.md | 43 ++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index cb8e7510bd..ff6fd222ba 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -37,6 +37,8 @@ this first milestone. The unfinished WASM/type-erasure changes predate this migration and are retained. In particular, the typed-constructor-field capture assertion is still outstanding. Method calls across REPL blocks remain assigned to the other branch. +Constructor-pattern field propagation also has a serious context-mixing blocker, +documented below; its `:fixme` regression is not evidence that migration is complete. ## Straightforward fixes applied @@ -127,6 +129,44 @@ constructors, and recursive UPS matchers. Compare results and generated matcher structure with the existing tests. `ups/examples/HindleyMilner.mls` also timed out during the trial and needs an isolated reproducer before assigning its cause. +#### Blocker: constructor-pattern bindings mix unrelated instance contexts + +The final block of +[`newres/ConstructorFieldRecovery.mls`](../hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls) +reproduces this with ordinary, valid constructor patterns: + +```mlscript +class SharedBase(val field) +class LeftChild extends SharedBase({left: 1}) +class RightChild extends SharedBase({right: 2}) +if (new LeftChild) is SharedBase(x) then x.left else 0 +if (new RightChild) is SharedBase(x) then x.right else 0 +``` + +These expressions should resolve independently and return `1` and `2`. +Instead, each pattern binding receives field shapes from both subclasses. +Strict resolution rejects `x.left` because the right-hand record lacks `left`, +and rejects `x.right` because the left-hand record lacks `right`. Thus another +subclass's constructor arguments can make an otherwise valid selection fail. +This affects the precision of nominal constructor-field extraction even when +the scrutinee's concrete subclass is known; it is not confined to unsupported +pattern forms or malformed-class recovery. + +The regression remains under `:fixme` to keep this failure visible. It is a +serious migration blocker, not an accepted loss of resolution precision. The +observed failure is rejection of valid programs; silent miscompilation has not +been established by this reproducer. The exact point where receiver/constructor +context is lost still needs tracing through inherited member lookup and pattern +binding publication. + +The fix must preserve the matched receiver's constructor provenance through field +extraction. Do not suppress missing-member diagnostics, drop capture marks, or +pick one of the merged candidates to make this test pass. Completion requires +removing this `:fixme`, verifying both independent results, and covering multiple +instances of one class, sibling subclasses, objects extending a shared base, +and nested constructor patterns. Genuinely ambiguous receiver alternatives must +retain their diagnostics. + ### 3. Declared interfaces versus inferred values Evidence: the prior WASM `Basics` failure mixes a nominal annotated receiver with @@ -199,7 +239,8 @@ errors when a selected interpretation lacks the required capability. 4. **Complete pattern transfers and reference preservation.** Start with UCS conjunction/record/tuple cases, then compiled class patterns, then recursive and transforming UPS cases. Keep fixed-point behavior tested independently - from matcher code generation. + from matcher code generation. Resolve the constructor-pattern context-mixing + blocker above before treating nominal field extraction as complete. 5. **Complete type/interface and call validation.** Resolve the pending interface question, finish declared result/field shapes, port module/generic checks, and finish the WASM migration. Keep cross-block method work coordinated externally. From 70658460241ce8206ed21792e405e850004085d2 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 21:18:51 +0800 Subject: [PATCH 051/228] Unify record member symbols and centralize aggregate subscriptions --- AGENTS.md | 7 ++ .../main/scala/hkmc2/codegen/Lowering.scala | 33 ++++++--- .../scala/hkmc2/semantics/NewResolver.scala | 55 ++++++++------ .../main/scala/hkmc2/semantics/Shape.scala | 14 ++-- .../main/scala/hkmc2/semantics/Symbol.scala | 4 +- .../src/main/scala/hkmc2/semantics/Term.scala | 34 ++++----- .../scala/hkmc2/semantics/TypeShape.scala | 2 +- .../test/mlscript-compile/RecordProducers.mls | 7 ++ .../mlscript/newres/ClonedConstructors.mls | 39 ++++++++++ .../test/mlscript/newres/ImportedRecords.mls | 15 ++++ .../src/test/mlscript/newres/Records.mls | 71 +++++++++++++++++++ 11 files changed, 222 insertions(+), 59 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript-compile/RecordProducers.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/ImportedRecords.mls diff --git a/AGENTS.md b/AGENTS.md index 4e89958037..9a14ecd652 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,6 +73,13 @@ When appropriate, explain the history of what led to the current implementation, especially if it involves non-obvious decisions/trade-offs or if alternative approaches were considered and rejected. +Comments must be understandable from the current code alone, without the conversation, +commit history, or an earlier version of the implementation. Explain the concrete +invariant, behavior, or failure being prevented. Name the relevant data and operations; +avoid vague claims about where a "lifecycle" or "responsibility" belongs. If history +or a rejected alternative matters, include enough context to explain the tradeoff +after commits are squashed. + ## User-Facing Diagnostics diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 60050b756b..86e2953345 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -648,10 +648,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case _ => false /** Consume wildcard lookup results, retaining the receiver selected by resolution. */ - private def openSelection(ref: UnresolvedRef)(k: (Term, DefinitionSymbol[?]) => Block)(using LoweringCtx): Block = + private def openSelection(ref: UnresolvedRef)(k: (Term, BlockMemberSymbol, DefinitionSymbol[?]) => Block)(using LoweringCtx): Block = if ref.isErroneous then compError else ref.resolvedMembers.distinct match - case (prefix, _) :: Nil => ref.resolvedTargets.distinct match - case target :: Nil => k(prefix, target) + case (prefix, member) :: Nil => ref.resolvedTargets.distinct match + case target :: Nil => k(prefix, member, target) case targets => fail: ErrorReport(msg"Wildcard-open reference '${ref.id.name}' requires one resolved definition" -> ref.toLoc :: targets.map(target => msg"target: ${target.describeKind}" -> target.toLoc), @@ -662,7 +662,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case candidates => fail: ErrorReport(msg"Wildcard-open reference '${ref.id.name}' is ambiguous" -> ref.toLoc :: candidates.flatMap: (prefix, member) => - (msg"candidate: ${member.describeMember} '${member.nme}' defined here" -> member.toLoc) :: + (msg"candidate: ${member.describe} '${member.nme}' defined here" -> member.toLoc) :: (msg"Opened here" -> prefix.toLoc) :: Nil , source = Diagnostic.Source.Compilation) @@ -951,7 +951,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): , S(t), source = Diagnostic.Source.Compilation) compError case ref: UnresolvedRef => - openSelection(ref)((prefix, target) => setupSelection(prefix, ref.id, S(target))(k)) + openSelection(ref)((prefix, member, target) => + setupNamedSelection(prefix, memberIdent(ref.id, S(member)), S(target))(k)) case Capture(base, thru) => term(base, inStmtPos = inStmtPos)(k) case t @ st.Ref(sym) => @@ -1157,12 +1158,12 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): subTerm(rhs): r => assignSymbol(resolvedSelectionSymbol.getOrElse(sym), sym, r, k(unit), trm.toLoc) case ref: UnresolvedRef => - openSelection(ref): (prefix, target) => + openSelection(ref): (prefix, member, target) => target match case sym: TermSymbol => subTerm_nonTail(prefix): p => subTerm_nonTail(rhs): r => - AssignField(p, definitionIdent(ref.id, sym), castTo(r, sym.erasedType, ref.toLoc), k(unit))(S(sym)) + AssignField(p, memberIdent(ref.id, S(member)), castTo(r, sym.erasedType, ref.toLoc), k(unit))(S(sym)) case _ => fail: ErrorReport(msg"Assignment requires a term member" -> ref.toLoc :: Nil, source = Diagnostic.Source.Compilation) @@ -1245,7 +1246,17 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case Nil => fail: ErrorReport(msg"This selection of member '${id.name}' has no resolved target" -> sel.toLoc :: Nil, source = Diagnostic.Source.Compilation) - case target :: Nil => setupSelection(prefix, id, S(target))(k) + case target :: Nil => + // The property and the class stored in it may have different names. + // Resolution retains the selected member separately from its interpretation. + sel.resolvedMembers.map(_.nme).distinct match + case name :: Nil => + setupNamedSelection(prefix, new Tree.Ident(name).withLocOf(id), S(target))(k) + case Nil => setupSelection(prefix, id, S(target))(k) + case _ => fail: + ErrorReport(msg"This selection of member '${id.name}' has multiple resolved member names" -> sel.toLoc :: + sel.resolvedMembers.map(member => msg"candidate: ${member.describe} '${member.nme}'" -> member.toLoc), + source = Diagnostic.Source.Compilation) case targets => if strictResolution then fail: ErrorReport( @@ -1693,8 +1704,12 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): def setupSelection(prefix: Term, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]])(k: Result => Block)(using LoweringCtx): Block = + setupNamedSelection(prefix, disamb.fold(memberIdent(nme, N))(definitionIdent(nme, _)), disamb)(k) + + private def setupNamedSelection(prefix: Term, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]]) + (k: Result => Block)(using LoweringCtx): Block = subTerm(prefix): p => - k(Select(p, disamb.fold(memberIdent(nme, N))(definitionIdent(nme, _)))(disamb)( + k(Select(p, nme)(disamb)( !disamb.isDefined // * ^ We assume that resolved selections are well-behaved (will not yield undefined or debind a method) // || disamb.exists(_.defn.exists(_.hasDeclareModifier.isEmpty)) // * This checks `declare` members, which is normally unwanted diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index c5a51f9a95..8d4184fb96 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -47,6 +47,10 @@ class NewResolver: val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty private val selfShapes: mutable.Map[InnerSymbol, BaseShape] = mutable.Map.empty + // Aggregate nodes whose spread subscriptions have already been installed in this + // elaboration. Term equality is structural, so key by identity: equal expressions + // can receive their pending spread candidates through different listener lists. + private val aggregateProducers = mutable.Set.empty[Identity[Tup | Rcd]] val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty /** Interpret types through completed symbolic candidates, independently of term overloads. */ @@ -395,18 +399,16 @@ class NewResolver: flow: FlowSymbol, marks: Ls[Marks]): Unit = def publish(shape: Shape): Unit = if host.shapes.add(shape) then host.shapeListeners.foreach(_(shape)) + def definition(sym: BlockMemberSymbol): Unit = + publish(symShapes.getOrElseUpdate((sym, flow, marks), SymShape(sym, flow, marks))) member match - case member: BlockMemberSymbol => - publish(symShapes.getOrElseUpdate((member, flow, marks), SymShape(member, flow, marks))) - case RecordMember(field, mutable) => - // Selecting the property does not depend on knowing its value yet. - // Unlike a definition, a field introduces no capture boundary of its own. - if !host.resolvedTargets.contains(field.sym) then host.resolvedTargets ::= field.sym - if mutable then publish(UnknownValueShape(field.rhs)) - else listenTerm(field.rhs): shape => - shape.exit(marks) match - case NoShape => () - case shape: TermShape => publish(shape) + case member: BlockMemberSymbol => definition(member) + case RecordMember(field, false) => definition(field.sym) + case RecordMember(field, true) => + // Mutability affects the read, not member identity. Select the ordinary + // term interpretation even though writes make its value shape unknown. + if !host.resolvedTargets.contains(field.tsym) then host.resolvedTargets ::= field.tsym + publish(UnknownValueShape(field.rhs)) private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, loc: Opt[Loc]): Unit = host.isErroneous = true @@ -690,6 +692,22 @@ class NewResolver: case _ => () ) + /** Install one spread subscription graph per aggregate node in this elaboration. + * Register before following spreads, whose callbacks can synchronously request + * this node again. An empty candidate set may be waiting for a forward definition, + * so it cannot indicate whether subscriptions have been installed. Every new + * consumer receives existing candidates and then subsequent publications. + */ + private def listenAggregate(aggregate: Tup | Rcd, listener: Shape => Unit) + (start: (TermShape => Unit) => Unit): Unit = + val first = aggregateProducers.add(new Identity(aggregate)) + // A definition imported from another elaborator can already carry candidates. + // Replay them even on this resolver's first subscription, before producing deltas. + aggregate.shapes.foreach(listener) + if first then + start: shape => + if aggregate.shapes.add(shape) then aggregate.shapeListeners.foreach(_(shape)) + def listen(trm: Term, discardMarks: Bool = false)(listener: Shape => Unit): Unit = log(s"listen: trm = ${trm.showDbg}") trm.shapeListeners += listener @@ -698,13 +716,7 @@ class NewResolver: lastWords("Synthetic selections must not enter new resolution") case TyApp(underlying, _) => listen(underlying, discardMarks)(listener) case Mut(underlying) => listenTerm(underlying)(listener) - case tuple: Tup => - if tuple.shapeProducerStarted then tuple.shapes.foreach(listener) - else - // Start before subscribing: recursive listeners must reuse this host. - tuple.shapeProducerStarted = true - def publish(shape: TermShape): Unit = - if tuple.shapes.add(shape) then tuple.shapeListeners.foreach(_(shape)) + case tuple: Tup => listenAggregate(tuple, listener): publish => def expand(elems: Ls[Elem], reversed: Ls[TupleShape.Element]): Unit = elems match case Nil => // A sole spread preserves its operand's shape and context exactly. @@ -738,14 +750,11 @@ class NewResolver: // element layout. Their runtime spread is still permitted. expand(rest, TupleShape.Spread(TupleShape.unknown(term), marks) :: reversed) expand(tuple.fields, Nil) - case record: Rcd => - if record.shapeProducerStarted then record.shapes.foreach(listener) - else - record.shapeProducerStarted = true + case record: Rcd => listenAggregate(record, listener): publish => def expand(stats: Ls[Statement], reversed: Ls[RecordShape.Element]): Unit = stats match case Nil => val shape = RecordShape(record, reversed.reverse) - if record.shapes.add(shape) then record.shapeListeners.foreach(_(shape)) + publish(shape) case (field: RcdField) :: rest => expand(rest, RecordShape.Field(field) :: reversed) case RcdSpread(term) :: rest => val seen = mutable.Set.empty[TermShape] diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 43b0f561c1..139fe6403e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -250,13 +250,8 @@ object MemberLookup: case S(member) => Found(member, Nil) case N => ext.fold[MemberLookup](Missing)(_.getMember(name)) -extension (symbol: BlockMemberSymbol | TermSymbol) - def describeMember: Str = symbol match - case symbol: BlockMemberSymbol => symbol.describe - case symbol: TermSymbol => symbol.describeKind - extension (member: BlockMemberSymbol | RecordMember) - def memberSymbol: BlockMemberSymbol | TermSymbol = member match + def memberSymbol: BlockMemberSymbol = member match case symbol: BlockMemberSymbol => symbol case member: RecordMember => member.field.sym @@ -433,9 +428,10 @@ object TupleShape: final case class Rest(shape: TupleShape, segments: Ls[Segment]) extends Element -/** A property selected from one record candidate. Carry its value term alongside - * its symbol rather than adding mutable definition state to TermSymbol. - * Mutable records expose a stable identity, but their initializer is not a sound value shape. +/** Per-candidate read information, separate from the field's ordinary member symbol. + * A spread retains the original property identity, but copying it into a mutable + * record makes its initializer an unsound value shape. Thus mutability belongs to + * this lookup result, not to mutable state on the shared member/definition symbols. */ final case class RecordMember(field: RcdField, mutable: Bool) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 11578c681d..f6d087d048 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -332,7 +332,9 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[TypeOrTermDef], val nameIsMe else defnListeners += f - def toLoc: Option[Loc] = Loc(trees) + // Synthesized members (including constructor and record fields) have no + // definition tree, but their term interpretation still has a source name. + def toLoc: Option[Loc] = Loc(trees).orElse(tsym.flatMap(_.toLoc)) def symbols = tsym.toList ::: trees.collect: case t: Tree.TypeDef => t.symbol diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 8db5980389..5a4e984f83 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -129,13 +129,6 @@ object AnySel: end AnySel -sealed trait AggregateImpl extends ShapeHost: - self: Term.Tup | Term.Rcd => - /** Set by NewResolver before subscribing to spreads. An empty host can be - * pending, so its candidate set cannot indicate whether production started. - */ - private[semantics] var shapeProducerStarted: Bool = false - sealed trait AppImpl extends ResolvableImpl: self: Term.App => var resolvedTargets: Ls[flow.AppTarget] = Nil // * filled during flow analysis @@ -367,7 +360,7 @@ sealed trait NewRefImpl extends AnyRefImpl: sealed trait NewSelImpl extends NewResolvableImpl: self: Term.NewSel => - var resolvedMembers: Ls[BlockMemberSymbol | TermSymbol] = Nil // * filled during resolution + var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution // Class identity and captures must survive even when candidates share an inherited member. var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil def hasAmbiguousClass: Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: @@ -379,7 +372,7 @@ sealed trait UnresolvedRefImpl extends NewResolvableImpl: self: Term.UnresolvedRef => // Retain the receiver as well as the definition: two instances can expose // the same member symbol without denoting the same storage location. - var resolvedMembers: Ls[(Term, BlockMemberSymbol | TermSymbol)] = Nil + var resolvedMembers: Ls[(Term, BlockMemberSymbol)] = Nil enum Term extends Statement, ShapePublisher: @@ -422,7 +415,7 @@ enum Term extends Statement, ShapePublisher: case TyApp(lhs: Term, targs: Ls[Term]) (val typ: Opt[Type]) extends Term, ResolvableImpl case DynSel(prefix: Term, fld: Term, arrayIdx: Bool) - case Tup(fields: Ls[Elem])(val tree: Tree.Tup) extends Term, AggregateImpl + case Tup(fields: Ls[Elem])(val tree: Tree.Tup) extends Term, ShapeHost case Mut(underlying: Tup | Rcd | New | DynNew) case CtxTup(fields: Ls[Elem])(val tree: Tree.Tup) case IfLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit) extends Term, ShapeHost @@ -443,7 +436,7 @@ enum Term extends Statement, ShapePublisher: case Constrained(constraints: Ls[SubConstraint], body: Term) case WildcardTy(in: Opt[Term], out: Opt[Term]) case Blk(stats: Ls[Statement], res: Term) extends Term, BlkImpl - case Rcd(mut: Bool, stats: Ls[Statement]) extends Term, AggregateImpl + case Rcd(mut: Bool, stats: Ls[Statement]) extends Term, ShapeHost case Quoted(body: Term) case Unquoted(body: Term) case New(cls: Term, args: Ls[Term], rft: Opt[ClassSymbol -> ObjBody]) @@ -677,7 +670,6 @@ enum Term extends Statement, ShapePublisher: case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) case s: Spd => s.copy(term = s.term.mkClone) })(term.tree) - copy.shapeProducerStarted = term.shapeProducerStarted copyShapes(term, copy) case Mut(underlying) => Mut(underlying.mkClone.asInstanceOf[Tup | Rcd | New | DynNew]) case term @ CtxTup(fields) => CtxTup(fields.map { @@ -695,7 +687,6 @@ enum Term extends Statement, ShapePublisher: case blk: Blk => blk.mkBlkClone case term @ Rcd(mut, stats) => val copy = Rcd(mut, stats.map(_.mkClone)) - copy.shapeProducerStarted = term.shapeProducerStarted copyShapes(term, copy) case Quoted(body) => Quoted(body.mkClone) case Unquoted(body) => Unquoted(body.mkClone) @@ -1273,8 +1264,10 @@ final case class LetDecl(sym: LocalVarSymbol | TermSymbol, annotations: Ls[Annot * to evaluate its value. Computed keys also have an identity, but cannot be * selected statically until their key is known. Cloning preserves this identity. */ -final case class RcdField(field: Term, rhs: Term, sym: TermSymbol) extends Statement: - require((sym.k is RecordField) && sym.owner.isEmpty) +final case class RcdField(field: Term, rhs: Term, sym: BlockMemberSymbol) extends Statement: + // RcdField.apply completes the member's sole term interpretation before exposing it. + val tsym: TermSymbol = sym.tsym.get + require((tsym.k is RecordField) && tsym.owner.isEmpty && tsym.defn.exists(_.sym is sym)) field match case Term.Lit(Tree.StrLit(name)) => require(sym.nme == name) case _ => () @@ -1286,7 +1279,16 @@ object RcdField: case _ => "computed field" val id = new Tree.Ident(name) id.withLocOf(field) - RcdField(field, rhs, TermSymbol(RecordField, N, id, erasedType = N)) + val sym = new BlockMemberSymbol(name, Nil) + val tsym = TermSymbol(RecordField, N, id, erasedType = N) + sym.tsym = S(tsym) + // fromBMS exits the selected TermSymbol's capture when reading a definition. + // Supply its matching entry so that the record's enclosing marks are preserved. + // Record lowering evaluates rhs directly; this capture only describes value flow. + tsym.defn = S(TermDefinition(RecordField, sym, tsym, Nil, N, N, + S(Term.Capture(rhs, tsym)), TermDefFlags.empty, Modulefulness.none, Nil, N)) + sym.complete() + RcdField(field, rhs, sym) final case class RcdSpread(rcd: Term) extends Statement final case class DefineVar(sym: LocalSymbol | TermSymbol, rhs: Term) extends Statement diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index deda903f7b..d90bf24e12 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -43,7 +43,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => else if ambiguousReceiver || shapes.sizeCompare(1) > 0 then val candidates = source.withoutCaptures match case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.map: (prefix, member) => - msg"candidate: ${member.describeMember}" -> member.toLoc + msg"candidate: ${member.describe}" -> member.toLoc case _ => shapes.toList.flatMap: case Nominal(defn) => msg"candidate: ${defn.sym.describeKind}" -> defn.toLoc :: Nil case Alias(symbol, _) => msg"candidate: ${symbol.describeKind}" -> symbol.toLoc :: Nil diff --git a/hkmc2/shared/src/test/mlscript-compile/RecordProducers.mls b/hkmc2/shared/src/test/mlscript-compile/RecordProducers.mls new file mode 100644 index 0000000000..66eec6ecf3 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/RecordProducers.mls @@ -0,0 +1,7 @@ +#lang(0.3.x, strictResolution: true) + +module RecordProducers { + val fields = {...{value: 36}} + // This selection starts the record producer in the imported file's elaborator. + val observed = fields.value +} diff --git a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls index 4a27d6e44a..83a04ba299 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls @@ -29,3 +29,42 @@ fun captured(module Inner: Nested, x) = :expect [true, false] [captured(Nested, Nested.Wrapped(3)), captured(Nested, Nested.Wrapped(4))] //│ = [true, false] + + +// A record property has an ordinary member/term interpretation, preserved in cloned patterns. +let constructors = {make: Direct} +fun throughRecord(x) = if x is @compile constructors.make(3) then true else false +//│ constructors = {make: fun Direct { class: class Direct }} + +:expect [true, false] +[throughRecord(Direct(3)), throughRecord(Direct(4))] +//│ = [true, false] + + +// Wildcard constructor lookup retains the record property's name as well. +fun openedRecord(x) = + open constructors + if x is @compile make(3) then true else false + +:expect [true, false] +[openedRecord(Direct(3)), openedRecord(Direct(4))] +//│ = [true, false] + +// Calling the stored constructor is a term interpretation. +:expect 35 +constructors.make(35).value +//│ = 35 + +// Static `new` requires a class interpretation; record properties supply terms. +:e +new constructors.make(35) +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.60: new constructors.make(35) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Record field symbol 'make' cannot be instantiated with keyword 'new'. +//│ ║ l.35: let constructors = {make: Direct} +//│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ║ l.60: new constructors.make(35) +//│ ╙── ^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/ImportedRecords.mls b/hkmc2/shared/src/test/mlscript/newres/ImportedRecords.mls new file mode 100644 index 0000000000..d99bb0a7e4 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ImportedRecords.mls @@ -0,0 +1,15 @@ +:. + +import "../../mlscript-compile/RecordProducers.mls" + +// The imported record already has candidates before this elaborator subscribes. +:expect [36, 36] +[RecordProducers.fields.value, RecordProducers.observed] +//│ = [36, 36] + +// Later subscribers also replay the same property identity and value shape. +:expect 36 +let importedFields = {...RecordProducers.fields} +importedFields.value +//│ = 36 +//│ importedFields = {value: 36} diff --git a/hkmc2/shared/src/test/mlscript/newres/Records.mls b/hkmc2/shared/src/test/mlscript/newres/Records.mls index a6662b0ba6..6675b76037 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Records.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Records.mls @@ -284,3 +284,74 @@ allMissingOpen() //│ ║ l.281: nonexistent //│ ╙── ^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + +// Structurally equal record nodes need independent producers while their spread is pending. +:expect [29, 29] +fun repeatedRecords(xs) = + let a = {...xs} + let b = {...xs} + [a.value, b.value] +repeatedRecords({value: 29}) +//│ = [29, 29] + +// Multiple subscribers can arrive before a forward spread has any candidates. +:expect [30, 30] +let pendingRecord = {...laterRecord()} +fun readPendingRecord() = [pendingRecord.value, pendingRecord.value] +fun laterRecord() = {value: 30} +readPendingRecord() +//│ = [30, 30] +//│ pendingRecord = {value: 30} + +// A mutable copy keeps property identity without mutating the source's value information. +:expect [31, 32] +let stableRecord = {value: 31} +let mutableCopy = mut {...stableRecord} +set mutableCopy.value = 32 +[stableRecord.value, mutableCopy.value] +//│ = [31, 32] +//│ mutableCopy = {value: 32} +//│ stableRecord = {value: 31} + +// TODO: Track mutable field writes; sharing a member symbol must not reuse its initializer. +:fixme +let stableNested = {value: {before: 33}} +let mutableNested = mut {...stableNested} +set mutableNested.value = {after: 34} +[stableNested.value.before, mutableNested.value.after] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.322: [stableNested.value.before, mutableNested.value.after] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'after' of a value with unknown shape. +//│ ║ l.319: let stableNested = {value: {before: 33}} +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ mutableNested = {value: {after: 34}} +//│ stableNested = {value: {before: 33}} + +// Record fields participate in ordinary member ambiguity diagnostics, including both origins. +:e +fun ambiguousRecordOpen() = + let firstRecord = {shared: 1} + let secondRecord = {shared: 2} + open firstRecord + open secondRecord + shared +ambiguousRecordOpen() +//│ ╔══[COMPILATION ERROR] Wildcard-open reference 'shared' is ambiguous +//│ ║ l.340: shared +//│ ║ ^^^^^^ +//│ ╟── candidate: record field 'shared' defined here +//│ ║ l.336: let firstRecord = {shared: 1} +//│ ║ ^^^^^^ +//│ ╟── Opened here +//│ ║ l.338: open firstRecord +//│ ║ ^^^^^^^^^^^ +//│ ╟── candidate: record field 'shared' defined here +//│ ║ l.337: let secondRecord = {shared: 2} +//│ ║ ^^^^^^ +//│ ╟── Opened here +//│ ║ l.339: open secondRecord +//│ ╙── ^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 42247efc6416f5b3b7ee5dc4d2ce43b7080131e0 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 21:24:44 +0800 Subject: [PATCH 052/228] Explain record and spread regression comments with concrete failure cases --- .../main/scala/hkmc2/codegen/Lowering.scala | 5 +-- .../scala/hkmc2/semantics/NewResolver.scala | 27 ++++++++------- .../main/scala/hkmc2/semantics/Shape.scala | 8 ++--- .../main/scala/hkmc2/semantics/Symbol.scala | 4 +-- .../src/main/scala/hkmc2/semantics/Term.scala | 10 +++--- .../test/mlscript-compile/RecordProducers.mls | 3 +- .../mlscript/newres/ClonedConstructors.mls | 16 +++++---- .../test/mlscript/newres/ImportedRecords.mls | 5 +-- .../src/test/mlscript/newres/Records.mls | 33 +++++++++++-------- .../src/test/mlscript/newres/SpreadCalls.mls | 4 ++- 10 files changed, 67 insertions(+), 48 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 86e2953345..5a9b7e72a4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1247,8 +1247,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): ErrorReport(msg"This selection of member '${id.name}' has no resolved target" -> sel.toLoc :: Nil, source = Diagnostic.Source.Compilation) case target :: Nil => - // The property and the class stored in it may have different names. - // Resolution retains the selected member separately from its interpretation. + // For `r = {make: C}`, the pattern head `r.make` resolves to class C, + // but the generated property read must use `make`. resolvedMembers holds + // the property names; target identifies the definition used by the pattern. sel.resolvedMembers.map(_.nme).distinct match case name :: Nil => setupNamedSelection(prefix, new Tree.Ident(name).withLocOf(id), S(target))(k) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 8d4184fb96..469c673442 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -47,9 +47,10 @@ class NewResolver: val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty private val selfShapes: mutable.Map[InnerSymbol, BaseShape] = mutable.Map.empty - // Aggregate nodes whose spread subscriptions have already been installed in this - // elaboration. Term equality is structural, so key by identity: equal expressions - // can receive their pending spread candidates through different listener lists. + // Tuple and record AST nodes for which we have subscribed to spread operands. + // Each node owns its shapes and listeners. Comparing nodes by structural equality + // would skip subscriptions for a second equal expression, leaving its listeners + // without results; compare their identities instead. private val aggregateProducers = mutable.Set.empty[Identity[Tup | Rcd]] val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty @@ -405,8 +406,9 @@ class NewResolver: case member: BlockMemberSymbol => definition(member) case RecordMember(field, false) => definition(field.sym) case RecordMember(field, true) => - // Mutability affects the read, not member identity. Select the ordinary - // term interpretation even though writes make its value shape unknown. + // Assignments can replace the stored value, so its initializer no longer + // determines what a read returns. Publish the known property symbol for + // selection/assignment, but an unknown shape for the value being read. if !host.resolvedTargets.contains(field.tsym) then host.resolvedTargets ::= field.tsym publish(UnknownValueShape(field.rhs)) @@ -692,17 +694,18 @@ class NewResolver: case _ => () ) - /** Install one spread subscription graph per aggregate node in this elaboration. - * Register before following spreads, whose callbacks can synchronously request - * this node again. An empty candidate set may be waiting for a forward definition, - * so it cannot indicate whether subscriptions have been installed. Every new - * consumer receives existing candidates and then subsequent publications. + /** Subscribe to spread operands once for each tuple or record AST node. Record + * the node before calling start: a recursive spread can call listen on the same + * node before start returns. Testing shapes.isEmpty would not prevent duplicate + * subscriptions while the node is waiting for a forward definition. Deliver + * cached shapes to each listener, which is already registered for future shapes. */ private def listenAggregate(aggregate: Tup | Rcd, listener: Shape => Unit) (start: (TermShape => Unit) => Unit): Unit = val first = aggregateProducers.add(new Identity(aggregate)) - // A definition imported from another elaborator can already carry candidates. - // Replay them even on this resolver's first subscription, before producing deltas. + // An imported definition can already have shapes computed by its own elaborator. + // Send those shapes to this listener even if first is true; recomputing the same + // shapes below will not notify it, because shapes.add rejects duplicates. aggregate.shapes.foreach(listener) if first then start: shape => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 139fe6403e..658a59ea49 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -428,10 +428,10 @@ object TupleShape: final case class Rest(shape: TupleShape, segments: Ls[Segment]) extends Element -/** Per-candidate read information, separate from the field's ordinary member symbol. - * A spread retains the original property identity, but copying it into a mutable - * record makes its initializer an unsound value shape. Thus mutability belongs to - * this lookup result, not to mutable state on the shared member/definition symbols. +/** A field found by record member lookup. Spreading r into `mut {...r}` reuses + * its field symbols, but writes can change the copied values even if r is immutable. + * Store mutability on this lookup result: the mutable copy's initializer no longer + * determines its value shape, while reads from an immutable r can still use it. */ final case class RecordMember(field: RcdField, mutable: Bool) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index f6d087d048..cb58993363 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -332,8 +332,8 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[TypeOrTermDef], val nameIsMe else defnListeners += f - // Synthesized members (including constructor and record fields) have no - // definition tree, but their term interpretation still has a source name. + // For constructor and record fields, trees is empty because the member was + // synthesized. Use the source location of its TermSymbol to locate diagnostics. def toLoc: Option[Loc] = Loc(trees).orElse(tsym.flatMap(_.toLoc)) def symbols = tsym.toList ::: trees.collect: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 5a4e984f83..f2748d92d6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -1265,7 +1265,8 @@ final case class LetDecl(sym: LocalVarSymbol | TermSymbol, annotations: Ls[Annot * selected statically until their key is known. Cloning preserves this identity. */ final case class RcdField(field: Term, rhs: Term, sym: BlockMemberSymbol) extends Statement: - // RcdField.apply completes the member's sole term interpretation before exposing it. + // The two-argument RcdField.apply sets sym.tsym and its definition before + // constructing this node. Lowering clones reuse the same initialized symbols. val tsym: TermSymbol = sym.tsym.get require((tsym.k is RecordField) && tsym.owner.isEmpty && tsym.defn.exists(_.sym is sym)) field match @@ -1282,9 +1283,10 @@ object RcdField: val sym = new BlockMemberSymbol(name, Nil) val tsym = TermSymbol(RecordField, N, id, erasedType = N) sym.tsym = S(tsym) - // fromBMS exits the selected TermSymbol's capture when reading a definition. - // Supply its matching entry so that the record's enclosing marks are preserved. - // Record lowering evaluates rhs directly; this capture only describes value flow. + // fromBMS removes a capture mark for tsym when it reads this definition. + // Wrap rhs in Capture to supply that mark, so the entry and exit cancel and + // leave the enclosing call-site marks intact. Lowering evaluates RcdField.rhs; + // it does not evaluate this synthetic definition separately. tsym.defn = S(TermDefinition(RecordField, sym, tsym, Nil, N, N, S(Term.Capture(rhs, tsym)), TermDefFlags.empty, Modulefulness.none, Nil, N)) sym.complete() diff --git a/hkmc2/shared/src/test/mlscript-compile/RecordProducers.mls b/hkmc2/shared/src/test/mlscript-compile/RecordProducers.mls index 66eec6ecf3..846adda739 100644 --- a/hkmc2/shared/src/test/mlscript-compile/RecordProducers.mls +++ b/hkmc2/shared/src/test/mlscript-compile/RecordProducers.mls @@ -2,6 +2,7 @@ module RecordProducers { val fields = {...{value: 36}} - // This selection starts the record producer in the imported file's elaborator. + // Resolving this selection records the `value` field in the shape of `fields`, + // before another file imports this module. val observed = fields.value } diff --git a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls index 83a04ba299..3957f95687 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls @@ -31,7 +31,8 @@ fun captured(module Inner: Nested, x) = //│ = [true, false] -// A record property has an ordinary member/term interpretation, preserved in cloned patterns. +// @compile clones the constructor reference when generating the matcher. It must +// still read constructors.make, even though the class stored there is named Direct. let constructors = {make: Direct} fun throughRecord(x) = if x is @compile constructors.make(3) then true else false //│ constructors = {make: fun Direct { class: class Direct }} @@ -41,7 +42,7 @@ fun throughRecord(x) = if x is @compile constructors.make(3) then true else fals //│ = [true, false] -// Wildcard constructor lookup retains the record property's name as well. +// The opened name `make` must compile to a read of constructors.make. fun openedRecord(x) = open constructors if x is @compile make(3) then true else false @@ -50,21 +51,22 @@ fun openedRecord(x) = [openedRecord(Direct(3)), openedRecord(Direct(4))] //│ = [true, false] -// Calling the stored constructor is a term interpretation. +// Calling the constructor function stored in `make` creates a Direct instance. :expect 35 constructors.make(35).value //│ = 35 -// Static `new` requires a class interpretation; record properties supply terms. +// Static `new` currently requires a class declaration. The `make` record field +// holds a constructor function, so it cannot be used with this syntax. :e new constructors.make(35) //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.60: new constructors.make(35) +//│ ║ l.62: new constructors.make(35) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Record field symbol 'make' cannot be instantiated with keyword 'new'. -//│ ║ l.35: let constructors = {make: Direct} +//│ ║ l.36: let constructors = {make: Direct} //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.60: new constructors.make(35) +//│ ║ l.62: new constructors.make(35) //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/ImportedRecords.mls b/hkmc2/shared/src/test/mlscript/newres/ImportedRecords.mls index d99bb0a7e4..0cde56684a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ImportedRecords.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ImportedRecords.mls @@ -2,12 +2,13 @@ import "../../mlscript-compile/RecordProducers.mls" -// The imported record already has candidates before this elaborator subscribes. +// Resolving RecordProducers.observed in the imported file already discovered the +// `value` field. Our first read of fields.value must receive that cached information. :expect [36, 36] [RecordProducers.fields.value, RecordProducers.observed] //│ = [36, 36] -// Later subscribers also replay the same property identity and value shape. +// Spreading the imported record must also expose its `value` field on the copy. :expect 36 let importedFields = {...RecordProducers.fields} importedFields.value diff --git a/hkmc2/shared/src/test/mlscript/newres/Records.mls b/hkmc2/shared/src/test/mlscript/newres/Records.mls index 6675b76037..7274b4d30b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Records.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Records.mls @@ -204,7 +204,7 @@ unknownOpen() {...unknownKeys, a: 25}.a //│ = 25 -// Forward producers remain pending until their body is available. +// Resolving forwardRecord.answer must wait for the body of makeRecord below. :expect 26 let forwardRecord = {...makeRecord()} fun makeRecord() = {answer: 26} @@ -286,7 +286,10 @@ allMissingOpen() //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -// Structurally equal record nodes need independent producers while their spread is pending. +// Both a.value and b.value must resolve to the field supplied by {value: 29}. +// The two {...xs} expressions have equal syntax but separate AST nodes and listeners. +// Treating the second expression as already processed would skip its subscription +// to xs, leaving b.value unresolved. :expect [29, 29] fun repeatedRecords(xs) = let a = {...xs} @@ -295,7 +298,8 @@ fun repeatedRecords(xs) = repeatedRecords({value: 29}) //│ = [29, 29] -// Multiple subscribers can arrive before a forward spread has any candidates. +// Both reads of pendingRecord.value are elaborated before laterRecord's body. +// Once its body is available, the resolver must supply the field to both reads. :expect [30, 30] let pendingRecord = {...laterRecord()} fun readPendingRecord() = [pendingRecord.value, pendingRecord.value] @@ -304,7 +308,7 @@ readPendingRecord() //│ = [30, 30] //│ pendingRecord = {value: 30} -// A mutable copy keeps property identity without mutating the source's value information. +// Updating mutableCopy.value must leave stableRecord.value equal to 31. :expect [31, 32] let stableRecord = {value: 31} let mutableCopy = mut {...stableRecord} @@ -314,23 +318,26 @@ set mutableCopy.value = 32 //│ mutableCopy = {value: 32} //│ stableRecord = {value: 31} -// TODO: Track mutable field writes; sharing a member symbol must not reuse its initializer. +// TODO: Infer the new value of mutableNested.value after the assignment. Until then, +// report its unknown value shape rather than assuming it still contains `before`. +// The unchanged stableNested.value.before must remain resolvable. :fixme let stableNested = {value: {before: 33}} let mutableNested = mut {...stableNested} set mutableNested.value = {after: 34} [stableNested.value.before, mutableNested.value.after] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.322: [stableNested.value.before, mutableNested.value.after] +//│ ║ l.328: [stableNested.value.before, mutableNested.value.after] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'after' of a value with unknown shape. -//│ ║ l.319: let stableNested = {value: {before: 33}} +//│ ║ l.325: let stableNested = {value: {before: 33}} //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ mutableNested = {value: {after: 34}} //│ stableNested = {value: {before: 33}} -// Record fields participate in ordinary member ambiguity diagnostics, including both origins. +// Both opened records define `shared`, so the reference is ambiguous. The error +// must point to each field definition and the corresponding open statement. :e fun ambiguousRecordOpen() = let firstRecord = {shared: 1} @@ -340,18 +347,18 @@ fun ambiguousRecordOpen() = shared ambiguousRecordOpen() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'shared' is ambiguous -//│ ║ l.340: shared +//│ ║ l.347: shared //│ ║ ^^^^^^ //│ ╟── candidate: record field 'shared' defined here -//│ ║ l.336: let firstRecord = {shared: 1} +//│ ║ l.343: let firstRecord = {shared: 1} //│ ║ ^^^^^^ //│ ╟── Opened here -//│ ║ l.338: open firstRecord +//│ ║ l.345: open firstRecord //│ ║ ^^^^^^^^^^^ //│ ╟── candidate: record field 'shared' defined here -//│ ║ l.337: let secondRecord = {shared: 2} +//│ ║ l.344: let secondRecord = {shared: 2} //│ ║ ^^^^^^ //│ ╟── Opened here -//│ ║ l.339: open secondRecord +//│ ║ l.346: open secondRecord //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index 1147bb00de..5f7d89936a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -238,7 +238,9 @@ fun invokeRest(f, x, ...tail) = f() invokeRest(...new Array(() => 97), Box(0)) //│ = 97 -// Structurally equal tuple nodes must each publish delayed spread candidates. +// When repeatedTuples receives [Box(98)], both a and b must acquire its element +// shape so that both `.value` selections resolve. Although the tuple expressions +// have equal syntax, resolving a does not notify the listeners attached to b. :expect [98, 98] fun repeatedTuples(xs) = let a = [0, ...xs] From 670d826efe77cb9e39234c310f8064c37a7122ed Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 21:48:36 +0800 Subject: [PATCH 053/228] Add dynamic resolution shapes and relax general test suite ambiguity checks --- doc/new-resolution-suite-migration.md | 68 ++++---- .../shared/src/main/scala/hkmc2/Config.scala | 2 +- .../main/scala/hkmc2/codegen/ErasedType.scala | 1 + .../main/scala/hkmc2/codegen/Lowering.scala | 127 +++++++++------ .../scala/hkmc2/semantics/Elaborator.scala | 1 + .../main/scala/hkmc2/semantics/Importer.scala | 2 + .../scala/hkmc2/semantics/NewResolver.scala | 34 +++- .../main/scala/hkmc2/semantics/Resolver.scala | 4 +- .../main/scala/hkmc2/semantics/Shape.scala | 24 ++- .../src/main/scala/hkmc2/semantics/Term.scala | 19 ++- .../scala/hkmc2/semantics/TypeShape.scala | 3 +- .../src/main/scala/hkmc2/syntax/Keyword.scala | 1 + .../main/scala/hkmc2/syntax/ParseRule.scala | 1 + hkmc2/shared/src/test/mlscript/apps/.mls | 2 +- hkmc2/shared/src/test/mlscript/basics/.mls | 2 +- .../src/test/mlscript/basics/SetIndented.mls | 4 +- hkmc2/shared/src/test/mlscript/codegen/.mls | 2 +- .../src/test/mlscript/codegen/Primes.mls | 11 +- .../src/test/mlscript/newres/Assignments.mls | 2 +- .../src/test/mlscript/newres/Dynamic.mls | 152 ++++++++++++++++++ .../test/mlscript/newres/loose/Targets.mls | 84 ++++++++++ hkmc2/shared/src/test/mlscript/ucs/.mls | 2 +- hkmc2/shared/src/test/mlscript/ups/.mls | 2 +- hkmc2/shared/src/test/mlscript/wasm/.mls | 2 +- 24 files changed, 440 insertions(+), 112 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/Dynamic.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index ff6fd222ba..81f1924b37 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -17,13 +17,15 @@ exclude the new shared `.mls` configuration files and new regression tests. | Total | 185 | 187 | 372 | Migrated files start with `:.`. Each suite's `.mls` loads the common language -configuration and enables strict resolution; nested directories inherit through -`:..`. The suite defaults disable JS execution so each file retains its original -execution flags. A parser-only test must not acquire runtime execution merely +configuration and uses non-strict resolution; nested directories inherit through +`:..`. `newres` retains strict resolution, with explicit non-strict coverage in +`newres/loose`. Non-strict resolution permits multiple selection/call targets, +but still rejects missing targets and known invalid operations. The suite defaults +disable JS execution so each file retains its original execution flags. A parser-only test must not acquire runtime execution merely because its resolution configuration changes. Blocked files retain their complete original sources and golden outputs. No new -`:todo`, `:fixme`, `:ignore`, or loose-resolution exception hides a migration +per-file `:todo`, `:fixme`, `:ignore`, or resolution-mode exception hides a migration failure. Existing expected failures remain visible. The inventory below records trial observations, not newly accepted failures. @@ -74,30 +76,40 @@ explicitly bound `Some5` wins over a wildcard open, yielding `"555"` rather than ### 1. Structural members and opaque values -Evidence: `basics/LiteralSelection.mls` fails on `arr.1` and `obj.a` because -tuple and record member lookup remains unimplemented in `TupleShape` and -`IntroShape`. The current `MemberInfo` requires a `BlockMemberSymbol`, which a -structural field need not have. `codegen/ImportJSModule.mls`, `Pwd.mls`, and many -app worksheets instead have opaque external results with no member shapes at all. - -**Question:** should ordinary member selection on an opaque external value be -accepted dynamically, or require a declaration/explicit dynamic selection? -This is different from a known record's structural member. - -**Proposal:** preserve strict resolution for ordinary source selections. Add a -member-target representation distinguishing nominal definitions from structural -fields, and an explicit opaque/dynamic category where the language authorizes -it. Structural targets carry their field/index path and value-shape publisher; -they must not invent nominal symbols or discard the selected value's provenance. -Resolve record overwrites and spreads in source order, independently of listener -arrival order. Lowering consumes those targets without doing lookup. Publish -external signatures through the same shape infrastructure; keep explicitly -dynamic operations on a dedicated path. - -Acceptance cases: duplicate record fields, spreads arriving late, tuple indices, -field values used as receivers, field assignment, missing fields, and JS imports. -Decide whether wildcard opens accept structural records as part of this work; -do not silently treat an unsupported structural open as an empty module. +Record fields now have their own `BlockMemberSymbol`s. Member lookup follows +record overwrites and spreads in source order and retains the selected value's +shape and captures. Structural tuple member lookup remains unimplemented. + +**Accepted policy:** JavaScript imports (including package imports), `globalThis`, +and explicit dynamic selection/instantiation introduce `DynShape`. Ordinary +selections and calls on these values are checked at runtime and yield dynamic +values. `foo() as dyn` explicitly gives a result this behavior; `fun bar(x: dyn)` +provides it to a parameter independently of call-site inference. Type aliases may +also denote `dyn`. These rules apply in both strict and non-strict resolution. +Dynamic instantiation uses the existing `new!` syntax. + +Call validation still depends on the shapes available during elaboration. An +unused callback, a callback used in a later REPL block, or an unsupported function +signature may have no inferred callee shapes yet. Such an empty set does not prove +that the callee is non-callable. Diagnosing all targetless applications requires +separating pending or incomplete inference from a completed lookup with no target; +`dyn` does not fill arbitrary empty shape sets. + +Dynamic selections have no fabricated nominal definition. Lowering retains their +runtime receiver and property name. Dynamic values propagate through record and +tuple spreads and unique wildcard opens. Competing wildcard-open receivers still +require disambiguation: choosing one would change which runtime object is read. +Class projections, constructor patterns, and type references also retain their +requirements for a known, unambiguous identity. + +`DynShape` is distinct from `UnknownValueShape`: recursive widening, mutable +record reads, and other losses of inference precision do not automatically +license dynamic member lookup. A known missing member in another receiver +candidate still reports an error even if a dynamic candidate is also present. +The constructor-pattern context-mixing blocker below therefore remains visible. + +Regression coverage is in `newres/Dynamic.mls`, `newres/Records.mls`, +`newres/SpreadCalls.mls`, and `newres/loose/Targets.mls`. ### 2. Pattern transfer and synthesized references diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index 106d3fe803..466348cf76 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -97,7 +97,7 @@ object Config: case class Language( allowUnresolvedAccesses: Bool, useNewResolution: Bool, - strictResolution: Bool, // Whether the new resolution is done pedantically + strictResolution: Bool, // Reject multiple resolved selection targets as well as missing targets typeCheck: Opt[TypeChecking], )(val versionName: Str) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala index ad75b7d1cd..e035910dcb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala @@ -259,6 +259,7 @@ object ErasedType: // * An intersection is never decomposed: narrowing to one member would call for a GLB, which this lattice // * cannot express. case CompType(_, _, false) => S(ErasedType.Unknown) + case DynTy() => S(ErasedType.Unknown) case UnitVal() => S(ErasedType.Unit) // * A written arrow denotes a function value, and every function value is a `Function`. case FunTy(_, _, _) => S(ErasedType.Function(rsc = S(false))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 5a9b7e72a4..4103b160b3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -648,10 +648,17 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case _ => false /** Consume wildcard lookup results, retaining the receiver selected by resolution. */ - private def openSelection(ref: UnresolvedRef)(k: (Term, BlockMemberSymbol, DefinitionSymbol[?]) => Block)(using LoweringCtx): Block = - if ref.isErroneous then compError else ref.resolvedMembers.distinct match - case (prefix, member) :: Nil => ref.resolvedTargets.distinct match - case target :: Nil => k(prefix, member, target) + private def openSelection(ref: UnresolvedRef)(k: (Term, Opt[BlockMemberSymbol], Opt[DefinitionSymbol[?]]) => Block)(using LoweringCtx): Block = + // A dynamic candidate also covers known fields on the same receiver. Keep + // other receivers distinct: a wildcard name must identify which object to read. + val dynamic = ref.dynamicPrefixes.distinct + val candidates = ref.resolvedMembers.distinct.filterNot((prefix, _) => dynamic.contains(prefix)) + .map((prefix, member) => prefix -> S(member)) ::: dynamic.map(_ -> N) + if ref.isErroneous then compError else candidates match + case (prefix, N) :: Nil => k(prefix, N, N) + case (prefix, member @ S(_)) :: Nil => ref.resolvedTargets.distinct match + case target :: Nil => k(prefix, member, S(target)) + case targets if targets.nonEmpty && !strictResolution => k(prefix, member, N) case targets => fail: ErrorReport(msg"Wildcard-open reference '${ref.id.name}' requires one resolved definition" -> ref.toLoc :: targets.map(target => msg"target: ${target.describeKind}" -> target.toLoc), @@ -662,11 +669,44 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case candidates => fail: ErrorReport(msg"Wildcard-open reference '${ref.id.name}' is ambiguous" -> ref.toLoc :: candidates.flatMap: (prefix, member) => - (msg"candidate: ${member.describe} '${member.nme}' defined here" -> member.toLoc) :: - (msg"Opened here" -> prefix.toLoc) :: Nil + member.toList.map(member => msg"candidate: ${member.describe} '${member.nme}' defined here" -> member.toLoc) ::: + (msg"Opened here" -> prefix.toLoc) :: Nil , source = Diagnostic.Source.Compilation) + /** Validate source selection once, then let reads, calls, and writes consume + * its runtime receiver, property name, and optional static definition. */ + private def newSelection(sel: NewSel)(k: (Term, Tree.Ident, Opt[DefinitionSymbol[?]]) => Block)(using LoweringCtx): Block = + val NewSel(prefix, id, _) = sel + if !checkProjection(sel) || sel.isErroneous then compError + else if sel.hasDynamicTarget then k(prefix, memberIdent(id, N), N) + else sel.resolvedTargets.distinct match + case Nil => fail: + ErrorReport(msg"This selection of member '${id.name}' has no resolved target" -> sel.toLoc :: Nil, + source = Diagnostic.Source.Compilation) + case target :: Nil => + // For `r = {make: C}`, the pattern head `r.make` resolves to class C, + // but the generated property read must use `make`. resolvedMembers holds + // the property names; target identifies the definition used by the pattern. + sel.resolvedMembers.map(_.nme).distinct match + case name :: Nil => + k(prefix, new Tree.Ident(name).withLocOf(id), S(target)) + case Nil => k(prefix, definitionIdent(id, target), S(target)) + case _ => fail: + ErrorReport(msg"This selection of member '${id.name}' has multiple resolved member names" -> sel.toLoc :: + sel.resolvedMembers.map(member => msg"candidate: ${member.describe} '${member.nme}'" -> member.toLoc), + source = Diagnostic.Source.Compilation) + case targets => + if strictResolution then fail: + ErrorReport( + msg"This selection of member '${id.name}' is ambiguous, as it has multiple resolved targets" -> sel.toLoc :: + targets.map: target => + val owner = target.asTrm.flatMap(_.owner).fold("")(o => s" in ${o.asBlkMember.get.describe} '${o.nme}'") + msg"target: ${target.describeKind} '${target.nme}'${owner}" -> target.toLoc + , + source = Diagnostic.Source.Compilation) + else k(prefix, memberIdent(id, N), N) + /** A projection must identify its class as well as its member: different * classes can inherit the same definition, and wildcard receivers can differ. */ private def checkProjection(sel: NewSel): Bool = sel.cls match @@ -943,16 +983,16 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case trgt :: Nil => ref(t, annots, S(trgt), inStmtPos = inStmtPos)(k) case ts => - if strictResolution && !t.isErroneous then raise: + if strictResolution then fail: ErrorReport( msg"Member reference '${bms.nme}' is ambiguous, as it has multiple resolved targets" -> t.toLoc :: ts.map: t => msg"target: ${t.describeKind}" -> t.toLoc , S(t), source = Diagnostic.Source.Compilation) - compError + else ref(t, annots, N, inStmtPos = inStmtPos)(k) case ref: UnresolvedRef => openSelection(ref)((prefix, member, target) => - setupNamedSelection(prefix, memberIdent(ref.id, S(member)), S(target))(k)) + setupNamedSelection(prefix, memberIdent(ref.id, member), target)(k)) case Capture(base, thru) => term(base, inStmtPos = inStmtPos)(k) case t @ st.Ref(sym) => @@ -1103,6 +1143,12 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): instantiated match // * Due to whacky JS semantics, we need to make sure that selections leading to a call // * are preserved in the call and not moved to a temporary variable. + case sel: NewSel => newSelection(sel): (prefix, name, target) => + subTerm_nonTail(prefix): p => + conclude(Select(p, name)(target)(false).withLocOf(sel)) + case ref: UnresolvedRef => openSelection(ref): (prefix, member, target) => + subTerm_nonTail(prefix): p => + conclude(Select(p, memberIdent(ref.id, member))(target)(false).withLocOf(ref)) case sel @ Sel(prefix, nme) => subTerm(prefix): p => conclude(Select(p, nme)(selSymbol(sel))(false).withLocOf(sel)) @@ -1160,27 +1206,29 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case ref: UnresolvedRef => openSelection(ref): (prefix, member, target) => target match - case sym: TermSymbol => + case S(sym: TermSymbol) => subTerm_nonTail(prefix): p => subTerm_nonTail(rhs): r => - AssignField(p, memberIdent(ref.id, S(member)), castTo(r, sym.erasedType, ref.toLoc), k(unit))(S(sym)) + AssignField(p, memberIdent(ref.id, member), castTo(r, sym.erasedType, ref.toLoc), k(unit))(S(sym)) + case N if ref.resolvedTargets.forall(_.isInstanceOf[TermSymbol]) => + subTerm_nonTail(prefix): p => + subTerm_nonTail(rhs): r => + AssignField(p, memberIdent(ref.id, member), r, k(unit))(N) case _ => fail: ErrorReport(msg"Assignment requires a term member" -> ref.toLoc :: Nil, source = Diagnostic.Source.Compilation) - case sel @ NewSel(prefix, nme, _) => - if !checkProjection(sel) then compError else - // Term resolution selects the definition; lowering only checks that - // the result is an unambiguous term member and emits the field write. - val sym = sel.resolvedTargets.distinct match - case sym :: Nil => sym.asTrm - case _ => N - sym match - case S(sym) => - subTerm(prefix): p => + case sel: NewSel => newSelection(sel): (prefix, name, target) => + target match + case S(sym: TermSymbol) => + subTerm_nonTail(prefix): p => + subTerm_nonTail(rhs): r => + AssignField(p, name, castTo(r, sym.erasedType, sel.toLoc), k(unit))(S(sym)) + case N if sel.resolvedTargets.forall(_.isInstanceOf[TermSymbol]) => + subTerm_nonTail(prefix): p => subTerm_nonTail(rhs): r => - AssignField(p, definitionIdent(nme, sym), castTo(r, sym.erasedType, sel.toLoc), k(unit))(S(sym)) - case N => fail: - ErrorReport(msg"Assignment requires an unambiguous term member" -> sel.toLoc :: Nil, + AssignField(p, name, r, k(unit))(N) + case _ => fail: + ErrorReport(msg"Assignment requires a term member" -> sel.toLoc :: Nil, source = Diagnostic.Source.Compilation) case sel @ Sel(prefix, nme) => subTerm(prefix): p => @@ -1241,33 +1289,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case whltrm: st.SynthWhile => ucs.Normalization(this)(whltrm)(k) - case sel @ NewSel(prefix, id, _) => - if !checkProjection(sel) || sel.isErroneous then compError else sel.resolvedTargets.distinct match - case Nil => fail: - ErrorReport(msg"This selection of member '${id.name}' has no resolved target" -> sel.toLoc :: Nil, - source = Diagnostic.Source.Compilation) - case target :: Nil => - // For `r = {make: C}`, the pattern head `r.make` resolves to class C, - // but the generated property read must use `make`. resolvedMembers holds - // the property names; target identifies the definition used by the pattern. - sel.resolvedMembers.map(_.nme).distinct match - case name :: Nil => - setupNamedSelection(prefix, new Tree.Ident(name).withLocOf(id), S(target))(k) - case Nil => setupSelection(prefix, id, S(target))(k) - case _ => fail: - ErrorReport(msg"This selection of member '${id.name}' has multiple resolved member names" -> sel.toLoc :: - sel.resolvedMembers.map(member => msg"candidate: ${member.describe} '${member.nme}'" -> member.toLoc), - source = Diagnostic.Source.Compilation) - case targets => - if strictResolution then fail: - ErrorReport( - msg"This selection of member '${id.name}' is ambiguous, as it has multiple resolved targets" -> sel.toLoc :: - targets.map: target => - val owner = target.asTrm.flatMap(_.owner).fold("")(o => s" in ${o.asBlkMember.get.describe} '${o.nme}'") - msg"target: ${target.describeKind} '${target.nme}'${owner}" -> target.toLoc - , - source = Diagnostic.Source.Compilation) - else setupSelection(prefix, id, N)(k) + case sel: NewSel => newSelection(sel)((prefix, name, target) => + setupNamedSelection(prefix, name, target)(k)) case sel @ Sel(prefix, nme) => @@ -1368,7 +1391,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): msg"Cannot compile ${t.describe} term that was not elaborated (maybe elaboration was one in 'lightweight' mode?)" -> t.toLoc :: Nil, source = Diagnostic.Source.Compilation) - case _: CompType | _: Neg | _: Term.FunTy | _: Term.Forall | _: Term.WildcardTy | _: Term.Unquoted | _: LeadingDotSel | _: Term.Constrained | _: Term.Annotated + case _: Term.DynTy | _: CompType | _: Neg | _: Term.FunTy | _: Term.Forall | _: Term.WildcardTy | _: Term.Unquoted | _: LeadingDotSel | _: Term.Constrained | _: Term.Annotated => fail: ErrorReport( msg"Unexpected term form in expression position (${t.describe})" -> diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 339d8808f7..83f91dda0a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1413,6 +1413,7 @@ extends Importer: val body = term(rhs, interp) Term.Constrained(constraints, body) case _ => lastWords(s"Unexpected lambda parameter shape: $lhs") + case Keywrd(Keyword.`dyn`) => Term.DynTy().withLocOf(tree) case InfixApp(lhs, Keywrd(Keyword.`as`), rhs) => Term.Asc(subterm(lhs, interp), subterm(rhs, Tpe)) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala index 43ce9be204..dfedc50db1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala @@ -42,6 +42,7 @@ class Importer extends NewResolver: file.ext match case "mjs" | "js" => + if newResolution then sym.shapes.add(DynShape()) Import(sym, file.toString, file) case "mls" => @@ -80,5 +81,6 @@ class Importer extends NewResolver: Import(sym, path, file) else + if newResolution then sym.shapes.add(DynShape()) Import(sym, path, file) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 469c673442..98bfc43580 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -83,6 +83,7 @@ class NewResolver: val l = typeResolution(left) val r = typeResolution(right) result.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) + case DynTy() => result.publish(TypeShape.Dynamic) case _: FunTy => result.publish(TypeShape.Function) case UnitVal() => result.publish(TypeShape.Unit) case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => @@ -110,6 +111,7 @@ class NewResolver: val visited = mutable.Set.empty[TypeResolution] def follow(resolution: TypeResolution): Unit = if visited.add(resolution) then resolution.listen: + case TypeShape.Dynamic => listener(DynShape()) case TypeShape.Nominal(defn) => listenExt(defn.ext, ext => listener(selfShapes.getOrElseUpdate(defn.sym, BaseShape(defn, ext)))) case TypeShape.Alias(_, rhs) => rhs.foreach(follow) @@ -156,8 +158,8 @@ class NewResolver: if p.sign.isEmpty then segment match case TupleShape.Field(field, inner) => listenTerm(field.term): sh => publish(p, sh.exit(inner).exit(marks).enter(mss)) - case TupleShape.Unknown(source, inner) => - publish(p, UnknownValueShape(source).exit(inner).exit(marks).enter(mss)) + case TupleShape.Unknown(_, inner, value) => + publish(p, value.exit(inner).exit(marks).enter(mss)) def loop(rest: Ls[TupleShape.Segment], before: Int, unknownBefore: Bool): Unit = rest match case Nil => () case (field: TupleShape.Field) :: tail => @@ -185,9 +187,13 @@ class NewResolver: else xs match case Nil => Nil case (_: TupleShape.Field) :: rest => drop(rest, count - 1, approximate) + case (unknown: TupleShape.Unknown) :: Nil => + // Consuming a prefix changes the length, but not the element + // shape, when there are no following fields to merge into it. + if approximate then unknown :: Nil else Nil case (_: TupleShape.Unknown) :: rest => val suffix = drop(rest, count, false) - if approximate then TupleShape.Unknown(tuple.source, Nil) :: suffix else suffix + if approximate then TupleShape.Unknown(tuple.source, Nil, UnknownValueShape(tuple.source)) :: suffix else suffix val remaining = drop(segments, expectedCount, true) val rest = if ps.isEmpty then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil) publish(p, rest.exit(marks).enter(mss)) @@ -332,7 +338,7 @@ class NewResolver: // An unknown element used as a callee stays a dynamic call. Propagate its // unknown result rather than inferring callability from a different candidate. lhs match - case Marked(_: UnknownValueShape, _) => + case Marked(_: (DynShape | UnknownValueShape), _) => if res.shapes.add(lhs) then res.shapeListeners.foreach(_(lhs)) return case _ => () @@ -412,6 +418,12 @@ class NewResolver: if !host.resolvedTargets.contains(field.tsym) then host.resolvedTargets ::= field.tsym publish(UnknownValueShape(field.rhs)) + private def publishDynamic(host: NewResolvable & ShapeHost, marks: Ls[Marks]): Unit = + DynShape().exit(marks) match + case shape: TermShape => + if host.shapes.add(shape) then host.shapeListeners.foreach(_(shape)) + case NoShape => () + private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, loc: Opt[Loc]): Unit = host.isErroneous = true val message = reason match @@ -429,6 +441,9 @@ class NewResolver: val candidate = prefix -> member.memberSymbol if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate publishMember(ref, member, ref.resSym, marks) + case MemberLookup.Dynamic(marks) => + if !ref.dynamicPrefixes.contains(prefix) then ref.dynamicPrefixes ::= prefix + publishDynamic(ref, marks) case MemberLookup.Missing => // A known miss in one wildcard source is not an error: another may // provide the name. Lowering diagnoses references with no candidates. @@ -477,6 +492,9 @@ class NewResolver: log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") if !sel.resolvedMembers.contains(bms.memberSymbol) then sel.resolvedMembers ::= bms.memberSymbol publishMember(sel, bms, sel.resSym, marks) + case MemberLookup.Dynamic(marks) => + sel.hasDynamicTarget = true + publishDynamic(sel, marks) case MemberLookup.Missing => sel.isErroneous = true resolError(sel, msg"$description does not contain member '${sel.id.name}'" -> loc :: Nil) @@ -717,6 +735,8 @@ class NewResolver: trm match case _: SynthSel => lastWords("Synthetic selections must not enter new resolution") + case Asc(_, sign) => listenTypeValues(sign)(listener) + case _: DynSel | _: DynNew => listener(DynShape()) case TyApp(underlying, _) => listen(underlying, discardMarks)(listener) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => listenAggregate(tuple, listener): publish => @@ -748,6 +768,9 @@ class NewResolver: then TupleShape.unknown(shape.source) else shape expand(rest, TupleShape.Spread(spread, marks) :: reversed) + case Marked(_: DynShape, marks) => + val spread = TupleShape(term, TupleShape.Unknown(term, Nil, DynShape()) :: Nil) + expand(rest, TupleShape.Spread(spread, marks) :: reversed) case Marked(_, marks) => // Opaque iterables (e.g. external Arrays) have no resolved // element layout. Their runtime spread is still permitted. @@ -770,6 +793,7 @@ class NewResolver: then RecordShape(shape.source, RecordShape.Unknown :: Nil) else shape expand(rest, RecordShape.Spread(spread, marks) :: reversed) + case Marked(_: DynShape, marks) => expand(rest, RecordShape.Dynamic(marks :: Nil) :: reversed) case _ => expand(rest, RecordShape.Unknown :: reversed) case _ :: rest => expand(rest, reversed) expand(record.stats, Nil) @@ -779,6 +803,8 @@ class NewResolver: IntroShape(intro) }) listener(sh) + case Ref(sym) if sym is sym.getState.globalThisSymbol => listener(DynShape()) + case SelfRef(sym) if sym is sym.getState.globalThisSymbol => listener(DynShape()) case ref @ Ref(loc: LocalSymbol) => loc.shapes.foreach(listener) loc.shapeListeners += listener diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index 5df92e427c..af6510bef9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -1082,7 +1082,7 @@ class Resolver(tl: TraceLogger) // Complex type: Function type, Wildcard type, Composed type, // Negation type, Forall type, - case t: (Term.FunTy | Term.WildcardTy | Term.CompType | Term.Neg | Term.Forall | Term.Constrained | Term.Tup) => + case t: (Term.DynTy | Term.FunTy | Term.WildcardTy | Term.CompType | Term.Neg | Term.Forall | Term.Constrained | Term.Tup) => t.subTerms.foreach(traverseSign(_, expect = Expect.NonModule(N))) // t is not a type. @@ -1168,7 +1168,7 @@ class Resolver(tl: TraceLogger) case Term.App(Term.Ref(_: BuiltinSymbol), Term.Tup(Fld(term = Term.Lit(_)) :: Nil)) => if expect.module then raiseError() else Type.NotImplemented // TODO: Support Lit with operator - case _: (Term.FunTy | Term.WildcardTy | Term.CompType | Term.Neg | Term.Forall | Term.Constrained | Term.Tup | Term.Lit) => + case _: (Term.DynTy | Term.FunTy | Term.WildcardTy | Term.CompType | Term.Neg | Term.Forall | Term.Constrained | Term.Tup | Term.Lit) => if expect.module then raiseError() else Type.NotImplemented // TODO: Support complex types diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 658a59ea49..e23dddd174 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -20,6 +20,7 @@ sealed trait Shape extends ShapeLike: case ns: SymShape => s"SymShape(${ns.sym.showDbg})" case ns: NewShape => s"NewShape(${ns.cls.showDbg}, ${ns.argss.map(_.showDbg).mkString(", ")})" case is: IntroShape => s"IntroShape(${is.trm.showDbg})" + case _: DynShape => "DynShape" case us: UnknownValueShape => s"UnknownValueShape(${us.source.showDbg})" case ts: TupleShape => s"TupleShape(${ts.source.showDbg})" case rs: RecordShape => s"RecordShape(${rs.source.showDbg})" @@ -233,11 +234,13 @@ end TermShape * searching wildcard opens, since it can introduce an additional candidate. */ enum MemberLookup: case Found(member: BlockMemberSymbol | RecordMember, marks: Ls[Marks]) + case Dynamic(marks: Ls[Marks]) case Missing case Unknown(reason: MemberLookup.Uncertainty, loc: Opt[Loc]) def withMarks(marks: Ls[Marks]): MemberLookup = this match case Found(member, inner) => Found(member, inner ::: marks) + case Dynamic(inner) => Dynamic(inner ::: marks) case _ => this object MemberLookup: @@ -381,7 +384,7 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) exte case TupleShape.Rest(_, segments) => segments case TupleShape.Spread(shape, marks) => shape.segments.map: case TupleShape.Field(field, inner) => TupleShape.Field(field, inner ::: marks :: Nil) - case TupleShape.Unknown(shape, inner) => TupleShape.Unknown(shape, inner ::: marks :: Nil) + case TupleShape.Unknown(source, inner, value) => TupleShape.Unknown(source, inner ::: marks :: Nil, value) /** Does this candidate already depend on the given producer in the given context? * `source` identifies the producer by syntax-node identity; `marks` distinguish * its spread contexts. Inspect the selected dependency tree, not flattened @@ -402,6 +405,15 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) exte def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str): MemberLookup = ??? // Structural tuple members are not implemented yet. +/** Values whose members and call results are deliberately checked only at runtime. + * Unlike UnknownValueShape, this authorizes dynamic operations; it is introduced + * by JavaScript interop and explicit `dyn` types, not by failed inference. + */ +final case class DynShape() extends NonAppTermShape: + def describe: Str = "dynamic value" + def toLoc: Opt[Loc] = N + protected def getMemberImpl(name: Str): MemberLookup = MemberLookup.Dynamic(Nil) + /** The element of an opaque or widened spread can be any value. Keep this * alternative in the flow graph so other, known arguments cannot silently make * an unresolved member selection appear to have a unique static target. @@ -418,10 +430,11 @@ object TupleShape: final case class Field(field: Fld, marks: Ls[Marks]) extends Segment /** An arbitrary number of arbitrary values. Use this for opaque layouts and * recursive widening, never for a spread whose shape has not arrived yet. - * The `source` field is used for user-facing diagnostic purposes. */ - final case class Unknown(source: Term, marks: Ls[Marks]) extends Segment + * `value` distinguishes dynamically typed JS elements from values whose + * shape was lost through widening; `source` supplies diagnostic locations. */ + final case class Unknown(source: Term, marks: Ls[Marks], value: NonMarkedShape) extends Segment final case class Spread(shape: TupleShape, marks: Marks) extends Element - def unknown(source: Term): TupleShape = TupleShape(source, Unknown(source, Nil) :: Nil) + def unknown(source: Term): TupleShape = TupleShape(source, Unknown(source, Nil, UnknownValueShape(source)) :: Nil) /** Retain the original candidate as well as the selected residual segments: * flattening away the parent would hide recursive producer dependencies from * containsSpread, allowing recursion through rest slicing to evade widening. */ @@ -452,7 +465,9 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] if key == name then Found(RecordMember(field, source.mut), Nil) else loop(rest) case _ => unknown case RecordShape.Unknown :: _ => unknown + case RecordShape.Dynamic(marks) :: _ => Dynamic(marks) case RecordShape.Spread(shape, marks) :: rest => shape.getMember(name) match + case Dynamic(inner) => Dynamic(inner ::: marks :: Nil) case Unknown(_, _) => unknown case Missing => loop(rest) case Found(member: RecordMember, inner) => @@ -471,6 +486,7 @@ object RecordShape: case Field(field: RcdField) case Spread(shape: RecordShape, marks: Marks) case Unknown + case Dynamic(marks: Ls[Marks]) export Element.* diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index f2748d92d6..fe919b3415 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -360,6 +360,8 @@ sealed trait NewRefImpl extends AnyRefImpl: sealed trait NewSelImpl extends NewResolvableImpl: self: Term.NewSel => + // At least one receiver permits runtime lookup without a static member symbol. + var hasDynamicTarget: Bool = false var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution // Class identity and captures must survive even when candidates share an inherited member. var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil @@ -373,6 +375,7 @@ sealed trait UnresolvedRefImpl extends NewResolvableImpl: // Retain the receiver as well as the definition: two instances can expose // the same member symbol without denoting the same storage location. var resolvedMembers: Ls[(Term, BlockMemberSymbol)] = Nil + var dynamicPrefixes: Ls[Term] = Nil enum Term extends Statement, ShapePublisher: @@ -381,6 +384,7 @@ enum Term extends Statement, ShapePublisher: case Error() case UnitVal() + case DynTy() case Missing // Placeholder terms that were not elaborated due to the "lightweight" elaboration mode `Mode.Light` case Lit(lit: Literal) extends Term @@ -587,7 +591,7 @@ enum Term extends Statement, ShapePublisher: val bodyFree = body.freeVars if bodyFree(result.nme) then bodyFree - result.nme else bodyFree case Forall(_, _, body) => body.freeVars - case Error | Missing | _: Lit | _: UnitVal | _: LeadingDotSel | _: Continue => Set.empty + case Error | Missing | _: Lit | _: DynTy | _: UnitVal | _: LeadingDotSel | _: Continue => Set.empty case Break(_, result, value) => value.iterator.flatMap(_.freeVars).toSet + result.nme case _ => subTerms.iterator.flatMap(_.freeVars).toSet @@ -628,6 +632,7 @@ enum Term extends Statement, ShapePublisher: val that = this match case Error() => Error() case UnitVal() => UnitVal() + case DynTy() => DynTy() case Missing => Missing case Lit(Tree.StrLit(value)) => Lit(Tree.StrLit(value)) case Lit(Tree.IntLit(value)) => Lit(Tree.IntLit(value)) @@ -641,14 +646,17 @@ enum Term extends Statement, ShapePublisher: val copy = NewSel(prefix.mkClone, id, cls.map(_.mkClone))(term.resSym) copy.resolvedMembers = term.resolvedMembers copy.resolvedClasses = term.resolvedClasses + copy.hasDynamicTarget = term.hasDynamicTarget copyNewResolution(term, copyShapes(term, copy)) case term @ UnresolvedRef(prefixes, id) => val clonedPrefixes = prefixes.map(_.mkClone) val copy = UnresolvedRef(clonedPrefixes, id)(term.resSym) - copy.resolvedMembers = term.resolvedMembers.map: (receiver, member) => + def cloneReceiver(receiver: Term): Term = val index = prefixes.indexWhere(_ is receiver) assert(index >= 0, "A wildcard candidate must belong to an opened prefix") - clonedPrefixes(index) -> member + clonedPrefixes(index) + copy.resolvedMembers = term.resolvedMembers.map((receiver, member) => cloneReceiver(receiver) -> member) + copy.dynamicPrefixes = term.dynamicPrefixes.map(cloneReceiver) copyNewResolution(term, copyShapes(term, copy)) case Capture(base, thru) => Capture(base.mkClone, thru) case term @ Resolved(t, sym) => copyResolution(term, Resolved(t.mkClone, sym)(term.typ)) @@ -816,6 +824,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: val desc = this match case Error() => "‹error›" case UnitVal() => "unit value" + case DynTy() => "dynamic type" case _: Rcd => "record literal" case Lit(lit) => lit.describeLit case Ref(sym) => "reference" @@ -891,7 +900,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case Blk(stats, res) => stats.toVector :+ res case _ => subTerms def subTerms: Vector[Term] = this match - case Error() | Missing | _: Lit | _: AnyRef_ | _: UnitVal => Vector.empty + case Error() | Missing | _: Lit | _: AnyRef_ | _: DynTy | _: UnitVal => Vector.empty case Capture(base, thru) => Vector.single(base) case Resolved(t, sym) => Vector.single(t) case App(lhs, rhs) => Vector.double(lhs, rhs) @@ -989,6 +998,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: def res: Document = this match case lit: Lit => lit.lit.idStr case UnitVal() => doc"()" + case DynTy() => doc"dyn" case r: SimpleRef => r.sym match case _: BuiltinSymbol => r.sym.nme @@ -1173,6 +1183,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: def showPlain(using DebugPrinter): Str = this match case Term.UnitVal() => "()" + case Term.DynTy() => "dyn" case Lit(lit) => lit.idStr case Resolved(t, sym) => t.showPlain case r @ Ref(symbol) => symbol.showAsPlain diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index d90bf24e12..d74bca131b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -15,6 +15,7 @@ enum TypeShape: case Intersection(left: TypeResolution, right: TypeResolution) case Function case Unit + case Dynamic case Abstract /** Candidates are published during elaboration. Erasure reads the resulting graph; it @@ -68,7 +69,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case _ => ErasedType.Unknown case Alias(_, rhs) :: Nil => rhs.fold(ErasedType.Unknown)(_.erase(next)) case Union(left, right) :: Nil => ErasedType.union(left.erase(next), right.erase(next)) - case Intersection(_, _) :: Nil | Abstract :: Nil => ErasedType.Unknown + case Intersection(_, _) :: Nil | Dynamic :: Nil | Abstract :: Nil => ErasedType.Unknown case Function :: Nil => ErasedType.Function(S(false)) case Unit :: Nil => ErasedType.Unit case Nil => diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index 2c9c7f5038..fe90cad986 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -152,6 +152,7 @@ object Keyword: val `forall` = Keyword("forall", N, N) val `exists` = Keyword("exists", N, N) val `null` = Keyword("null", N, N) + val `dyn` = Keyword("dyn", N, N) val `undefined` = Keyword("undefined", N, N) val `abstract` = Keyword("abstract", N, N) val `constructor` = Keyword("constructor", N, N) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala index aa459826ec..2b6dc84fc3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala @@ -415,6 +415,7 @@ class ParseRules(using State): modified(`staged`), singleKw(`true`)(BoolLit(true)), singleKw(`false`)(BoolLit(false)), + singleKw(`dyn`)(Keywrd(`dyn`)), singleKw(`undefined`)(UnitLit(false)), singleKw(`null`)(UnitLit(true)), singleKw(`this`)(Ident("this")), diff --git a/hkmc2/shared/src/test/mlscript/apps/.mls b/hkmc2/shared/src/test/mlscript/apps/.mls index 411541472f..f84908e26a 100644 --- a/hkmc2/shared/src/test/mlscript/apps/.mls +++ b/hkmc2/shared/src/test/mlscript/apps/.mls @@ -1,2 +1,2 @@ :.. -#lang(strictResolution: true) +#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/basics/.mls b/hkmc2/shared/src/test/mlscript/basics/.mls index 411541472f..f84908e26a 100644 --- a/hkmc2/shared/src/test/mlscript/basics/.mls +++ b/hkmc2/shared/src/test/mlscript/basics/.mls @@ -1,2 +1,2 @@ :.. -#lang(strictResolution: true) +#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/basics/SetIndented.mls b/hkmc2/shared/src/test/mlscript/basics/SetIndented.mls index 0ba50aef81..b3956c320f 100644 --- a/hkmc2/shared/src/test/mlscript/basics/SetIndented.mls +++ b/hkmc2/shared/src/test/mlscript/basics/SetIndented.mls @@ -12,7 +12,7 @@ set //│ ╔══[COMPILATION ERROR] Cannot use 'this' outside of an object scope //│ ║ l.8: (this).pc = 0 //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Assignment requires an unambiguous term member +//│ ╔══[COMPILATION ERROR] This selection of member 'pc' has no resolved target //│ ║ l.6: set this.pc = 0 //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -24,7 +24,7 @@ set //│ ╔══[COMPILATION ERROR] Cannot use 'this' outside of an object scope //│ ║ l.23: this.pc = 0 //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Assignment requires an unambiguous term member +//│ ╔══[COMPILATION ERROR] This selection of member 'pc' has no resolved target //│ ║ l.23: this.pc = 0 //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/.mls b/hkmc2/shared/src/test/mlscript/codegen/.mls index 411541472f..f84908e26a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/.mls @@ -1,2 +1,2 @@ :.. -#lang(strictResolution: true) +#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/codegen/Primes.mls b/hkmc2/shared/src/test/mlscript/codegen/Primes.mls index 3cbbdb1e66..13fd0f824c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Primes.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Primes.mls @@ -60,9 +60,6 @@ set Foo.x' += 1 //│ ╟── Object 'Foo' does not contain member 'x'' //│ ║ l.41: object Foo with //│ ╙── ^^^ -//│ ╔══[COMPILATION ERROR] Assignment requires an unambiguous term member -//│ ║ l.50: set Foo.x' += 1 -//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. Foo.("x'") @@ -72,7 +69,7 @@ Foo.("x'") class Foo with fun f' = 1 //│ ╔══[COMPILATION ERROR] Illegal function member name: 'f'' -//│ ║ l.73: fun f' = 1 +//│ ║ l.70: fun f' = 1 //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. @@ -80,7 +77,7 @@ class Foo with class Foo with class F' //│ ╔══[COMPILATION ERROR] Illegal class member name: 'F'' -//│ ║ l.81: class F' +//│ ║ l.78: class F' //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. @@ -88,7 +85,7 @@ class Foo with class Foo with object F' //│ ╔══[COMPILATION ERROR] Illegal object member name: 'F'' -//│ ║ l.89: object F' +//│ ║ l.86: object F' //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. @@ -96,7 +93,7 @@ class Foo with class Foo with module F' //│ ╔══[COMPILATION ERROR] Illegal module member name: 'F'' -//│ ║ l.97: module F' +//│ ║ l.94: module F' //│ ║ ^^ //│ ╙── Member names must start with a letter or underscore, followed by letters, digits, or underscores. diff --git a/hkmc2/shared/src/test/mlscript/newres/Assignments.mls b/hkmc2/shared/src/test/mlscript/newres/Assignments.mls index b5df130ab9..9dd1117ef7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Assignments.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Assignments.mls @@ -82,7 +82,7 @@ module Classes { class Only } set Classes.Only = 0 -//│ ╔══[COMPILATION ERROR] Assignment requires an unambiguous term member +//│ ╔══[COMPILATION ERROR] Assignment requires a term member //│ ║ l.84: set Classes.Only = 0 //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls new file mode 100644 index 0000000000..c9b66002fa --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls @@ -0,0 +1,152 @@ +:. + +// JavaScript imports allow ordinary selection and calls, even in strict mode. +:expect () +import "../../js/Test.mjs" as js +js.greet() +//│ > Hello from Test.mjs! + +:expect "/a/b" +import "node:path" as path +path.posix.join("/a", "b") +//│ = "/a/b" + +// An ascription forgets the initializer's shape; successive selections and calls +// remain dynamic rather than acquiring a fabricated member definition. +:expect 3 +let d = ({nested: {get: () => 3}} as dyn) +d.nested.get() +//│ = 3 +//│ d = {nested: {get: fun get}} + +:expect 4 +fun readDynamic(x: dyn) = x.value +readDynamic({value: 4}) +//│ = 4 + +:expect 5 +type Dynamic = dyn +fun readAlias(x: Dynamic) = x.value +readAlias({value: 5}) +//│ = 5 + +:expect 6 +let writable = mut {value: 0} as dyn +set writable.value = 6 +writable.value +//│ = 6 +//│ writable = {value: 6} + +:expect 7 +fun identity(x) = x +identity({value: 7} as dyn).value +//│ = 7 + +// Dynamic spreads retain dynamic element values through parameter propagation. +:expect 8 +fun readSpread(x) = x.value +readSpread(...([{value: 8}] as dyn)) +//│ = 8 + +:expect 9 +let copied = {...({value: 9} as dyn)} +copied.value +//│ = 9 +//│ copied = {value: 9} + +:expect 10 +fun fromOpen() = + let opened = ({value: 10} as dyn) + open opened + value +fromOpen() +//│ = 10 + +// Explicit dynamic selections are also JS boundaries. +:expect 11 +let explicit = {nested: {value: 11}} +explicit."nested".value +//│ = 11 +//│ explicit = {nested: {value: 11}} + +// Known invalid operations still fail; dynamicity is not inferred from failure. +:e +{value: 1}.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.74: {value: 1}.missing +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'missing' +//│ ║ l.74: {value: 1}.missing +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +1() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.84: 1() +//│ ║ ^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.84: 1() +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] TypeError: 1 is not a function + +:e +dyn +//│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (dynamic type) +//│ ║ l.94: dyn +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Dynamic result ascriptions apply after evaluating the call. +:expect 12 +fun produce() = {value: 12} +(produce() as dyn).value +//│ = 12 + +:expect 13 +globalThis.JSON.parse("{\"value\":13}").value +//│ = 13 + +// A dyn parameter supplies a shape without needing a call site. +fun notCalled(x: dyn) = x.anything(1).result + +// A dynamic candidate must not disguise another candidate's known missing member. +:e +let mixed = if hide(true) then ({value: 1} as dyn) else {other: 2} +mixed.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.116: mixed.value +//│ ║ ^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'value' +//│ ║ l.115: let mixed = if hide(true) then ({value: 1} as dyn) else {other: 2} +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ mixed = {value: 1} + +// Calling a selected JS method must preserve its receiver (`Map.get` uses this). +:expect 14 +let map = new! globalThis.Map() +map.set("key", 14) +map.get("key") +//│ = 14 +//│ map = Map(1) {"key" => 14} + +:expect 15 +fun skipFirst(first, ...tail) = readSpread(...tail) +skipFirst(...([0, {value: 15}] as dyn)) +//│ = 15 + +// A known and a dynamic alternative for one open still name one runtime receiver. +:expect 16 +fun mixedOpen() = + let source = if hide(true) then ({value: 16} as dyn) else {value: 17} + open source + value +mixedOpen() +//│ = 16 + +// Dynamicity permits compilation, while the runtime still checks the operation. +:re +(1 as dyn)() +//│ ═══[RUNTIME ERROR] TypeError: 1 is not a function + diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls new file mode 100644 index 0000000000..bc68d708ee --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls @@ -0,0 +1,84 @@ +:. + +// Both receivers expose the same runtime property. Non-strict resolution keeps +// the property read and call even though their definitions are different. +:expect 1 +module Left { fun get() = 1 } +module Right { fun get() = 2 } +let receiver = if hide(true) then Left else Right +receiver.get() +//│ = 1 +//│ receiver = class Left + +:e +receiver.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.14: receiver.missing +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Module 'Left' does not contain member 'missing' +//│ ║ l.6: module Left { fun get() = 1 } +//│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.14: receiver.missing +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Module 'Right' does not contain member 'missing' +//│ ║ l.7: module Right { fun get() = 2 } +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +1() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.30: 1() +//│ ║ ^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.30: 1() +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] TypeError: 1 is not a function + +// Missing names are still errors when used as callees. +:e +missingCallee() +//│ ╔══[COMPILATION ERROR] Name not found: missingCallee +//│ ║ l.41: missingCallee() +//│ ╙── ^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:expect 2 +let writableLeft = mut {value: 0} +let writableRight = mut {value: 1} +let writable = if hide(true) then writableLeft else writableRight +set writable.value = 2 +writable.value +//│ = 2 +//│ writable = {value: 2} +//│ writableLeft = {value: 2} +//│ writableRight = {value: 1} + +// A direct conditional callee retains both possible functions. +:expect 3 +fun first() = 3 +fun second() = 4 +let call = if hide(true) then first else second +call() +//│ = 3 +//│ call = fun first + +:e +fun unknownReceiver(x) = x.missing +//│ ╔══[COMPILATION ERROR] This selection of member 'missing' has no resolved target +//│ ║ l.68: fun unknownReceiver(x) = x.missing +//│ ╙── ^^^^^^^^^ + +// Allowing multiple targets does not make class definitions assignable. +:e +module ClassLeft { class Item } +module ClassRight { class Item } +let classes = if hide(true) then ClassLeft else ClassRight +set classes.Item = 0 +//│ ╔══[COMPILATION ERROR] Assignment requires a term member +//│ ║ l.78: set classes.Item = 0 +//│ ╙── ^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ classes = class ClassLeft { Item: class Item } + diff --git a/hkmc2/shared/src/test/mlscript/ucs/.mls b/hkmc2/shared/src/test/mlscript/ucs/.mls index 411541472f..f84908e26a 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/.mls @@ -1,2 +1,2 @@ :.. -#lang(strictResolution: true) +#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/ups/.mls b/hkmc2/shared/src/test/mlscript/ups/.mls index 411541472f..f84908e26a 100644 --- a/hkmc2/shared/src/test/mlscript/ups/.mls +++ b/hkmc2/shared/src/test/mlscript/ups/.mls @@ -1,2 +1,2 @@ :.. -#lang(strictResolution: true) +#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/wasm/.mls b/hkmc2/shared/src/test/mlscript/wasm/.mls index b1b8e2f06a..887fd5a946 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/.mls @@ -1,5 +1,5 @@ :.. :wasm -#lang(strictResolution: true) +#lang(strictResolution: false) //│ Wasm result: //│ = {} From dd87eb91afd10ce7e8bd396e393f0da51d82bb97 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 23 Sep 2026 22:07:52 +0800 Subject: [PATCH 054/228] Migrate 54 more worksheets to new resolution --- doc/new-resolution-suite-migration.md | 351 ++++++++---------- hkmc2/shared/src/test/mlscript/apps/.mls | 1 - hkmc2/shared/src/test/mlscript/basics/.mls | 1 - .../test/mlscript/basics/ObjectExtensions.mls | 60 +-- hkmc2/shared/src/test/mlscript/codegen/.mls | 1 - .../test/mlscript/codegen/BadGenerators.mls | 13 +- .../src/test/mlscript/codegen/BadNew.mls | 75 ++-- .../src/test/mlscript/codegen/BadThis.mls | 10 +- .../src/test/mlscript/codegen/BasicTerms.mls | 10 +- hkmc2/shared/src/test/mlscript/codegen/Do.mls | 11 +- .../src/test/mlscript/codegen/ImportAlias.mls | 15 +- .../test/mlscript/codegen/ImportJSClass.mls | 3 +- .../test/mlscript/codegen/ImportJSModule.mls | 1 + .../src/test/mlscript/codegen/ImportMLs.mls | 45 ++- .../src/test/mlscript/codegen/ImportMLsJS.mls | 14 +- .../mlscript/codegen/InterleavedRecords.mls | 3 +- .../src/test/mlscript/codegen/QQImport.mls | 1 + .../src/test/mlscript/codegen/Spreads.mls | 36 +- .../shared/src/test/mlscript/codegen/This.mls | 6 +- .../src/test/mlscript/codegen/Throw.mls | 25 +- .../src/test/mlscript/codegen/UnitValue.mls | 5 +- hkmc2/shared/src/test/mlscript/ucs/.mls | 1 - .../ucs/patterns/CompiledClassPatterns.mls | 10 +- .../CompiledQualifiedConstructors.mls | 1 + hkmc2/shared/src/test/mlscript/ups/.mls | 1 - .../test/mlscript/ups/BasicStackPatterns.mls | 9 +- .../test/mlscript/ups/SimpleConjunction.mls | 1 + .../src/test/mlscript/ups/TransformFree.mls | 1 + .../src/test/mlscript/ups/UpsBugsBacklog.mls | 5 +- .../mlscript/ups/examples/BasicStackParse.mls | 5 +- .../src/test/mlscript/ups/examples/DnfCnf.mls | 1 + .../mlscript/ups/examples/DoubleOrSum.mls | 1 + .../test/mlscript/ups/examples/Extraction.mls | 1 + .../test/mlscript/ups/examples/Flatten.mls | 1 + .../mlscript/ups/examples/TupleSpread.mls | 1 + .../src/test/mlscript/ups/fixpoint/.mls | 1 + .../mlscript/ups/fixpoint/Diagnostics.mls | 9 +- .../ups/fixpoint/FixedPointPatterns.mls | 3 +- .../ups/fixpoint/IndirectRecursion.mls | 13 +- .../ups/fixpoint/MoreAlternatives.mls | 1 + .../mlscript/ups/fixpoint/NonCatchAll.mls | 19 +- .../ups/fixpoint/RecursionAlternatives.mls | 25 +- .../mlscript/ups/fixpoint/SimpleExample.mls | 1 + .../ups/fixpoint/UnsupportedShapes.mls | 70 ++-- .../src/test/mlscript/ups/nondeterminism/.mls | 1 + .../ups/nondeterminism/BitArithmetic.mls | 1 + .../ups/nondeterminism/EvenOddTree.mls | 1 + .../mlscript/ups/nondeterminism/LaRbTree.mls | 1 + .../src/test/mlscript/ups/parametric/.mls | 1 + .../mlscript/ups/parametric/EtaConversion.mls | 6 +- .../ups/parametric/HigherOrderPattern.mls | 11 +- .../test/mlscript/ups/parametric/ListLike.mls | 1 + .../test/mlscript/ups/parametric/Nullable.mls | 5 +- .../src/test/mlscript/ups/recursion/.mls | 1 + .../test/mlscript/ups/recursion/BitSeq.mls | 1 + .../test/mlscript/ups/recursion/BitTree.mls | 1 + .../ups/recursion/LeafEvenOddTree.mls | 1 + .../test/mlscript/ups/recursion/NatBox.mls | 1 + .../test/mlscript/ups/recursion/NullTree.mls | 1 + .../test/mlscript/ups/recursion/SignBox.mls | 1 + .../src/test/mlscript/ups/specialization/.mls | 1 + .../ups/specialization/SimpleList.mls | 5 +- .../ups/specialization/SimpleLiterals.mls | 3 +- .../mlscript/ups/syntax/MixedParameters.mls | 17 +- .../ups/transformation/BindingLess.mls | 1 + hkmc2/shared/src/test/mlscript/wasm/.mls | 3 - 66 files changed, 527 insertions(+), 406 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/ups/fixpoint/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ups/nondeterminism/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ups/parametric/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ups/recursion/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ups/specialization/.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 81f1924b37..bbcb4a42e7 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -1,28 +1,50 @@ # New-resolution suite migration -## Scope and retained milestone +## Scope and current progress The first pass tried all 372 active `.mls` files in `basics`, `codegen`, `ucs`, -`ups`, and `apps` with new resolution and strict resolution. The 51 files under -`ucs/staging` are excluded by the test runner and remain unchanged. Counts below -exclude the new shared `.mls` configuration files and new regression tests. +`ups`, and `apps` with new resolution and strict resolution, retaining 185 ports. +After dynamic shapes and non-strict suite defaults became available, all 187 +deferred worksheets were retried. This pass migrates another 54, bringing the +count to 239 of 372 (64%). The 51 files under `ucs/staging` are excluded by the +test runner and remain unchanged. Counts below exclude shared `.mls` +configuration files and new-resolution regression tests. | Suite | Migrated | Retained on existing configuration | Active files | | --- | ---: | ---: | ---: | -| basics | 55 | 35 | 90 | -| codegen | 62 | 63 | 125 | -| ucs | 50 | 20 | 70 | -| ups | 13 | 58 | 71 | +| basics | 56 | 34 | 90 | +| codegen | 78 | 47 | 125 | +| ucs | 52 | 18 | 70 | +| ups | 48 | 23 | 71 | | apps | 5 | 11 | 16 | -| Total | 185 | 187 | 372 | +| Total | 239 | 133 | 372 | Migrated files start with `:.`. Each suite's `.mls` loads the common language -configuration and uses non-strict resolution; nested directories inherit through -`:..`. `newres` retains strict resolution, with explicit non-strict coverage in -`newres/loose`. Non-strict resolution permits multiple selection/call targets, -but still rejects missing targets and known invalid operations. The suite defaults -disable JS execution so each file retains its original execution flags. A parser-only test must not acquire runtime execution merely -because its resolution configuration changes. +configuration and uses its non-strict default; nested directories inherit through +`:..`. The redundant `#lang(strictResolution: false)` settings have been removed +from the six general suites, including WASM. `newres` retains strict resolution; +`newres/loose` still needs an explicit override of that strict parent. Non-strict +resolution permits multiple selection/call targets, but still rejects missing +targets and known invalid operations. The common language configuration disables +JS execution, so each file retains its original execution flags. A parser-only test must not acquire runtime execution +merely because its resolution configuration changes. + +The additional ports comprise one basics test (`ObjectExtensions`), 16 codegen +tests, two UCS compiled-pattern tests, and 35 UPS tests. They include JavaScript +imports, spread calls, qualified constructors, and recursive, parametric, and +fixed-point patterns. Five new UPS directory configurations connect these tests +to the common language configuration. + +Forty-nine files needed only the configuration header and refreshed goldens. +`BadThis`, `BasicTerms`, `Spreads`, and `ImportMLs` additionally expect static +errors for invalid operations already present in their negative cases; no existing +error expectations were removed. `Throw` uses `new Error(...)`, preserving its +throw/catch results. Tests of foreign callable classes remain deferred rather +than being rewritten to avoid the capability under test. + +The two remaining WASM worksheets were also retried; both remain deferred. +WASM therefore remains at 19 migrated worksheets out of 21, outside the table +above. Blocked files retain their complete original sources and golden outputs. No new per-file `:todo`, `:fixme`, `:ignore`, or resolution-mode exception hides a migration @@ -34,10 +56,9 @@ their existing mode. This is caller-side migration, not a claim that all importe application implementations use new resolution. The 20 application compilation fixtures and four `mlscript-compile/ups` fixtures need a subsequent dependency-ordered migration. Their shared consumers make a blanket flag change inappropriate for -this first milestone. +this worksheet migration. -The unfinished WASM/type-erasure changes predate this migration and are retained. -In particular, the typed-constructor-field capture assertion is still outstanding. +The typed-constructor-field capture assertion in WASM `Basics` remains outstanding. Method calls across REPL blocks remain assigned to the other branch. Constructor-pattern field propagation also has a serious context-mixing blocker, documented below; its `:fixme` regression is not evidence that migration is complete. @@ -117,9 +138,11 @@ Evidence: new shape propagation lacks record, conjunction, negation, string concatenation, and transformation cases. Examples include `ucs/general/BooleanPatterns.mls`, `patterns/String.mls`, and `normalization/RecordImpliedByClass.mls`. Tuple bindings currently publish no -shapes. `CompiledQualifiedConstructors.mls` reaches `Term.mkClone` with a -`NewSel`; `CompiledClassPatterns.mls` reaches it with a `MemberRef`. Many UPS -failures combine these gaps with synthesized matcher selections. +shapes. `CompiledQualifiedConstructors.mls` and `CompiledClassPatterns.mls` now +pass and are migrated, as are most UPS fixed-point, recursive, and parametric +cases. Remaining failures include unsupported pattern forms, patterns used as +terms (for example `.unapply`), and synthesized selections entering new resolution +in `ucs/examples/EitherOrBoth.mls`. **Question:** what resolution state should synthesized matcher references share with source references, and what precision should recursive/transformed pattern @@ -202,8 +225,10 @@ generic aliases. Coordinate REPL method cases with the other branch. ### 4. Dynamic construction and foreign callable classes -Evidence: `basics/DynamicInstantiation.mls` and `codegen/ImportJSClass.mls` reach -static class-lowering assumptions for explicitly dynamic constructions. +`codegen/ImportJSClass.mls` now passes with dynamic construction and is migrated. +`basics/DynamicInstantiation.mls` remains blocked by unimplemented member lookup +in `DefnShape.getMemberImpl` for `new! C.class(1, 2)`; its previous class-lowering +failure is no longer the first observed blocker. `PredefUsage.mls` calls JavaScript's `String` as a function, while its current declaration supplies only a module interpretation. Bare MLscript classes also reject old empty-call syntax, as intended by the earlier migration. @@ -212,9 +237,9 @@ reject old empty-call syntax, as intended by the earlier migration. capabilities, without making every module or bare class implicitly callable? **Proposal:** give foreign declarations explicit callable/constructible -capabilities using the existing overload interpretation mechanism. Lower -explicitly dynamic construction directly as dynamic IR, distinct from static -class construction. Keep static `new` on the listener-resolved class path. +capabilities using the existing overload interpretation mechanism. Explicitly +dynamic construction already lowers to dynamic IR; keep static `new` on the +listener-resolved class path. Change test syntax only where that preserves the test's intent; tests explicitly about callable JS constructors must not be rewritten to avoid that feature. @@ -235,7 +260,10 @@ errors when a selected interpretation lacks the required capability. a small existing reproducer. - Preserve source origins in synthesized references and diagnostics. Some migrated error cases now report a definition rather than a use site, or lose - a location. These need attention without changing the chosen symbol. + a location. In particular, `ups/UpsBugsBacklog.mls` and + `ups/syntax/MixedParameters.mls` retain their expected undefined-binding errors + but lose the use-site location. These need attention without changing the + chosen symbol. ## Execution order and completion gates @@ -243,15 +271,18 @@ errors when a selected interpretation lacks the required capability. application difftests, and the aggregate suite. Review output changes; keep nonmigrated files intact. WASM `Basics` retains its previous configuration pending the capture fix, keeping this partial migration green. -2. **Investigate retained timeouts.** Minimize `codegen/Generators.mls` and - `ups/examples/HindleyMilner.mls`; check emitted IR/JS when migrating those worksheets. -3. **Implement structural targets and external signatures.** Agree on opaque - selection policy, then migrate records, tuples, mutation, and JS interop in - small batches. Re-run affected negative tests as well as successful programs. +2. **Investigate the retained timeout.** Minimize `ups/examples/HindleyMilner.mls`, + which still times out and terminates SBT on the current JVM. The + `codegen/Generators.mls` trial now finishes but rejects unresolved `next` + selections; its earlier timeout was not reproduced. +3. **Complete structural targets and external signatures.** Dynamic JS values and + record fields are supported; finish tuple members, mutation, primitive members, + and foreign declarations in small batches. Re-run affected negative tests as + well as successful programs. 4. **Complete pattern transfers and reference preservation.** Start with UCS - conjunction/record/tuple cases, then compiled class patterns, then recursive - and transforming UPS cases. Keep fixed-point behavior tested independently - from matcher code generation. Resolve the constructor-pattern context-mixing + conjunction/record/tuple cases, then the remaining recursive and transforming + UPS cases. Keep fixed-point behavior tested independently from matcher code + generation. Resolve the constructor-pattern context-mixing blocker above before treating nominal field extraction as complete. 5. **Complete type/interface and call validation.** Resolve the pending interface question, finish declared result/field shapes, port module/generic checks, and @@ -267,17 +298,14 @@ errors when a selected interpretation lacks the required capability. ## Validation -- `ctest`: 45 compilation tests pass. -- `catest`: 20 application compilation tests pass in their existing mode. -- Main `dtest` after retaining the migrated batch: 681 tests pass. -- `adtest` after restoring deferred files: all 18 tests pass (16 worksheets and - two shared configuration files). -- The added `newres/TypeApplications.mls` regression passes separately. -- `hkmc2AllTests/test` passes after restoring WASM `Basics.mls` to its previous - configuration. All 22 WASM tests pass; the final main test run passes 685 tests. - The capture assertion remains a migration blocker, not an accepted test failure. -- This checkpoint includes the latest golden outputs and records the outstanding - migration blocker. No trial-generated application report changes remain. +- `ctest`: all 46 selected compilation tests pass. +- `catest`: all 20 application compilation fixtures pass in their existing mode. +- `cwtest`: the WASM compilation fixture passes. +- `hkmc2AllTests/test`: all tests pass, including 700 main tests, 18 application + tests, and 22 WASM tests. The main count includes the five new UPS directory + configurations; it does not mean that 700 worksheets have been migrated. +- All retained ports have reviewed goldens. Deferred worksheets retain their + original source and output; no trial-generated application report changes remain. ## Deferred-file inventory @@ -287,202 +315,153 @@ The files themselves retain the pre-trial configuration and output. ### basics -- `basics/BadAssignments.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: SelfRef(globalThis:globalThis) (of class SelfRef) -- `basics/BadModuleUses.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] This selection of member 'mtd' has no resolved target -- `basics/BadOverloading.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Not yet supported: overloading of function 'Foo' -- `basics/BadTypeClasses.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Resolution error in member reference; Value symbol 'someInt' cannot be used as a type +- `basics/BadAssignments.mls`: Unexpected exception; scala.NotImplementedError: SelfRef(globalThis:globalThis) (of class SelfRef) +- `basics/BadModuleUses.mls`: Unexpected lack of compilation or type error after `fun foo(): module null = M` +- `basics/BadOverloading.mls`: Unexpected lack of compilation or type error after `fun Foo = 1` +- `basics/BadTypeClasses.mls`: Unexpected lack of compilation or type error after `M.f` - `basics/Classes.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'id' has no resolved target - `basics/CompanionModules_Classes.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'C' cannot be called like a function. - `basics/CompanionModules_Functions.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'foo' cannot be called like a function. -- `basics/CyclicModuleForwarders.mls`: Unexpected exception; /!!!\ Uncaught error: java.lang.StackOverflowError +- `basics/CyclicModuleForwarders.mls`: Unexpected exception; java.lang.StackOverflowError - `basics/DynamicFields.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class constructor 'DynCtor' does not contain member 'dynField' -- `basics/DynamicInstantiation.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape -- `basics/DynamicSelection.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `basics/DynamicInstantiation.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) +- `basics/DynamicSelection.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) - `basics/ExplicitLabels.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Integer literal does not contain member 'break' -- `basics/FunDefs.mls`: Unexpected exception; /!!!\ Uncaught error: java.lang.StackOverflowError -- `basics/GenericClasses.mls`: Unexpected lack of compilation or type error; -- `basics/Inheritance.mls`: Unexpected lack of error to fix; [COMPILATION ERROR] This selection of member 'x' has no resolved target -- `basics/LiteralSelection.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `basics/MiscArrayTests.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `basics/ModuleMethods.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'module M' +- `basics/FunDefs.mls`: Unexpected exception; java.lang.StackOverflowError +- `basics/GenericClasses.mls`: Unexpected lack of compilation or type error after `class Foo[A](x: Foo[A, A])` +- `basics/Inheritance.mls`: Unexpected lack of error to fix after `[Bar.x, Bar.foo]` +- `basics/LiteralSelection.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `basics/MiscArrayTests.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `basics/ModuleMethods.mls`: Unexpected lack of compilation or type error after `fun f(m: M)` - `basics/MultiParamListClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Instance of class constructor 'Foo' cannot receive more argument lists. -- `basics/MutArr.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `basics/MutRcd.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `basics/MutVal.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `basics/New.mls`: Unexpected lack of warnings; [COMPILATION ERROR] Resolution error in application; Class 'Foo' cannot receive more argument lists. +- `basics/MutArr.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `basics/MutRcd.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Record literal does not contain member 'foo' +- `basics/MutVal.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) +- `basics/New.mls`: Unexpected lack of warnings after `Foo` - `basics/NewMut.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class 'Foo' does not contain member 'y' - `basics/NewlineOps.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'length' has no resolved target -- `basics/NewlineSels.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `basics/ObjectExtensions.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape +- `basics/NewlineSels.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'c' has no resolved target - `basics/OpenIn.mls`: Unexpected compilation error; [COMPILATION ERROR] Builtin '~' is not a binary operator - `basics/Overloading.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'Foo' cannot be called like a function. -- `basics/PrefixOps.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; -- `basics/Puns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(x))))) (of class Record) -- `basics/Records.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `basics/StrTest.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Unexpected term form in expression position (negation type) +- `basics/PrefixOps.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Bin' +- `basics/Puns.mls`: Unexpected exception; scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(x))))) (of class Record) +- `basics/Records.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'repeat' +- `basics/StrTest.mls`: Unexpected lack of compilation or type error after `(~)("a")` - `basics/Underscores.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'f' has no resolved target - `basics/ValMemberSymbols.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Integer literal cannot receive more argument lists. ### codegen -- `codegen/Arrays.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/AuxiliaryConstructors.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/BadGenerators.mls`: Unexpected warning; [WARNING] This annotation has no effect. -- `codegen/BadNew.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape +- `codegen/Arrays.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `codegen/AuxiliaryConstructors.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) - `codegen/BadOpen.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Module 'Foo' does not contain member 'y' -- `codegen/BadThis.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'clearInterval' has no resolved target -- `codegen/BasicTerms.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Integer literal cannot be called like a function. -- `codegen/BlockPrinter.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/ClassMatching.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/BlockPrinter.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `codegen/ClassMatching.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) - `codegen/ConfigDirective.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'call' has no resolved target - `codegen/ConsoleLog.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'log' has no resolved target - `codegen/CurriedClassInheritance.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Instance of class constructor 'Bar' cannot receive more argument lists. -- `codegen/CurriedClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in object instantiation; Class 'A' expected 1 argument, but got 1 -- `codegen/Do.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'hello' has no resolved target -- `codegen/ErasedTypes.mls`: Unexpected warning; [WARNING] This annotation has no effect. -- `codegen/FirstClassFunctionTransform.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/Generators.mls`: New-resolution trial exceeded the 25-second runner timeout. -- `codegen/Getters.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'whoops' has no resolved target +- `codegen/CurriedClasses.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) +- `codegen/ErasedTypes.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class definition 'Function' cannot be called like a function. +- `codegen/FirstClassFunctionTransform.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `codegen/Generators.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'next' has no resolved target +- `codegen/Getters.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'oops' has no resolved target - `codegen/Hygiene.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'foo' -- `codegen/ImportAlias.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'inc' has no resolved target -- `codegen/ImportExample.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/ImportJSClass.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape -- `codegen/ImportJSModule.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'greet' has no resolved target -- `codegen/ImportMLs.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Module 'Option' does not contain member 'oops' -- `codegen/ImportMLsJS.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'isDefined' has no resolved target +- `codegen/ImportExample.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) - `codegen/ImportedOps.mls`: Unexpected compilation error; [COMPILATION ERROR] Builtin '~' is not a binary operator -- `codegen/Inliner.mls`: Unexpected warning; [WARNING] This annotation has no effect. -- `codegen/InterleavedRecords.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/Misc.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/ModuleMethods.mls`: Unexpected lack of compilation or type error; +- `codegen/Inliner.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `codegen/Misc.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `codegen/ModuleMethods.mls`: Unexpected lack of compilation or type error after `Example |>. s(123)` - `codegen/Modules.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'None' cannot be called like a function. -- `codegen/NestedClasses.mls`: Unexpected internal error; [INTERNAL ERROR] Compiler reached an unexpected state at 'Lowering.scala:453': Unexpected class term shape -- `codegen/NestedScoped.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/NoFreeze.mls`: Unexpected compilation error; [COMPILATION ERROR] Assignment requires an unambiguous term member -- `codegen/NoModuleCheck.mls`: Unexpected lack of compilation or type error; [COMPILATION ERROR] Resolution error in object instantiation; +- `codegen/NestedClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'x' has no resolved target +- `codegen/NestedScoped.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `codegen/NoFreeze.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'x' has no resolved target +- `codegen/NoModuleCheck.mls`: Unexpected lack of compilation or type error after `M."foo"(1)` - `codegen/ObjectMethodDebinding.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'foo' has no resolved target - `codegen/Open.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'length' - `codegen/OpenWildcard.mls`: Unexpected compilation error; [COMPILATION ERROR] Wildcard-open reference 'None' is ambiguous -- `codegen/ParamClasses.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `codegen/ParamClasses.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) - `codegen/PartialApps.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Tuple literal cannot receive more argument lists. -- `codegen/PlainClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class 'Foo' cannot receive more argument lists. +- `codegen/PlainClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class value cannot receive more argument lists. - `codegen/PredefUsage.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot be called like a function. - `codegen/PrivateMembers.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'y' has no resolved target - `codegen/Pwd.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'pop' has no resolved target -- `codegen/QQImport.mls`: Unexpected runtime error; [RUNTIME ERROR] ReferenceError: Term is not defined -- `codegen/Quasiquotes.mls`: Unexpected runtime error; [RUNTIME ERROR] ReferenceError: Term is not defined -- `codegen/RandomStuff.mls`: Unexpected exception; /!!!\ Uncaught error: java.lang.StackOverflowError -- `codegen/ReboundLet.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/RuntimeUsage.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Class 'Str' does not contain member 'leave' -- `codegen/SanityChecks.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function literal expected 1 argument, but got 0 +- `codegen/Quasiquotes.mls`: Unexpected compilation error; [COMPILATION ERROR] Unsupported quasiquote type member reference +- `codegen/RandomStuff.mls`: Unexpected exception; java.lang.StackOverflowError +- `codegen/ReboundLet.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) +- `codegen/RuntimeUsage.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Class value does not contain member 'leaveOut' +- `codegen/SanityChecks.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function literal expected 2 arguments, but got 1 - `codegen/ScopedBlocks.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'Foo' cannot receive more argument lists. -- `codegen/ScopedBlocksAndHandlers.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Handle(h,Capture(MemberRef(member:Effect),term:f),List(),class:Handler$h$,List(HandlerTermDefinition(k,TermDefinition(Fun,member:perform,term:Handler$h$/perform,List(... -- `codegen/Scoping.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/SelfReferences.mls`: Unexpected lack of compilation or type error; -- `codegen/SetStmt.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `codegen/Spreads.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function 'foo' expected 4 arguments, but got 1 -- `codegen/This.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'a' has no resolved target +- `codegen/ScopedBlocksAndHandlers.mls`: Unexpected exception; shape propagation for `Handle` is unimplemented. +- `codegen/Scoping.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) +- `codegen/SelfReferences.mls`: Unexpected lack of compilation or type error after `val self = this` +- `codegen/SetStmt.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) - `codegen/ThisCallVariations.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'call' has no resolved target -- `codegen/ThisCalls.mls`: Unexpected lack of compilation or type error; -- `codegen/Throw.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class 'Error' cannot receive more argument lists. -- `codegen/TraceLog.mls`: Unexpected exception; /!!!\ Uncaught error: java.lang.StackOverflowError -- `codegen/UnitValue.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'log' has no resolved target +- `codegen/ThisCalls.mls`: Unexpected lack of compilation or type error after `Example |>. g(123)` +- `codegen/TraceLog.mls`: Unexpected exception; java.lang.StackOverflowError - `codegen/While.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Integer literal cannot be called like a function. ### ucs - `ucs/examples/BinarySearchTree.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. -- `ucs/examples/EitherOrBoth.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection of type None; Object 'None' cannot be used as a type +- `ucs/examples/EitherOrBoth.mls`: Unexpected exception; java.lang.Exception: Internal Error: Synthetic selections must not enter new resolution - `ucs/examples/LeftistTree.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. - `ucs/examples/ListFold.mls`: Unexpected compilation error; [COMPILATION ERROR] Builtin '~' is not a binary operator - `ucs/examples/ULC.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. -- `ucs/general/BooleanPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Composition(false,Negation(Literal(IntLit(2))),Negation(Literal(IntLit(3)))) (of class Composition) -- `ucs/hygiene/HygienicBindings.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class 'Error' cannot receive more argument lists. +- `ucs/general/BooleanPatterns.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Negation(Literal(IntLit(2))),Negation(Literal(IntLit(3)))) (of class Composition) +- `ucs/hygiene/HygienicBindings.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class value cannot receive more argument lists. - `ucs/normalization/Deduplication.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'length' has no resolved target -- `ucs/normalization/InheritanceNormalization.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:B),None)) (of class Composition) +- `ucs/normalization/InheritanceNormalization.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:B),None)) (of class Composition) - `ucs/normalization/OverlapOfPrimitives.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. -- `ucs/normalization/RecordImpliedByClass.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(av))), (Ident(b),Alias(Wildcard(),Ident(bv))))) (of class Record) -- `ucs/patterns/BooleansOps.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Composition(false,Alias(Wildcard(),Ident(a)),Alias(Wildcard(),Ident(b))) (of class Composition) -- `ucs/patterns/CompiledClassPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Add) (of class hkmc2.semantics.Term$MemberRef) -- `ucs/patterns/CompiledQualifiedConstructors.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: NewSel(SimpleRef(Inner),Ident(Wrapped),None) (of class hkmc2.semantics.Term$NewSel) -- `ucs/patterns/ConjunctionPattern.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Composition(false,Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:A),None)),Constructor(MemberRef(member:B),None)) (of class Comp... -- `ucs/patterns/RecordPattern.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Record(List((Ident(x),Alias(Wildcard(),Ident(a))), (Ident(y),Alias(Wildcard(),Ident(b))))) (of class Record) -- `ucs/patterns/String.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Concatenation(Literal(StrLit(0x)),Alias(Wildcard(),Ident(body))) (of class Concatenation) -- `ucs/patterns/where.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ucs/normalization/RecordImpliedByClass.mls`: Unexpected exception; scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(av))), (Ident(b),Alias(Wildcard(),Ident(bv))))) (of class Record) +- `ucs/patterns/BooleansOps.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Alias(Wildcard(),Ident(a)),Alias(Wildcard(),Ident(b))) (of class Composition) +- `ucs/patterns/ConjunctionPattern.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:A),None)),Constructor(MemberRef(member:B),None)) (of class Composition) +- `ucs/patterns/RecordPattern.mls`: Unexpected exception; scala.NotImplementedError: Record(List((Ident(x),Alias(Wildcard(),Ident(a))), (Ident(y),Alias(Wildcard(),Ident(b))))) (of class Record) +- `ucs/patterns/String.mls`: Unexpected exception; scala.NotImplementedError: Concatenation(Literal(StrLit(0x)),Alias(Wildcard(),Ident(body))) (of class Concatenation) +- `ucs/patterns/where.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'get' has no resolved target - `ucs/syntax/Else.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class 'Set' does not contain member 'has' - `ucs/syntax/SimpleUCS.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'get' has no resolved target ### ups -- `ups/BasicStackPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack)),Ident(x)) (of class hkmc2.semantics.Term$UnresolvedRef) -- `ups/EmptyJunctions.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: Range(IntLit(5),IntLit(3),true) (of class Range) -- `ups/Future.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Pattern symbol 'Test' cannot be used as a type -- `ups/LocalPatterns.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; -- `ups/MatchResult.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; -- `ups/RecursiveTransformations.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(b) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/SimpleConjunction.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:observeLeft) (of class hkmc2.semantics.Term$MemberRef) -- `ups/SimpleTransform.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(a) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/TransformFree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/UpsBugsBacklog.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(c) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/examples/BasicSeqStackParse.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `ups/examples/BasicStackParse.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) -- `ups/examples/Computation.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(f) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/examples/DnfCnf.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Or) (of class hkmc2.semantics.Term$MemberRef) -- `ups/examples/DoubleOrSum.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:addWithPrint) (of class hkmc2.semantics.Term$MemberRef) -- `ups/examples/DoubleTripleList.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack), MemberRef(member:annotations)),Ident(Nil)) (of class hkmc2.semantics.Term$UnresolvedRef) -- `ups/examples/EvaluationContext.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `ups/examples/EvaluationContext2.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -- `ups/examples/Extraction.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: NewSel(MemberRef(member:Option),Ident(Some),None) (of class hkmc2.semantics.Term$NewSel) -- `ups/examples/Flatten.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack), MemberRef(member:annotations)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) -- `ups/examples/HindleyMilner.mls`: New-resolution trial exceeded the 25-second runner timeout. -- `ups/examples/ListPredicates.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(list) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/examples/Negation.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/examples/PrecedenceClimbStackParse.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) -- `ups/examples/Record.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(weight) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/examples/TupleSpread.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/fixpoint/Diagnostics.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/fixpoint/FixedPointPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/fixpoint/IndirectRecursion.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/EmptyJunctions.mls`: Unexpected exception; scala.NotImplementedError: Range(IntLit(5),IntLit(3),true) (of class Range) +- `ups/Future.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Test' +- `ups/LocalPatterns.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Expected a term; got pattern 'Zero' +- `ups/MatchResult.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Cross' +- `ups/RecursiveTransformations.mls`: Unexpected exception; scala.NotImplementedError: Negation(Constructor(Capture(MemberRef(member:Bin),term:f),None)) (of class Negation) +- `ups/SimpleTransform.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function 'area' expected 1 argument, but got 2 +- `ups/examples/BasicSeqStackParse.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'at' has no resolved target +- `ups/examples/Computation.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'padStart' has no resolved target +- `ups/examples/DoubleTripleList.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'head' has no resolved target +- `ups/examples/EvaluationContext.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) +- `ups/examples/EvaluationContext2.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) +- `ups/examples/HindleyMilner.mls`: Timeout under new resolution; the test runner terminated SBT. +- `ups/examples/ListPredicates.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'length' has no resolved target +- `ups/examples/Negation.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function 'flatten' expected 1 argument, but got 3 +- `ups/examples/PrecedenceClimbStackParse.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'ParseStep' +- `ups/examples/Record.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function 'bmi' expected 1 argument, but got 2 - `ups/fixpoint/ListFusion.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target -- `ups/fixpoint/MoreAlternatives.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/fixpoint/NonCatchAll.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/fixpoint/RecursionAlternatives.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/fixpoint/SimpleExample.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(a) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/fixpoint/UnsupportedShapes.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/nondeterminism/BitArithmetic.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:And) (of class hkmc2.semantics.Term$MemberRef) -- `ups/nondeterminism/EvenOddTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:B) (of class hkmc2.semantics.Term$MemberRef) -- `ups/nondeterminism/LaRbTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:A) (of class hkmc2.semantics.Term$MemberRef) -- `ups/parametric/EtaConversion.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Zero) (of class hkmc2.semantics.Term$MemberRef) -- `ups/parametric/HigherOrderPattern.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Int) (of class hkmc2.semantics.Term$MemberRef) -- `ups/parametric/ListLike.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/parametric/Nullable.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/recursion/BitSeq.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Pair) (of class hkmc2.semantics.Term$MemberRef) -- `ups/recursion/BitTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Pair) (of class hkmc2.semantics.Term$MemberRef) -- `ups/recursion/LeafEvenOddTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:B) (of class hkmc2.semantics.Term$MemberRef) -- `ups/recursion/NatBox.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Box) (of class hkmc2.semantics.Term$MemberRef) -- `ups/recursion/NullTree.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Pair) (of class hkmc2.semantics.Term$MemberRef) -- `ups/recursion/SignBox.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Box) (of class hkmc2.semantics.Term$MemberRef) -- `ups/regex/EmailAddress.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:UserNameLetter) (of class hkmc2.semantics.Term$MemberRef) +- `ups/regex/EmailAddress.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) - `ups/regex/Identifier.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'forEach' has no resolved target -- `ups/regex/Separation.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(t) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/regex/TailRepetition.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; -- `ups/specialization/SimpleList.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Box) (of class hkmc2.semantics.Term$MemberRef) -- `ups/specialization/SimpleLiterals.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:observeZero) (of class hkmc2.semantics.Term$MemberRef) -- `ups/syntax/InterestingPatterns.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) -- `ups/syntax/MixedParameters.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: MemberRef(member:Bit) (of class hkmc2.semantics.Term$MemberRef) -- `ups/syntax/PatternBody.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: UnresolvedRef(List(MemberRef(member:Stack)),Ident(::)) (of class hkmc2.semantics.Term$UnresolvedRef) -- `ups/transformation/BindingLess.mls`: Unexpected exception; /!!!\ Uncaught error: scala.MatchError: SimpleRef(x) (of class hkmc2.semantics.Term$SimpleRef) +- `ups/regex/Separation.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Integer' +- `ups/regex/TailRepetition.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Zero' +- `ups/syntax/InterestingPatterns.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'TreeDepth' +- `ups/syntax/PatternBody.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target ### apps -- `apps/AccountingTest.mls`: Unexpected exception; /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +- `apps/AccountingTest.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) - `apps/CSVTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member '1' has no resolved target - `apps/IterTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'reverse' has no resolved target - `apps/parsing-web-demo/ExamplesTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'sort' has no resolved target - `apps/parsing/DirectiveTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'display' has no resolved target - `apps/parsing/LeftRecursion.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'display' has no resolved target - `apps/parsing/LexerTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'join' has no resolved target -- `apps/parsing/ParseRuleVisualizerTest.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class 'Error' cannot receive more argument lists. +- `apps/parsing/ParseRuleVisualizerTest.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class value cannot receive more argument lists. - `apps/parsing/PrattParsingTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target - `apps/parsing/RecursiveDescentTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target -- `apps/parsing/RulesTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'display' has no resolved target +- `apps/parsing/RulesTest.mls`: Unexpected exception; java.lang.Exception: Internal Error: Synthetic selections must not enter new resolution + +### wasm + +- `wasm/Basics.mls`: Unexpected exception; java.lang.IllegalArgumentException: requirement failed: Expected symbol term:getX⁰ but got term:Foo⁰ +- `wasm/Binaryen.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class 'Instance' does not contain member 'exports' diff --git a/hkmc2/shared/src/test/mlscript/apps/.mls b/hkmc2/shared/src/test/mlscript/apps/.mls index f84908e26a..e3f9030862 100644 --- a/hkmc2/shared/src/test/mlscript/apps/.mls +++ b/hkmc2/shared/src/test/mlscript/apps/.mls @@ -1,2 +1 @@ :.. -#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/basics/.mls b/hkmc2/shared/src/test/mlscript/basics/.mls index f84908e26a..e3f9030862 100644 --- a/hkmc2/shared/src/test/mlscript/basics/.mls +++ b/hkmc2/shared/src/test/mlscript/basics/.mls @@ -1,2 +1 @@ :.. -#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls index ff0c9b5d64..4a275c91c1 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls @@ -1,3 +1,4 @@ +:. :js @@ -5,11 +6,14 @@ object Foo :fixme // Support? new Foo -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found reference of type Foo. -//│ ║ l.7: new Foo -//│ ║ ^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] TypeError: Foo1 is not a constructor +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.8: new Foo +//│ ║ ^^^^^^^ +//│ ╟── Object symbol 'Foo' cannot be instantiated with keyword 'new'. +//│ ║ l.5: object Foo +//│ ╙── ^^^^^^^^^^ +//│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :re new! Foo @@ -17,39 +21,47 @@ new! Foo :fixme // Support? new! Foo.class -//│ ╔══[COMPILATION ERROR] Object 'Foo' does not contain member 'class' -//│ ║ l.19: new! Foo.class -//│ ╙── ^^^^^^ -//│ = Foo +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.23: new! Foo.class +//│ ║ ^^^^^^^^^ +//│ ╟── Object 'Foo' does not contain member 'class' +//│ ║ l.5: object Foo +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. new! Foo!class //│ = Foo :fixme // Support? object Bar extends Foo -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found reference of type Foo. -//│ ║ l.29: object Bar extends Foo +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.36: object Bar extends Foo //│ ║ ^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] TypeError: Class extends value [object Object] is not a constructor or null +//│ ╟── Object symbol 'Foo' cannot be instantiated with keyword 'new'. +//│ ║ l.5: object Foo +//│ ╙── ^^^^^^^^^^ +//│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :re object Bar extends Foo!class -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found dynamic selection. -//│ ║ l.38: object Bar extends Foo!class +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.48: object Bar extends Foo!class //│ ║ ^^^^^^^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] TypeError: Cannot redefine property: class +//│ ╟── Invalid class expression: dynamic selection +//│ ║ l.48: object Bar extends Foo!class +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme // Support? object Bar extends! Foo!class -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found reference. -//│ ║ l.46: object Bar extends! Foo!class -//│ ║ ^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ╔══[COMPILATION ERROR] Too many parameter lists for parent class -//│ ║ l.46: object Bar extends! Foo!class -//│ ╙── ^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.58: object Bar extends! Foo!class +//│ ║ ^^^^^^^^^^^ +//│ ╟── Invalid class expression: reference +//│ ║ l.58: object Bar extends! Foo!class +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/.mls b/hkmc2/shared/src/test/mlscript/codegen/.mls index f84908e26a..e3f9030862 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/.mls @@ -1,2 +1 @@ :.. -#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls b/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls index 7c59fd113a..c7cb6ab466 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -5,7 +6,7 @@ open annotations :ge yield 1 //│ ╔══[COMPILATION ERROR] Yield expressions are not allowed in this context. -//│ ║ l.6: yield 1 +//│ ║ l.7: yield 1 //│ ╙── ^^^^^^^ //│ = 1 @@ -14,7 +15,7 @@ yield 1 () => 3 //│ ╔══[WARNING] This annotation has no effect. //│ ╟── This annotation is not supported on function literal terms. -//│ ║ l.14: () => 3 +//│ ║ l.15: () => 3 //│ ╙── ^^^^^^^ //│ = fun @@ -25,17 +26,17 @@ class C with fun f = yield 1 //│ ╔══[COMPILATION ERROR] Generators are not supported on functions without a parameter list -//│ ║ l.25: fun f = +//│ ║ l.26: fun f = //│ ║ ^^^^^^^ -//│ ║ l.26: yield 1 +//│ ║ l.27: yield 1 //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Yield expressions are not allowed in this context. -//│ ║ l.26: yield 1 +//│ ║ l.27: yield 1 //│ ╙── ^^^^^^^ :ge @generator fun bad = 1 //│ ╔══[COMPILATION ERROR] Generators are not supported on functions without a parameter list -//│ ║ l.38: fun bad = 1 +//│ ║ l.39: fun bad = 1 //│ ╙── ^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls b/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls index 3f7703c54f..0952c4cbf9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls @@ -1,3 +1,4 @@ +:. :js @@ -5,44 +6,50 @@ :re new //│ ╔══[PARSE ERROR] Expected 'with' keyword, expression, or block after `new` keyword; found end of input instead -//│ ║ l.6: new +//│ ║ l.7: new //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :re new 2 -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found integer literal. -//│ ║ l.14: new 2 -//│ ║ ^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] TypeError: 2 is not a constructor +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.15: new 2 +//│ ║ ^^^^^ +//│ ╟── Invalid class expression: integer literal +//│ ║ l.15: new 2 +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :re new 2 + 2 -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found integer literal. -//│ ║ l.23: new 2 + 2 -//│ ║ ^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] TypeError: 2 is not a constructor +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.26: new 2 + 2 +//│ ║ ^^^^^ +//│ ╟── Invalid class expression: integer literal +//│ ║ l.26: new 2 + 2 +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :sjs :re new! 2 + 2 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let tmp1; tmp1 = globalThis.Object.freeze(new 2()); return tmp1 + 2 +//│ let tmp; tmp = globalThis.Object.freeze(new 2()); return tmp + 2 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] TypeError: 2 is not a constructor :e :re new() -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found unit value. -//│ ║ l.40: new() -//│ ║ ^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] TypeError: runtime.Unit is not a constructor +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.45: new() +//│ ║ ^^^^^ +//│ ╟── Invalid class expression: unit value +//│ ║ l.45: new() +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :re new!() @@ -51,14 +58,16 @@ new!() :e :re new {} -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found unit value. -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] TypeError: runtime.Unit is not a constructor +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.60: new {} +//│ ║ ^^^ +//│ ╙── Invalid class expression: unit value +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :pe new! {} //│ ╔══[PARSE ERROR] Expected expression after prefix keyword 'new!'; found end of block instead -//│ ║ l.59: new! {} +//│ ║ l.68: new! {} //│ ╙── ^^ @@ -75,17 +84,19 @@ new { x = 1 } //│ lhs = Ident of "x" //│ rhs = IntLit of 1 //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.70: new { x = 1 } +//│ ║ l.79: new { x = 1 } //│ ╙── ^ -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found block. -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.79: new { x = 1 } +//│ ║ ^^^^^^^^^^^ +//│ ╙── Invalid class expression: block //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e new! { x = 1 } //│ ╔══[COMPILATION ERROR] Unrecognized definitional assignment left-hand side: prefix operator 'new!' -//│ ║ l.86: new! { x = 1 } +//│ ║ l.97: new! { x = 1 } //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -93,12 +104,14 @@ new! { x = 1 } :e let xs = new Oopsie //│ ╔══[COMPILATION ERROR] Name not found: Oopsie -//│ ║ l.94: let xs = new Oopsie -//│ ╙── ^^^^^^ -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found ‹error›. -//│ ║ l.94: let xs = new Oopsie -//│ ║ ^^^^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. +//│ ║ l.105: let xs = new Oopsie +//│ ╙── ^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.105: let xs = new Oopsie +//│ ║ ^^^^^^^^^^ +//│ ╟── Invalid class expression: ‹error› +//│ ║ l.105: let xs = new Oopsie +//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadThis.mls b/hkmc2/shared/src/test/mlscript/codegen/BadThis.mls index 0e530bb6d7..9ee5fe7a3b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadThis.mls @@ -1,3 +1,4 @@ +:. :js @@ -18,9 +19,16 @@ val globalThis = "oops" //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') +:e :re globalThis.clearInterval -//│ ═══[RUNTIME ERROR] TypeError: globalThis.Error is not a function +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.24: globalThis.clearInterval +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── String literal does not contain member 'clearInterval' +//│ ║ l.18: val globalThis = "oops" +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') :re diff --git a/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls b/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls index 9ee03eeac2..3dc2e7aa41 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls @@ -1,3 +1,4 @@ +:. :js :global @@ -15,7 +16,7 @@ 1 2 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.15: 1 +//│ ║ l.16: 1 //│ ╙── ^ //│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— //│ Program: @@ -57,8 +58,15 @@ print("Hi") //│ = 2 +:e :re 2(2) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.63: 2(2) +//│ ║ ^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.63: 2(2) +//│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ return runtime.safeCall(2(2)) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/codegen/Do.mls b/hkmc2/shared/src/test/mlscript/codegen/Do.mls index 4bc1993423..c1e2256551 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Do.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Do.mls @@ -1,3 +1,4 @@ +:. :js @@ -17,7 +18,7 @@ do console.log("ok") :w do 1 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.18: do 1 +//│ ║ l.19: do 1 //│ ╙── ^ do @@ -39,18 +40,18 @@ val f = case //│ Split with nested patterns: //│ > case //│ > caseScrut is 0 then "null" -//│ > let tmp:unused = member:console⁰.log‹member:log›("non-null") +//│ > let tmp:unused = member:console⁰^term:f⁰.log("non-null") //│ > caseScrut is 1 then "unit" //│ > let res = "other" -//│ > let tmp:unused = (member:Predef⁰.)print‹member:print›(res⁰) +//│ > let tmp:unused = member:Predef⁰.print^term:f⁰(res⁰) //│ > caseScrut is _ then res⁰ //│ Expanded split with flattened patterns: //│ > ‹if|while› //│ > caseScrut is 0 then "null" -//│ > let tmp:unused = member:console⁰.log‹member:log›("non-null") +//│ > let tmp:unused = member:console⁰^term:f⁰.log("non-null") //│ > caseScrut is 1 then "unit" //│ > let res = "other" -//│ > let tmp:unused = (member:Predef⁰.)print‹member:print›(res⁰) +//│ > let tmp:unused = member:Predef⁰.print^term:f⁰(res⁰) //│ > else res⁰ //│ f = fun diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportAlias.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportAlias.mls index 146759266e..65a47617a3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportAlias.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportAlias.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,8 +7,11 @@ import "../../mlscript-compile/Example.mls" as AnotherExample :e Example.inc(0) //│ ╔══[COMPILATION ERROR] Name not found: Example -//│ ║ l.7: Example.inc(0) +//│ ║ l.8: Example.inc(0) //│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'inc' has no resolved target +//│ ║ l.8: Example.inc(0) +//│ ╙── ^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :expect 1 @@ -21,8 +25,11 @@ import "../../mlscript-compile/Example.mjs" as JSExample :e Example.inc(0) //│ ╔══[COMPILATION ERROR] Name not found: Example -//│ ║ l.22: Example.inc(0) +//│ ║ l.26: Example.inc(0) //│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'inc' has no resolved target +//│ ║ l.26: Example.inc(0) +//│ ╙── ^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :expect 1 @@ -33,13 +40,13 @@ JSExample.inc(0) :pe import "../../mlscript-compile/Example.mls" as //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.34: import "../../mlscript-compile/Example.mls" as +//│ ║ l.41: import "../../mlscript-compile/Example.mls" as //│ ╙── ^ :e import "../../mlscript-compile/Example.mls" as "Examle" //│ ╔══[COMPILATION ERROR] Expected identifier after 'as' in import statement -//│ ║ l.40: import "../../mlscript-compile/Example.mls" as "Examle" +//│ ║ l.47: import "../../mlscript-compile/Example.mls" as "Examle" //│ ╙── ^^^^^^^^ import "../../mlscript-compile/Example.mls" diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportJSClass.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportJSClass.mls index 7a92461d77..184e52c0bc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportJSClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportJSClass.mls @@ -1,3 +1,4 @@ +:. :js @@ -29,7 +30,7 @@ fun foo() = import "../../js/MyClass.mjs" () //│ ╔══[COMPILATION ERROR] Imports must be at the top level -//│ ║ l.29: import "../../js/MyClass.mjs" +//│ ║ l.30: import "../../js/MyClass.mjs" //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportJSModule.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportJSModule.mls index ea0e5c828e..4fbc580c0a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportJSModule.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportJSModule.mls @@ -1,3 +1,4 @@ +:. :js import "../../js/Test.mjs" diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls index abbb799d49..a08f45f197 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls @@ -1,3 +1,4 @@ +:. :js @@ -81,25 +82,47 @@ None == Option.None :e :re Option.oops -//│ ╔══[COMPILATION ERROR] Module 'Option' does not contain member 'oops' -//│ ║ l.83: Option.oops -//│ ╙── ^^^^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.84: Option.oops +//│ ║ ^^^^^^^^^^^ +//│ ╟── Module 'Option' does not contain member 'oops' +//│ ║ in ../../mlscript-compile/Option.mls +//│ ║ l.4: module Option with ... +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e open Option { oops } -//│ ╔══[COMPILATION ERROR] Module 'Option' does not contain member 'oops' -//│ ║ l.90: open Option { oops } -//│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.95: open Option { oops } +//│ ║ ^^^^ +//│ ╟── Module 'Option' does not contain member 'oops' +//│ ║ in ../../mlscript-compile/Option.mls +//│ ║ l.4: module Option with ... +//│ ╙── ^^^^^^ +:e +:re oops +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.106: oops +//│ ║ ^^^^ +//│ ╟── Module 'Option' does not contain member 'oops' +//│ ║ in ../../mlscript-compile/Option.mls +//│ ║ l.4: module Option with ... +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :re Option.None.oops -//│ ╔══[COMPILATION ERROR] Object 'None' does not contain member 'oops' -//│ ║ l.99: Option.None.oops -//│ ╙── ^^^^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.118: Option.None.oops +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Object 'None' does not contain member 'oops' +//│ ║ in ../../mlscript-compile/Option.mls +//│ ║ l.8: object None +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls index 328c6a8ebb..5d2ce98a77 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,11 +7,14 @@ import "../../mlscript-compile/Option.mjs" :e Option.isDefined(new Option.Some(1)) -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found selection. -//│ ║ l.8: Option.isDefined(new Option.Some(1)) -//│ ║ ^^^^^^^^^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ = true +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.9: Option.isDefined(new Option.Some(1)) +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── Dynamic value cannot be instantiated with keyword 'new'. +//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ║ l.9: Option.isDefined(new Option.Some(1)) +//│ ╙── ^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. Option.isDefined(new! Option.Some(1)) //│ = true diff --git a/hkmc2/shared/src/test/mlscript/codegen/InterleavedRecords.mls b/hkmc2/shared/src/test/mlscript/codegen/InterleavedRecords.mls index 3bb5656eef..4c3b5650a5 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/InterleavedRecords.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/InterleavedRecords.mls @@ -1,3 +1,4 @@ +:. :js @@ -71,7 +72,7 @@ let rcd = a: 0 42 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.72: 42 +//│ ║ l.73: 42 //│ ╙── ^^ //│ > Hello! //│ rcd = {a: 0} diff --git a/hkmc2/shared/src/test/mlscript/codegen/QQImport.mls b/hkmc2/shared/src/test/mlscript/codegen/QQImport.mls index 3bbaa93b76..3abcf21223 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/QQImport.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/QQImport.mls @@ -1,3 +1,4 @@ +:. :js :qq diff --git a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls index 9546efd078..5a4882a0c5 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls @@ -1,3 +1,4 @@ +:. :js @@ -23,8 +24,15 @@ foo(1, ...[2, 3], 4) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = [1, 2, 3, 4] +:e :re foo(...a) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.29: foo(...a) +//│ ║ ^^^^^^^^^ +//│ ╟── Function 'foo' expected 4 arguments, but got 3 +//│ ║ l.12: fun foo(w, x, y, z) = [w, x, y, z] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'foo' expected 4 arguments but got 3 @@ -59,9 +67,21 @@ data class A(...r) let x = new A(1, 2, 3) x.r //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. -//│ ║ l.58: data class A(...r) +//│ ║ l.66: data class A(...r) //│ ╙── ^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'r' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.67: let x = new A(1, 2, 3) +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Class value expected 0 arguments, but got 3 +//│ ║ l.66: data class A(...r) +//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.68: x.r +//│ ║ ^^^ +//│ ╟── Instance of class 'A' does not contain member 'r' +//│ ║ l.67: let x = new A(1, 2, 3) +//│ ╙── ^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[1, 2, 3]', got: 'undefined' //│ x = A() @@ -71,13 +91,17 @@ data class A(...r) with fun getR = r new A(1, 2, 3).getR //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. -//│ ║ l.70: data class A(...r) with +//│ ║ l.90: data class A(...r) with //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.92: new A(1, 2, 3).getR +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Class value expected 0 arguments, but got 3 +//│ ║ l.90: data class A(...r) with +//│ ╙── ^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'r' -//│ ║ l.71: fun getR = r -//│ ║ ^ //│ ╟── which references the symbol introduced here -//│ ║ l.70: data class A(...r) with +//│ ║ l.90: data class A(...r) with //│ ╙── ^ //│ ═══[RUNTIME ERROR] ReferenceError: r is not defined //│ ═══[RUNTIME ERROR] Expected: '[1, 2, 3]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/codegen/This.mls b/hkmc2/shared/src/test/mlscript/codegen/This.mls index 0ed3ece219..559cd0806a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/This.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/This.mls @@ -1,3 +1,4 @@ +:. :js @@ -7,8 +8,11 @@ fun test(x) = [this.a, x] //│ ╔══[COMPILATION ERROR] Cannot use 'this' outside of an object scope -//│ ║ l.8: [this.a, x] +//│ ║ l.9: [this.a, x] //│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'a' has no resolved target +//│ ║ l.9: [this.a, x] +//│ ╙── ^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let test; //│ test = function test(x) { throw "This code cannot be run as its compilation yielded an error." }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls index 6decf80e0a..4e57453244 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls @@ -1,13 +1,14 @@ +:. :js :re -throw Error("e") +throw new Error("e") //│ ═══[RUNTIME ERROR] Error: e :re -fun f(x, y) = throw Error("e") +fun f(x, y) = throw new Error("e") f(2, 2) //│ ═══[RUNTIME ERROR] Error: e @@ -15,7 +16,7 @@ f(2, 2) :re fun f(x, y) = if x == y then x + y - x == (y + 1) then throw Error("not equal") + x == (y + 1) then throw new Error("not equal") f(3, 2) //│ ═══[RUNTIME ERROR] Error: not equal @@ -27,15 +28,15 @@ f(2, 3) :re :sjs fun f(x) = - let y = throw Error("e") + let y = throw new Error("e") return y f(1) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f2; //│ f2 = function f(x) { -//│ throw runtime.safeCall(globalThis.Error("e")) +//│ throw globalThis.Object.freeze(new globalThis.Error("e")) //│ }; -//│ throw runtime.safeCall(globalThis.Error("e")) +//│ throw globalThis.Object.freeze(new globalThis.Error("e")) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] Error: e @@ -43,20 +44,20 @@ f(1) :re :sjs fun f(x) = - throw (if x then Error("x") else Error("y")) + throw (if x then new Error("x") else new Error("y")) f(false) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f3; //│ f3 = function f(x) { //│ if (x === true) { -//│ throw runtime.safeCall(globalThis.Error("x")) +//│ throw globalThis.Object.freeze(new globalThis.Error("x")) //│ } -//│ throw runtime.safeCall(globalThis.Error("y")); +//│ throw globalThis.Object.freeze(new globalThis.Error("y")); //│ }; //│ if (false === true) { -//│ throw runtime.safeCall(globalThis.Error("x")) +//│ throw globalThis.Object.freeze(new globalThis.Error("x")) //│ } -//│ throw runtime.safeCall(globalThis.Error("y")); +//│ throw globalThis.Object.freeze(new globalThis.Error("y")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] Error: y @@ -92,7 +93,7 @@ foo(42) //│ ═══[RUNTIME ERROR] 42 :re -foo(Error("hi")) +foo(new Error("hi")) //│ > caught something else //│ ═══[RUNTIME ERROR] Error: hi diff --git a/hkmc2/shared/src/test/mlscript/codegen/UnitValue.mls b/hkmc2/shared/src/test/mlscript/codegen/UnitValue.mls index b9c3ef6a79..58683fbf65 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/UnitValue.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/UnitValue.mls @@ -1,3 +1,4 @@ +:. :js @@ -57,7 +58,7 @@ print of Box(foo()).value fun foo() = while false do 0 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.58: while false do 0 +//│ ║ l.59: while false do 0 //│ ╙── ^ print of Box(foo()).value @@ -88,7 +89,7 @@ print of Box(foo(1)).value :e fun f = let x = 1 //│ ╔══[COMPILATION ERROR] Expected a body for let bindings in expression position -//│ ║ l.89: fun f = let x = 1 +//│ ║ l.90: fun f = let x = 1 //│ ╙── ^^^^^ print of f diff --git a/hkmc2/shared/src/test/mlscript/ucs/.mls b/hkmc2/shared/src/test/mlscript/ucs/.mls index f84908e26a..e3f9030862 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/.mls @@ -1,2 +1 @@ :.. -#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledClassPatterns.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledClassPatterns.mls index 91544def01..334671738f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledClassPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledClassPatterns.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -153,10 +154,10 @@ class Add2(val x, val y, z) :e case @compile (Add2(0, _, _) | Add2(_, 1, _)) then 0 //│ ╔══[COMPILATION ERROR] Parameter `z` is not accessible. -//│ ║ l.151: class Add2(val x, val y, z) +//│ ║ l.152: class Add2(val x, val y, z) //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Parameter `z` is not accessible. -//│ ║ l.151: class Add2(val x, val y, z) +//│ ║ l.152: class Add2(val x, val y, z) //│ ╙── ^ //│ = fun @@ -169,7 +170,8 @@ fun compiledInner(module Inner: Outer.Inner, x) = if x is @compile Inner.Wrapped(3) then true else false //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define compiledInner⁰ as fun compiledInner¹(Inner, x) { -//│ let matcher__Wrappedʿ3ʾ$, matcher__3$, matchSuccess, lambda, lambda1; +//│ let scrut, matcher__Wrappedʿ3ʾ$, matcher__3$, matchSuccess, lambda, lambda1; +//│ set scrut = x; //│ define lambda as fun lambda⁵(input) { //│ let value, value1, p_0$, result1$, p_0$1, tmp, tmp1; //│ match input @@ -207,7 +209,7 @@ fun compiledInner(module Inner: Outer.Inner, x) = //│ end //│ }; //│ set matcher__3$ = lambda⁶; -//│ set matchSuccess = matcher__Wrappedʿ3ʾ$(x); +//│ set matchSuccess = matcher__Wrappedʿ3ʾ$(scrut); //│ match matchSuccess //│ true => //│ return true diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledQualifiedConstructors.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledQualifiedConstructors.mls index acbe37642d..52fb50ade8 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledQualifiedConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledQualifiedConstructors.mls @@ -1,3 +1,4 @@ +:. :js open annotations diff --git a/hkmc2/shared/src/test/mlscript/ups/.mls b/hkmc2/shared/src/test/mlscript/ups/.mls index f84908e26a..e3f9030862 100644 --- a/hkmc2/shared/src/test/mlscript/ups/.mls +++ b/hkmc2/shared/src/test/mlscript/ups/.mls @@ -1,2 +1 @@ :.. -#lang(strictResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/ups/BasicStackPatterns.mls b/hkmc2/shared/src/test/mlscript/ups/BasicStackPatterns.mls index d7adcc017b..2ee9a6b6ee 100644 --- a/hkmc2/shared/src/test/mlscript/ups/BasicStackPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ups/BasicStackPatterns.mls @@ -1,3 +1,4 @@ +:. :js @@ -9,12 +10,12 @@ open Stack :e :w pattern P = x :: xs => [x, xs] -//│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.11: pattern P = x :: xs => [x, xs] -//│ ╙── ^ //│ ╔══[WARNING] Unused pattern binding: x. -//│ ║ l.11: pattern P = x :: xs => [x, xs] +//│ ║ l.12: pattern P = x :: xs => [x, xs] //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: x +//│ ║ l.12: pattern P = x :: xs => [x, xs] +//│ ╙── ^ pattern P = (x :: xs) => [x, xs] diff --git a/hkmc2/shared/src/test/mlscript/ups/SimpleConjunction.mls b/hkmc2/shared/src/test/mlscript/ups/SimpleConjunction.mls index 1dabbc9f85..fbd5b72b65 100644 --- a/hkmc2/shared/src/test/mlscript/ups/SimpleConjunction.mls +++ b/hkmc2/shared/src/test/mlscript/ups/SimpleConjunction.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/ups/TransformFree.mls b/hkmc2/shared/src/test/mlscript/ups/TransformFree.mls index 4f54ac9876..978a236311 100644 --- a/hkmc2/shared/src/test/mlscript/ups/TransformFree.mls +++ b/hkmc2/shared/src/test/mlscript/ups/TransformFree.mls @@ -1,3 +1,4 @@ +:. :js open annotations diff --git a/hkmc2/shared/src/test/mlscript/ups/UpsBugsBacklog.mls b/hkmc2/shared/src/test/mlscript/ups/UpsBugsBacklog.mls index 75f3fbb801..c2b30ff2c1 100644 --- a/hkmc2/shared/src/test/mlscript/ups/UpsBugsBacklog.mls +++ b/hkmc2/shared/src/test/mlscript/ups/UpsBugsBacklog.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -46,9 +47,7 @@ if tokens is @compile Parse as stack do print(stack) :fixme // Should be a "name not found" if tokens is @compile Parse as stack do print(stack) -//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'stack' -//│ ║ l.48: @compile Parse as stack do print(stack) -//│ ╙── ^^^^^ +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'stack' // What's actually intended: if tokens is diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/BasicStackParse.mls b/hkmc2/shared/src/test/mlscript/ups/examples/BasicStackParse.mls index 59f7c24d47..0bcaff6622 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/BasicStackParse.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/BasicStackParse.mls @@ -1,3 +1,4 @@ +:. :js @@ -70,10 +71,10 @@ pattern Parse = ((Expr as e) :: "+" :: (Expr as e) :: rest) => Op(e, "+", e) :: rest ) //│ ╔══[COMPILATION ERROR] Unexpected record property pattern. -//│ ║ l.69: ((Str as name) :: rest) => Id(name) :: rest +//│ ║ l.70: ((Str as name) :: rest) => Id(name) :: rest //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unexpected record property pattern. -//│ ║ l.70: ((Expr as e) :: "+" :: (Expr as e) :: rest) => Op(e, "+", e) :: rest +//│ ║ l.71: ((Expr as e) :: "+" :: (Expr as e) :: rest) => Op(e, "+", e) :: rest //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls b/hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls index 8f80b027ce..a239449e03 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls @@ -1,3 +1,4 @@ +:. :js open annotations diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls b/hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls index 2573afb86c..37d1e8b3da 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls @@ -1,3 +1,4 @@ +:. :js open annotations diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls index 1f0025923c..d7b08b6084 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/Flatten.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Flatten.mls index 8e8e9f90dd..9641eb82d7 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/Flatten.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/Flatten.mls @@ -1,3 +1,4 @@ +:. :js open annotations diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls b/hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls index fc56f338d4..572349ad53 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls @@ -1,3 +1,4 @@ +:. :js // Extract the first and last elements of a list diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/Diagnostics.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/Diagnostics.mls index 565ba234cd..fd087d6ac6 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/Diagnostics.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/Diagnostics.mls @@ -1,3 +1,4 @@ +:. :js // Diagnostics parity: warnings produced by the other compilation paths must @@ -21,7 +22,7 @@ pattern Steps = UnBox as Steps | _ :expect "matched" if Box(0) is @compile Steps(CaptureBit(ZeroToOne)) then "matched" else "no match" //│ ╔══[WARNING] This extraction argument's transformation result is discarded by pattern `CaptureBit`. -//│ ║ l.22: if Box(0) is @compile Steps(CaptureBit(ZeroToOne)) then "matched" else "no match" +//│ ║ l.23: if Box(0) is @compile Steps(CaptureBit(ZeroToOne)) then "matched" else "no match" //│ ║ ^^^^^^^^^ //│ ╙── Write `... as _` to discard it explicitly. //│ = "matched" @@ -35,9 +36,9 @@ if Box(0) is @compile Steps(CaptureBit(ZeroToOne)) then "matched" else "no match pattern FastSteps = @compile ((Box(CaptureBit(ZeroToOne)) => 0) as FastSteps | _) //│ ╔══[COMPILATION ERROR] Pattern `CaptureBit` has zero pattern parameters. //│ ╟── But one pattern argument were provided. -//│ ║ l.35: pattern FastSteps = @compile ((Box(CaptureBit(ZeroToOne)) => 0) as FastSteps | _) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.36: pattern FastSteps = @compile ((Box(CaptureBit(ZeroToOne)) => 0) as FastSteps | _) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[WARNING] This extraction argument's transformation result is discarded by pattern `CaptureBit`. -//│ ║ l.35: pattern FastSteps = @compile ((Box(CaptureBit(ZeroToOne)) => 0) as FastSteps | _) +//│ ║ l.36: pattern FastSteps = @compile ((Box(CaptureBit(ZeroToOne)) => 0) as FastSteps | _) //│ ║ ^^^^^^^^^ //│ ╙── Write `... as _` to discard it explicitly. diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/FixedPointPatterns.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/FixedPointPatterns.mls index 162d72150a..23773c2e37 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/FixedPointPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/FixedPointPatterns.mls @@ -1,3 +1,4 @@ +:. :js // Fixed-point patterns are patterns that pipe their own output back into @@ -374,7 +375,7 @@ if term3 is EvalAnnotated(r) and r is Lit(n) then n // Only annotating the chained pattern doesn't work. pattern EvalAnnotatedStep = @compile (Ctx(Redex) as EvalAnnotatedStep) | _ //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.375: pattern EvalAnnotatedStep = @compile (Ctx(Redex) as EvalAnnotatedStep) | _ +//│ ║ l.376: pattern EvalAnnotatedStep = @compile (Ctx(Redex) as EvalAnnotatedStep) | _ //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── `EvalAnnotatedStep` is recursive and must be compiled as a whole (rather than just part of it). //│ ╙── Falling back to the naive translation. diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/IndirectRecursion.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/IndirectRecursion.mls index 6df9fa28a8..a854e68c44 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/IndirectRecursion.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/IndirectRecursion.mls @@ -1,3 +1,4 @@ +:. :js // Fixed-point patterns may recurse indirectly through other definitions: @@ -78,13 +79,13 @@ pattern PeelB = UnTag as PeelA | _ :w fun runPeelA(x) = if x is @compile PeelA(y) then y //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.79: fun runPeelA(x) = if x is @compile PeelA(y) then y -//│ ║ ^^^^^^^ +//│ ║ l.80: fun runPeelA(x) = if x is @compile PeelA(y) then y +//│ ║ ^^^^^^^^ //│ ╟── One of its recursive parts accepts any term -//│ ║ l.71: pattern PeelB = UnTag as PeelA | _ +//│ ║ l.72: pattern PeelB = UnTag as PeelA | _ //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── while another can fail. -//│ ║ l.70: pattern PeelA = UnBox as PeelB | 42 +//│ ║ l.71: pattern PeelA = UnBox as PeelB | 42 //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╙── Falling back to the naive translation. @@ -160,8 +161,8 @@ fun atLeastOneSlow(x) = if x is AtLeastOneA(r) then r else "" :w fun atLeastOneFast(x) = if x is @compile AtLeastOneA(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.161: fun atLeastOneFast(x) = if x is @compile AtLeastOneA(r) then r else "" -//│ ║ ^^^^^^^^^^^^^ +//│ ║ l.162: fun atLeastOneFast(x) = if x is @compile AtLeastOneA(r) then r else "" +//│ ║ ^^^^^^^^^^^^^^ //│ ╟── `AtLeastOneA` does not match unless its first pattern does, which is not supported for mutually recursive definitions. //│ ╙── Falling back to the naive translation. diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/MoreAlternatives.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/MoreAlternatives.mls index 8dcfd1418d..7c19007b01 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/MoreAlternatives.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/MoreAlternatives.mls @@ -1,3 +1,4 @@ +:. :js // Fixed-point patterns may list extra non-recursive alternatives between the diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/NonCatchAll.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/NonCatchAll.mls index 66594299cf..72c6dae94d 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/NonCatchAll.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/NonCatchAll.mls @@ -1,3 +1,4 @@ +:. :js // A fixed-point pattern need not end in a catch-all wildcard: @@ -138,13 +139,13 @@ if 42 is KeepOriginalInt(r) then r else "" :expect 42 if 42 is @compile KeepOriginalInt(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.139: if 42 is @compile KeepOriginalInt(r) then r else "" -//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ║ l.140: if 42 is @compile KeepOriginalInt(r) then r else "" +//│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── A term this pattern can still rewrite -//│ ║ l.131: pattern KeepOriginalInt = IntToStr as KeepOriginalInt | Num +//│ ║ l.132: pattern KeepOriginalInt = IntToStr as KeepOriginalInt | Num //│ ║ ^^^^^^^^ //│ ╟── can also be matched by a trailing alternative. -//│ ║ l.131: pattern KeepOriginalInt = IntToStr as KeepOriginalInt | Num +//│ ║ l.132: pattern KeepOriginalInt = IntToStr as KeepOriginalInt | Num //│ ║ ^^^ //│ ╙── Falling back to the naive translation. //│ = 42 @@ -247,14 +248,14 @@ pattern AddStep = :w pattern AddSteps = @compile ((AddStep as AddSteps) | Add(Lit, Lit)) //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.248: pattern AddSteps = @compile ((AddStep as AddSteps) | Add(Lit, Lit)) -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.249: pattern AddSteps = @compile ((AddStep as AddSteps) | Add(Lit, Lit)) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── A term this pattern can still rewrite -//│ ║ l.248: pattern AddSteps = @compile ((AddStep as AddSteps) | Add(Lit, Lit)) +//│ ║ l.249: pattern AddSteps = @compile ((AddStep as AddSteps) | Add(Lit, Lit)) //│ ║ ^^^^^^^ //│ ╟── can also be matched by a trailing alternative. -//│ ║ l.248: pattern AddSteps = @compile ((AddStep as AddSteps) | Add(Lit, Lit)) -//│ ║ ^^^^^^^^^^^^ +//│ ║ l.249: pattern AddSteps = @compile ((AddStep as AddSteps) | Add(Lit, Lit)) +//│ ║ ^^^^^^^^^^^^^ //│ ╙── Falling back to the naive translation. diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/RecursionAlternatives.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/RecursionAlternatives.mls index fa04257392..e9ad048763 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/RecursionAlternatives.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/RecursionAlternatives.mls @@ -1,3 +1,4 @@ +:. :js // A fixed-point pattern may have several recursive alternatives: @@ -176,13 +177,13 @@ fun peelOrRewrapSlow(x) = if x is PeelOrRewrap(r) then r else "" :w fun peelOrRewrapFast(x) = if x is @compile PeelOrRewrap(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.177: fun peelOrRewrapFast(x) = if x is @compile PeelOrRewrap(r) then r else "" -//│ ║ ^^^^^^^^^^^^^^ +//│ ║ l.178: fun peelOrRewrapFast(x) = if x is @compile PeelOrRewrap(r) then r else "" +//│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Some recursive alternatives can rewrite the same term in more than one way, including this one -//│ ║ l.170: | UnBox as PeelOrRewrap +//│ ║ l.171: | UnBox as PeelOrRewrap //│ ║ ^^^^^ //│ ╟── and this one. -//│ ║ l.171: | Rewrap as PeelOrRewrap +//│ ║ l.172: | Rewrap as PeelOrRewrap //│ ║ ^^^^^^ //│ ╙── Falling back to the naive translation. @@ -255,13 +256,13 @@ fun mixedSlow(x) = if x is MixedSteps(r) then r else "" :w fun mixedFast(x) = if x is @compile MixedSteps(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.256: fun mixedFast(x) = if x is @compile MixedSteps(r) then r else "" -//│ ║ ^^^^^^^^^^^^ +//│ ║ l.257: fun mixedFast(x) = if x is @compile MixedSteps(r) then r else "" +//│ ║ ^^^^^^^^^^^^^ //│ ╟── Some recursive alternatives can rewrite the same term in more than one way, including this one -//│ ║ l.249: | (UnTag | UnBox) as MixedSteps +//│ ║ l.250: | (UnTag | UnBox) as MixedSteps //│ ║ ^^^^^ //│ ╟── and this one. -//│ ║ l.250: | Rewrap as MixedSteps +//│ ║ l.251: | Rewrap as MixedSteps //│ ║ ^^^^^^ //│ ╙── Falling back to the naive translation. @@ -310,12 +311,12 @@ pattern WildStep = _ as WildStep | UnTag as WildStep | Tag(_) :w fun wildStep(x) = if x is @compile WildStep(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.311: fun wildStep(x) = if x is @compile WildStep(r) then r else "" -//│ ║ ^^^^^^^^^^ +//│ ║ l.312: fun wildStep(x) = if x is @compile WildStep(r) then r else "" +//│ ║ ^^^^^^^^^^^ //│ ╟── Some recursive alternatives can rewrite the same term in more than one way, including this one -//│ ║ l.308: pattern WildStep = _ as WildStep | UnTag as WildStep | Tag(_) +//│ ║ l.309: pattern WildStep = _ as WildStep | UnTag as WildStep | Tag(_) //│ ║ ^ //│ ╟── and this one. -//│ ║ l.308: pattern WildStep = _ as WildStep | UnTag as WildStep | Tag(_) +//│ ║ l.309: pattern WildStep = _ as WildStep | UnTag as WildStep | Tag(_) //│ ║ ^^^^^ //│ ╙── Falling back to the naive translation. diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/SimpleExample.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/SimpleExample.mls index b367d6e756..cf2f37fcaf 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/SimpleExample.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/SimpleExample.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls index ae1a3de6c1..cc4b68e464 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls @@ -1,3 +1,4 @@ +:. :js // Fixed-point-shaped patterns that the machine compilation does not support @@ -32,8 +33,8 @@ if Box(Lit(7)) is PeelOnce(r) then r else "" :expect Lit(7) if Box(Lit(7)) is @compile PeelOnce(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.33: if Box(Lit(7)) is @compile PeelOnce(r) then r else "" -//│ ║ ^^^^^^^^^^ +//│ ║ l.34: if Box(Lit(7)) is @compile PeelOnce(r) then r else "" +//│ ║ ^^^^^^^^^^^ //│ ╟── `Rest` does not recurse back, so this pattern applies once rather than repeatedly. //│ ╙── Falling back to the naive translation. //│ = Lit(7) @@ -57,9 +58,9 @@ if nested is SumLits(r) then r else "" :expect Lit(10) if nested is @compile SumLits(r) then r else "" //│ ╔══[WARNING] The recursive context must occur as exactly one direct constructor argument. -//│ ║ l.46: pattern TwoHoles(pattern H) = H | Pair(TwoHoles(H), TwoHoles(H)) +//│ ║ l.47: pattern TwoHoles(pattern H) = H | Pair(TwoHoles(H), TwoHoles(H)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.47: pattern SumLits = TwoHoles(Pair(Lit(a), Lit(b)) => Lit(a + b)) as SumLits | _ +//│ ║ l.48: pattern SumLits = TwoHoles(Pair(Lit(a), Lit(b)) => Lit(a + b)) as SumLits | _ //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = Lit(10) @@ -92,8 +93,8 @@ reduce(Pair(Lit(1), Pair(Lit(3), Lit(2)))) :expect Pair(Lit(1), Lit(2)) if Pair(Lit(1), Lit(2)) is @compile Reduce(r) then r else "" //│ ╔══[WARNING] The recursive context has no redex alternative, so no rewriting step can apply. -//│ ║ l.81: pattern Reduce = NoRedex(Sum) as Reduce | _ -//│ ╙── ^^^^^^^^^^^ +//│ ║ l.82: pattern Reduce = NoRedex(Sum) as Reduce | _ +//│ ╙── ^^^^^^^^^^^^ //│ = Pair(Lit(1), Lit(2)) @@ -116,8 +117,8 @@ if Box(Box(7)) is OnlyRecursive(r) then r else "" :expect "" if Box(Box(7)) is @compile OnlyRecursive(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.117: if Box(Box(7)) is @compile OnlyRecursive(r) then r else "" -//│ ║ ^^^^^^^^^^^^^^^ +//│ ║ l.118: if Box(Box(7)) is @compile OnlyRecursive(r) then r else "" +//│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── It has only recursive alternatives, so a match can never finish. //│ ╙── Falling back to the naive translation. //│ = "" @@ -130,8 +131,8 @@ pattern EarlyWildcard = UnBox as EarlyWildcard | _ | Lit(_) :expect 7 if Box(7) is @compile EarlyWildcard(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.131: if Box(7) is @compile EarlyWildcard(r) then r else "" -//│ ║ ^^^^^^^^^^^^^^^ +//│ ║ l.132: if Box(7) is @compile EarlyWildcard(r) then r else "" +//│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── A wildcard alternative makes the alternatives after it unreachable. //│ ╙── Falling back to the naive translation. //│ = 7 @@ -144,8 +145,8 @@ pattern LateRecursion = UnBox as LateRecursion | UnTag as Rest | _ :expect 7 if Box(7) is @compile LateRecursion(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.145: if Box(7) is @compile LateRecursion(r) then r else "" -//│ ║ ^^^^^^^^^^^^^^^ +//│ ║ l.146: if Box(7) is @compile LateRecursion(r) then r else "" +//│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Its recursive alternatives must come before its other alternatives. //│ ╙── Falling back to the naive translation. //│ = 7 @@ -159,8 +160,8 @@ pattern ToExtracting = UnBox as Extracting | _ :expect Box(7) if Box(7) is @compile ToExtracting(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.160: if Box(7) is @compile ToExtracting(r) then r else "" -//│ ║ ^^^^^^^^^^^^^^ +//│ ║ l.161: if Box(7) is @compile ToExtracting(r) then r else "" +//│ ║ ^^^^^^^^^^^^^^^ //│ ╟── The recursion goes through `Extracting`, which is not a parameterless pattern definition. //│ ╙── Falling back to the naive translation. //│ = Box(7) @@ -175,8 +176,8 @@ pattern WanderC = UnBox as WanderB | _ :expect 7 if Box(7) is @compile WanderA(r) then r else "" //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.176: if Box(7) is @compile WanderA(r) then r else "" -//│ ║ ^^^^^^^^^ +//│ ║ l.177: if Box(7) is @compile WanderA(r) then r else "" +//│ ║ ^^^^^^^^^^ //│ ╟── The recursion goes through `WanderC` but never comes back to `WanderA`. //│ ╙── Falling back to the naive translation. //│ = 7 @@ -188,7 +189,7 @@ if Box(7) is @compile WanderA(r) then r else "" pattern PartOfCycleA = @compile (UnBox as PartOfCycleB) | _ pattern PartOfCycleB = UnTag as PartOfCycleA | _ //│ ╔══[WARNING] This fixed-point pattern is not supported by pattern compilation. -//│ ║ l.188: pattern PartOfCycleA = @compile (UnBox as PartOfCycleB) | _ +//│ ║ l.189: pattern PartOfCycleA = @compile (UnBox as PartOfCycleB) | _ //│ ║ ^^^^^^^^^^^^^^^^^^^^^ //│ ╟── `@compile` has to be placed on the whole body of a definition taking part in the recursion through `PartOfCycleB`. //│ ╙── Falling back to the naive translation. @@ -215,12 +216,12 @@ pattern SideCtx(pattern H) = H | Pair(SideCtx(H), SideUnBox) pattern SideGo = SideCtx(SideSum) as SideGo | _ fun sideGo(x) = if x is @compile SideGo(r) then r else "" //│ ╔══[WARNING] Side patterns of a context alternative must be transform-free. -//│ ║ l.212: pattern SideUnBox = Box(x) => x +//│ ║ l.213: pattern SideUnBox = Box(x) => x //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.213: pattern SideSum = Pair(Lit(a), Lit(b)) => Lit(a + b) +//│ ║ l.214: pattern SideSum = Pair(Lit(a), Lit(b)) => Lit(a + b) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.214: pattern SideCtx(pattern H) = H | Pair(SideCtx(H), SideUnBox) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.215: pattern SideCtx(pattern H) = H | Pair(SideCtx(H), SideUnBox) +//│ ╙── ^^^^^^^^^^^^^^^ // An alternative that is not a class pattern, so there is no constructor to @@ -231,9 +232,9 @@ pattern TupleCtx(pattern H) = H | [TupleCtx(H), _] pattern TupleGo = TupleCtx(TupleSum) as TupleGo | _ fun tupleGo(x) = if x is @compile TupleGo(r) then r else "" //│ ╔══[WARNING] This alternative is not supported by fixed-point pattern compilation. -//│ ║ l.229: pattern TupleSum = Pair(Lit(a), Lit(b)) => Lit(a + b) +//│ ║ l.230: pattern TupleSum = Pair(Lit(a), Lit(b)) => Lit(a + b) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.230: pattern TupleCtx(pattern H) = H | [TupleCtx(H), _] +//│ ║ l.231: pattern TupleCtx(pattern H) = H | [TupleCtx(H), _] //│ ╙── ^^^^^^^^^^^^^^^^ @@ -245,20 +246,21 @@ pattern AritySum = Pair(Lit(a), Lit(b)) => Lit(a + b) pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) pattern ArityGo = ArityCtx(AritySum) as ArityGo | _ fun arityGo(x) = if x is @compile ArityGo(r) then r else "" +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.246: pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╙── Class definition 'Pair' expected 2 pattern arguments, but got 1 //│ ╔══[COMPILATION ERROR] Class `Pair` has 2 parameters. -//│ ║ l.13: data class Pair(val first, val second) +//│ ║ l.14: data class Pair(val first, val second) //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── But 1 arguments were provided. -//│ ║ l.245: pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) -//│ ╙── ^^^^^^^^^^ +//│ ║ l.246: pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) +//│ ╙── ^^^^^^^^^^^ //│ ╔══[WARNING] Cannot rebuild `Pair` because not all of its parameters are accessible. -//│ ║ l.244: pattern AritySum = Pair(Lit(a), Lit(b)) => Lit(a + b) +//│ ║ l.245: pattern AritySum = Pair(Lit(a), Lit(b)) => Lit(a + b) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.245: pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Expected two arguments, but found only one argument. -//│ ║ l.245: pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) -//│ ╙── ^^^^^^^^^^ +//│ ║ l.246: pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) +//│ ╙── ^^^^^^^^^^^^^^^^ // A rewriting alternative placed after one that descends. @@ -268,8 +270,8 @@ pattern OrderCtx(pattern H) = H | Pair(OrderCtx(H), _) | Lit(_) pattern OrderGo = OrderCtx(OrderSum) as OrderGo | _ fun orderGo(x) = if x is @compile OrderGo(r) then r else "" //│ ╔══[WARNING] Redex alternatives must precede all recursive context alternatives. -//│ ║ l.268: pattern OrderGo = OrderCtx(OrderSum) as OrderGo | _ -//│ ╙── ^^^^^^^^^^^^^^^^^ +//│ ║ l.270: pattern OrderGo = OrderCtx(OrderSum) as OrderGo | _ +//│ ╙── ^^^^^^^^^^^^^^^^^^ // Splitting those same definitions across blocks crashes while building the diff --git a/hkmc2/shared/src/test/mlscript/ups/nondeterminism/.mls b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ups/nondeterminism/BitArithmetic.mls b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/BitArithmetic.mls index 3b6f35ebab..c997309ae2 100644 --- a/hkmc2/shared/src/test/mlscript/ups/nondeterminism/BitArithmetic.mls +++ b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/BitArithmetic.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/nondeterminism/EvenOddTree.mls b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/EvenOddTree.mls index 83c1444e38..bc97cb7985 100644 --- a/hkmc2/shared/src/test/mlscript/ups/nondeterminism/EvenOddTree.mls +++ b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/EvenOddTree.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/nondeterminism/LaRbTree.mls b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/LaRbTree.mls index 1b1d07d16b..c2c5218c6b 100644 --- a/hkmc2/shared/src/test/mlscript/ups/nondeterminism/LaRbTree.mls +++ b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/LaRbTree.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls index 0044b48129..701dd7ccc0 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls @@ -1,3 +1,4 @@ +:. :js pattern Nullable(pattern T) = T | null @@ -23,8 +24,9 @@ import "../../../mlscript-compile/Char.mls" fun foo(x) = x is Nullable(Char.Letter) //│ Normalized: //│ > ‹if|while› -//│ > let tmp:patternArgument0$ = member:Char⁰.Letter‹member:Letter› -//│ > let tmp:unapplyResult = (member:Nullable⁰.)unapply(tmp:patternArgument0$¹, x⁰) +//│ > let tmp:scrut = x⁰ +//│ > let tmp:patternArgument0$ = member:Char⁰^term:foo⁰.Letter +//│ > let tmp:unapplyResult = (member:Nullable⁰^term:foo⁰.)unapply(tmp:patternArgument0$¹, tmp:scrut¹) //│ > tmp:unapplyResult is (tmp:runtime⁰.)MatchSuccess‹class:MatchSuccess›(output, bindings) then true //│ > else false diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls index 8078124cca..db10276797 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } @@ -9,21 +10,21 @@ object A :e null is @compile Nullable //│ ╔══[COMPILATION ERROR] Pattern `Nullable` has one pattern parameter. -//│ ║ l.5: pattern Nullable(pattern T) = null | T +//│ ║ l.6: pattern Nullable(pattern T) = null | T //│ ║ ^ //│ ╟── But zero pattern arguments were provided. -//│ ║ l.10: null is @compile Nullable +//│ ║ l.11: null is @compile Nullable //│ ╙── ^^^^^^^^ //│ = false :e null is @compile Nullable(A, A) //│ ╔══[COMPILATION ERROR] Pattern `Nullable` has one pattern parameter. -//│ ║ l.5: pattern Nullable(pattern T) = null | T +//│ ║ l.6: pattern Nullable(pattern T) = null | T //│ ║ ^ //│ ╟── But two pattern arguments were provided. -//│ ║ l.20: null is @compile Nullable(A, A) -//│ ╙── ^^^^^^^^^^^^^ +//│ ║ l.21: null is @compile Nullable(A, A) +//│ ╙── ^^^^^^^^^^^^^^ //│ = false pattern Nullable(pattern T) = null | T diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls index dfcf9b16f1..cebe68dc5b 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls @@ -1,3 +1,4 @@ +:. :js open annotations diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls index 0816291424..6c4992cef1 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -59,10 +60,10 @@ null is Optional(Int) as Some(42) // Test the error message. fun toIntOption(x) = if x is (@compile Optional) as o then o //│ ╔══[COMPILATION ERROR] Pattern `Optional` has one pattern parameter. -//│ ║ l.47: pattern Optional(pattern T) = (null => None) | ((T as x) => Some(x)) +//│ ║ l.48: pattern Optional(pattern T) = (null => None) | ((T as x) => Some(x)) //│ ║ ^ //│ ╟── But zero pattern arguments were provided. -//│ ║ l.60: fun toIntOption(x) = if x is (@compile Optional) as o then o +//│ ║ l.61: fun toIntOption(x) = if x is (@compile Optional) as o then o //│ ╙── ^^^^^^^^ fun toIntOption(x) = if x is (@compile Optional(Int)) as o then o diff --git a/hkmc2/shared/src/test/mlscript/ups/recursion/.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/recursion/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ups/recursion/BitSeq.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/BitSeq.mls index 13bc3a5c48..fa62ba3635 100644 --- a/hkmc2/shared/src/test/mlscript/ups/recursion/BitSeq.mls +++ b/hkmc2/shared/src/test/mlscript/ups/recursion/BitSeq.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/recursion/BitTree.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/BitTree.mls index 9fa478ee7d..63d7ebb51a 100644 --- a/hkmc2/shared/src/test/mlscript/ups/recursion/BitTree.mls +++ b/hkmc2/shared/src/test/mlscript/ups/recursion/BitTree.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/recursion/LeafEvenOddTree.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/LeafEvenOddTree.mls index 3ce40c8248..aee6194305 100644 --- a/hkmc2/shared/src/test/mlscript/ups/recursion/LeafEvenOddTree.mls +++ b/hkmc2/shared/src/test/mlscript/ups/recursion/LeafEvenOddTree.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/recursion/NatBox.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/NatBox.mls index 25a2c59916..cd07bb6c19 100644 --- a/hkmc2/shared/src/test/mlscript/ups/recursion/NatBox.mls +++ b/hkmc2/shared/src/test/mlscript/ups/recursion/NatBox.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/recursion/NullTree.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/NullTree.mls index 1915b61150..318e0eb5e5 100644 --- a/hkmc2/shared/src/test/mlscript/ups/recursion/NullTree.mls +++ b/hkmc2/shared/src/test/mlscript/ups/recursion/NullTree.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/recursion/SignBox.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/SignBox.mls index a040bbd9f2..d863a5578b 100644 --- a/hkmc2/shared/src/test/mlscript/ups/recursion/SignBox.mls +++ b/hkmc2/shared/src/test/mlscript/ups/recursion/SignBox.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } diff --git a/hkmc2/shared/src/test/mlscript/ups/specialization/.mls b/hkmc2/shared/src/test/mlscript/ups/specialization/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/specialization/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls index 5b1200824f..6143d1d1b9 100644 --- a/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls +++ b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } @@ -151,10 +152,10 @@ fun (::) cons(head, tail) = Pair(head, tail) :e pattern List(a) = null | Pair(a, List) //│ ╔══[COMPILATION ERROR] Found an inconsistent variable in disjunction patterns. -//│ ║ l.152: pattern List(a) = null | Pair(a, List) +//│ ║ l.153: pattern List(a) = null | Pair(a, List) //│ ║ ^ //│ ╟── The variable is missing from this sub-pattern. -//│ ║ l.152: pattern List(a) = null | Pair(a, List) +//│ ║ l.153: pattern List(a) = null | Pair(a, List) //│ ╙── ^^^^ null is @compile List diff --git a/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls index 02aebb83b8..9bd4cfe01a 100644 --- a/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls +++ b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } @@ -52,6 +53,6 @@ pattern ManyZero = ("0" ~ (ManyZero | "")) | "" :todo let res = "1" is @compile ManyZero //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.49: pattern ManyZero = ("0" ~ (ManyZero | "")) | "" +//│ ║ l.50: pattern ManyZero = ("0" ~ (ManyZero | "")) | "" //│ ╙── ^^^^^^^^^^^^^^^^^^^^ //│ res = false diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/MixedParameters.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/MixedParameters.mls index 5564b20025..3900ab9a3f 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/MixedParameters.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/MixedParameters.mls @@ -1,3 +1,4 @@ +:. :js // The semantics of mixed pattern parameters and extraction parameters. @@ -23,7 +24,7 @@ pattern P'(pattern Q) = [Q as a, Q as b] => [a, b] // is that we either provide all of them or none at all. if [0, 0] is P(Bit, a) then a //│ ╔══[COMPILATION ERROR] Expected two extraction arguments, but found only one argument. -//│ ║ l.24: if [0, 0] is P(Bit, a) then a +//│ ║ l.25: if [0, 0] is P(Bit, a) then a //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -33,7 +34,7 @@ if [0, 0] is P(Bit, a) then a // then none can be provided. if [0, 0] is P'(Bit, a, b) then "values: " + a + ", " + b //│ ╔══[COMPILATION ERROR] Expected zero extraction arguments, but found two arguments. -//│ ║ l.34: if [0, 0] is P'(Bit, a, b) then "values: " + a + ", " + b +//│ ║ l.35: if [0, 0] is P'(Bit, a, b) then "values: " + a + ", " + b //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -41,9 +42,7 @@ if [0, 0] is P'(Bit, a, b) then "values: " + a + ", " + b // Order actually matters. But it seems that I didn't implement the error // reporting correctly. if [0, 0] is P(a, Bit, b) then "values: " + a + ", " + b -//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'a' -//│ ║ l.43: if [0, 0] is P(a, Bit, b) then "values: " + a + ", " + b -//│ ╙── ^ +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'a' //│ ═══[RUNTIME ERROR] ReferenceError: a is not defined pattern S(a, pattern Q, b) = [Q as a, Q as b] @@ -56,9 +55,7 @@ f([0, 0]) :fixme // Order actually matters. fun f(x) = if x is S(Bit, a, b) then [a, b] -//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'a' -//│ ║ l.58: fun f(x) = if x is S(Bit, a, b) then [a, b] -//│ ╙── ^ +//│ ═══[COMPILATION ERROR] No definition found in scope for member 'a' :re f([0, 0]) @@ -85,7 +82,7 @@ if 0 is Q'(Bit) as y then y // Similarly, it cannot be written like this: if 0 is Q'(Bit, y) then y //│ ╔══[COMPILATION ERROR] Expected zero extraction arguments, but found one argument. -//│ ║ l.86: if 0 is Q'(Bit, y) then y +//│ ║ l.83: if 0 is Q'(Bit, y) then y //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -98,7 +95,7 @@ pattern CaptureBit(value) = Bit as value // discarded unless the user makes the discard explicit with `as _`. if 0 is CaptureBit(ZeroToOne) then "discarded implicitly" //│ ╔══[WARNING] This extraction argument's transformation result is discarded by pattern `CaptureBit`. -//│ ║ l.99: if 0 is CaptureBit(ZeroToOne) then "discarded implicitly" +//│ ║ l.96: if 0 is CaptureBit(ZeroToOne) then "discarded implicitly" //│ ║ ^^^^^^^^^ //│ ╙── Write `... as _` to discard it explicitly. //│ = "discarded implicitly" diff --git a/hkmc2/shared/src/test/mlscript/ups/transformation/BindingLess.mls b/hkmc2/shared/src/test/mlscript/ups/transformation/BindingLess.mls index 704f8fa2af..5d84dfd77d 100644 --- a/hkmc2/shared/src/test/mlscript/ups/transformation/BindingLess.mls +++ b/hkmc2/shared/src/test/mlscript/ups/transformation/BindingLess.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Char.mls" diff --git a/hkmc2/shared/src/test/mlscript/wasm/.mls b/hkmc2/shared/src/test/mlscript/wasm/.mls index 887fd5a946..6168956255 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/.mls @@ -1,5 +1,2 @@ :.. :wasm -#lang(strictResolution: false) -//│ Wasm result: -//│ = {} From f3deeaa7179265cd3051844b53fbe27c4a4d5219 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 10:30:28 +0800 Subject: [PATCH 055/228] Normalize constructor contexts across lexical instance boundaries --- doc/new-resolution-suite-migration.md | 85 ++++-------- .../main/scala/hkmc2/codegen/Lowering.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 24 +++- .../scala/hkmc2/semantics/NewResolver.scala | 88 ++++++------ .../main/scala/hkmc2/semantics/Shape.scala | 127 +++++++++--------- .../test/mlscript/basics/ObjectExtensions.mls | 8 +- .../src/test/mlscript/codegen/BadNew.mls | 6 - .../src/test/mlscript/codegen/Spreads.mls | 10 +- .../src/test/mlscript/newres/Basics.mls | 19 ++- .../src/test/mlscript/newres/Captures.mls | 2 +- .../mlscript/newres/ClonedConstructors.mls | 18 +-- .../newres/ConstructorFieldRecovery.mls | 36 ++--- .../src/test/mlscript/newres/CtxSens.mls | 3 +- .../src/test/mlscript/newres/Functions.mls | 5 - .../src/test/mlscript/newres/Inheritance.mls | 2 +- .../shared/src/test/mlscript/newres/Opens.mls | 4 +- .../src/test/mlscript/newres/ParamClasses.mls | 16 ++- .../mlscript/newres/RecursiveEnvironment.mls | 75 ++++++++++- .../src/test/mlscript/newres/ReslScratch.mls | 10 +- .../src/test/mlscript/newres/WIPTest.mls | 3 - .../src/test/mlscript/wasm/ClassMethods.mls | 13 +- 21 files changed, 295 insertions(+), 263 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 916258c648..14168257fa 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -161,47 +161,9 @@ before trying to fix recursive cases by adding recursion limits. Acceptance cases: bound members after tuple/record extraction, nested constructors, aliases, guards, transformed results, repeated references, qualified imported constructors, and recursive UPS matchers. Compare results and generated matcher -structure with the existing tests. `ups/examples/HindleyMilner.mls` still exceeds -the 25-second limit; the recursive environment reproducer below isolates its -unbounded constructor-context marks. - -#### Blocker: constructor-pattern bindings mix unrelated instance contexts - -The final block of -[`newres/ConstructorFieldRecovery.mls`](../hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls) -reproduces this with ordinary, valid constructor patterns: - -```mlscript -class SharedBase(val field) -class LeftChild extends SharedBase({left: 1}) -class RightChild extends SharedBase({right: 2}) -if (new LeftChild) is SharedBase(x) then x.left else 0 -if (new RightChild) is SharedBase(x) then x.right else 0 -``` - -These expressions should resolve independently and return `1` and `2`. -Instead, each pattern binding receives field shapes from both subclasses. -Strict resolution rejects `x.left` because the right-hand record lacks `left`, -and rejects `x.right` because the left-hand record lacks `right`. Thus another -subclass's constructor arguments can make an otherwise valid selection fail. -This affects the precision of nominal constructor-field extraction even when -the scrutinee's concrete subclass is known; it is not confined to unsupported -pattern forms or malformed-class recovery. - -The regression remains under `:fixme` to keep this failure visible. It is a -serious migration blocker, not an accepted loss of resolution precision. The -observed failure is rejection of valid programs; silent miscompilation has not -been established by this reproducer. The exact point where receiver/constructor -context is lost still needs tracing through inherited member lookup and pattern -binding publication. - -The fix must preserve the matched receiver's constructor provenance through field -extraction. Do not suppress missing-member diagnostics, drop capture marks, or -pick one of the merged candidates to make this test pass. Completion requires -removing this `:fixme`, verifying both independent results, and covering multiple -instances of one class, sibling subclasses, objects extending a shared base, -and nested constructor patterns. Genuinely ambiguous receiver alternatives must -retain their diagnostics. +structure with the existing tests. `ups/examples/HindleyMilner.mls` now finishes +its new-resolution trial, but retains unsupported member and pattern cases +(see the recheck below). ### 3. Declared interfaces versus inferred values @@ -262,23 +224,25 @@ shows this without `yield`: a generator returning `1` makes resolution reject `.next` as a member of an integer literal. Generator calls need their own result shape and iterator-member behavior; annotation recognition already works. -`ups/examples/HindleyMilner.mls` still exceeds 25 seconds with JavaScript disabled. -The slowdown occurs while compiling `infer`. A live JVM stack shows deeply nested -`ExitMark.hashCode` calls; temporary instrumentation identifies repetitions of the -same `Env` constructor exit mark, accumulated through recursive environment -passing and method selection. The reduced +`ups/examples/HindleyMilner.mls` now finishes a new-resolution trial with JavaScript +disabled in about 1.4 seconds. It still reports unsupported member-variable shapes +and pattern/member errors, so the original worksheet retains its existing mode. +The recursive environment regression [`newres/RecursiveEnvironment.mls`](../hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls) -reproduces this independently of imports, implicit arguments, patterns, and the -earlier worksheet errors. With a 1 MiB JVM stack it reports a stack overflow in -`TermShape.exit` within a few seconds. The runtime call would immediately return -`0`; compilation must consider the recursive branch too. - -Unlike the full worksheet, the reduced case finishes with a stack overflow in -about 1.3 seconds under the normal suite configuration, so it remains an active -`:fixme` regression. The required fix is a convergent approximation of recursive -contexts that preserves meaningful member-access filtering. Dropping candidates or imposing an arbitrary -mark-depth limit would not supply those semantics. No such limit or diagnostic -instrumentation is retained. Both original worksheets keep their existing mode. +now resolves and executes, including actual recursive calls, explicit `new`, +nested class captures, wildcard opens, and partial construction. + +The unbounded marks came from a missing lexical boundary: a method's reference to +an outer constructor captured the method scope but skipped its enclosing instance. +Class bodies now introduce captures, and a class and its constructor share one +resolution boundary. Consuming a reconstructed instance cancels the old instance +exit against its capture, retaining the fresh constructor exit. Explicit `new` +uses the same constructor context, including for arguments and later parameter +lists. Context fragments compose from the definition to its consumer; argument +flow traverses that composition in reverse. Repeated boundaries in either +direction assert an invariant violation; there is no truncation or depth limit. +These changes also fix the sibling-subclass field-extraction regression in +`newres/ConstructorFieldRecovery.mls`, whose independent results are now checked. ### Other implementation work @@ -304,9 +268,8 @@ instrumentation is retained. Both original worksheets keep their existing mode. application difftests, and the aggregate suite. Review output changes; keep nonmigrated files intact. WASM `Basics` retains its previous configuration pending the capture fix, keeping this partial migration green. -2. **Bound recursive context inference and model generator results.** Use the - isolated recursive environment case to establish convergence without dropping - possible shapes. Use `GeneratorResults` to specify iterator results rather +2. **Model generator results.** Recursive constructor contexts now normalize + through lexical captures. Use `GeneratorResults` to specify iterator results rather than propagating the generator body's return shape. The earlier generator timeout was not reproduced. 3. **Complete structural targets and external signatures.** Dynamic JS values and @@ -468,7 +431,7 @@ The files themselves retain the pre-trial configuration and output. - `ups/examples/DoubleTripleList.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'head' has no resolved target - `ups/examples/EvaluationContext.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) - `ups/examples/EvaluationContext2.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `ups/examples/HindleyMilner.mls`: Timeout during resolution even with JS disabled; recursive environment passing accumulates unbounded constructor exit marks (see the isolated reproducer above). +- `ups/examples/HindleyMilner.mls`: The constructor-context timeout is fixed; the new-resolution trial finishes with unsupported member-variable shapes and pattern/member errors (see the recheck above). - `ups/examples/ListPredicates.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'length' has no resolved target - `ups/examples/Negation.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function 'flatten' expected 1 argument, but got 3 - `ups/examples/PrecedenceClimbStackParse.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'ParseStep' diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 4103b160b3..cde8dfaabe 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -714,9 +714,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case S(cls) => if sel.isErroneous then false else if sel.hasAmbiguousClass then + // Different instance contexts can lead to the same class declaration. + // Preserve the ambiguity, but report each declaration's location once. raise: ErrorReport(msg"The projection class is ambiguous" -> cls.toLoc :: - sel.resolvedClasses.map((sym, _) => msg"class: '${sym.nme}'" -> sym.toLoc), + sel.resolvedClasses.map(_._1).distinct.map(sym => msg"class: '${sym.nme}'" -> sym.toLoc), source = Diagnostic.Source.Compilation) false else if sel.resolvedClasses.isEmpty then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 83f91dda0a..e6dff78185 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -60,6 +60,18 @@ object Elaborator: case LambdaOrHandlerBlock => "LambdaOrHandlerBlock" case NonReturnContext(sym) => s"NonReturnContext(${sym})" + /** Functions and instantiated class bodies cross lexical resolution boundaries. + * Function's NonReturnContext already supplies its boundary, so Function must + * not introduce it again. Modules/objects have no per-instance boundary. + * In C's method, referring to the outer C constructor crosses the old instance + * boundary too. That capture cancels the old instance exit when the method's + * result is consumed; the fresh constructor exit belongs to the new instance. + */ + def resolutionBoundary: Opt[AnyDefinitionSymbol] = this match + case NonReturnContext(sym) => sym + case InnerScope(sym: ClassSymbol) => S(sym) + case _ => N + def inner: Opt[InnerSymbol] = this match case InnerScope(inner) => S(inner) case _ => N @@ -153,17 +165,15 @@ object Elaborator: private def getExplicit(name: Str)(using config: Config): Opt[Ctx.Elem] = env.get(name).orElse: val inherited = parent.flatMap(_.getExplicit(name)) - if config.language.useNewResolution then outer match - case OuterCtx.NonReturnContext(S(sym)) => inherited.map(Ctx.CaptElem(_, sym)) - case _ => inherited + if config.language.useNewResolution then inherited.map(capture) else inherited + private def capture(elem: Ctx.Elem): Ctx.Elem = + outer.resolutionBoundary.fold(elem)(Ctx.CaptElem(elem, _)) + private lazy val visibleWildcardOpens: Ls[Ctx.OpenSource] = val inherited = parent.toList.flatMap(_.visibleWildcardOpens) - wildcardOpens ::: (outer match - case OuterCtx.NonReturnContext(S(sym)) => inherited.map(source => - Ctx.OpenSource(Ctx.CaptElem(source.elem, sym))(source.id)) - case _ => inherited) + wildcardOpens ::: inherited.map(source => Ctx.OpenSource(capture(source.elem))(source.id)) def lookupLabel(name: Str): LabelLookup = @tailrec diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 98bfc43580..6cb84d380a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -478,10 +478,11 @@ class NewResolver: trm match case TyApp(base, _) => listenClass(base)(select, reject) case Capture(base, thru) => - listenClass(base)((cls, marks) => select(cls, marks ::: EntryMark(thru, N, NoMarks) :: Nil), reject) + listenClass(base)((cls, marks) => select(cls, marks ::: EntryMark(ResolutionBoundary(thru), N, NoMarks) :: Nil), reject) case _ => listen(trm): case sh: SymShape => - completedClass(sh)(cls => select(cls, sh.markss), + completedClass(sh)(cls => select(cls, + ExitMark(ResolutionBoundary(cls.sym), S(sh.resSym), NoMarks) :: sh.markss), () => fromBMS(sh.sym, sh.resSym, sh.markss, value, trm, _ => ())) case sh: TermShape => value(sh) @@ -515,31 +516,51 @@ class NewResolver: resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc :: Nil) ) - def resolveNew(nw: Term.New): Unit = - log(s"resolveNew? res = ${nw.showDbg}") - nw.cls.classHead match - case trm: NewResolvable => listen(trm): shape => - def reject(): Unit = - nw.isErroneous = true - resolError(nw, msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.toLoc :: Nil) - shape match - case ss: SymShape => completedClass(ss)(cd => - if !trm.resolvedTargets.contains(cd.sym) then trm.resolvedTargets ::= cd.sym - listenExt(cd.ext, extsh => - val dsh = DefnShape(cd, extsh) - val sh = newShapes.getOrElseUpdate((cd.sym, ss.markss, nw.resSym), { - dsh.unappliedParams.lazyZip(nw.args).foreach: - case ((ps, mss), args) => - zipArgs(mss, ps.params, ps.restParam, args, nw, dsh) - NewShape(dsh, cd.sym, ss.markss, nw.args, nw) - }) - if nw.shapes.add(sh) then nw.shapeListeners.foreach(_(sh)) - ) - , reject) - case _ => reject() - case _ => + /** Both constructor references and explicit `new` use the same definition and + * parameter lists, including auxiliary constructor(...) lists. A partial `new` + * therefore retains the same callable head and contexts as a partial C(...). + */ + private def constructorShape(cls: ClassDef, ext: Opt[TermShape]): DefnShape = + val (symbol, definition, base) = cls.ctorSym match + case S(ctor) => (ctor, ctor.defn.get, S(BaseShape(cls, ext))) + case N => (cls.sym, cls, ext) + val shape = defnShapes.getOrElseUpdate(symbol, DefnShape(definition, base)) + softAssert(shape.defn is definition) + (shape.ext, base) match + case (S(left: BaseShape), S(right: BaseShape)) => + softAssert((left.defn is right.defn) && left.ext == right.ext) + case (left, right) => softAssert(left == right) + shape + + def resolveNew(nw: Term.New): Unit = nw.cls.classHead match + case Term.Error() => nw.isErroneous = true + // Static construction records a resolved class on its reference for lowering. + // Other expression forms require dynamic construction, even if they publish + // no shapes (for example an unsupported reference in an extends clause). + case _: NewResolvable => resolveNewClass(nw) + case _ => + nw.isErroneous = true + resolError(nw, msg"Invalid class expression: ${nw.cls.describe}" -> nw.cls.toLoc :: Nil) + + private def resolveNewClass(nw: Term.New): Unit = + // listenClass preserves captures and supplies the same class-body exit as a + // constructor value. In particular, a captured constructor already carries + // that exit; adding a second one here would duplicate its instance boundary. + listenClass(nw.cls)((cd, marks) => + listenExt(cd.ext, extsh => + val dsh = constructorShape(cd, extsh) + val sh = newShapes.getOrElseUpdate((cd.sym, marks, nw.resSym), { + dsh.unappliedParams.lazyZip(nw.args).foreach: + case ((ps, _), args) => zipArgs(marks, ps.params, ps.restParam, args, nw, dsh) + NewShape(dsh, cd.sym, marks, nw.args, nw) + }) + if nw.shapes.add(sh) then nw.shapeListeners.foreach(_(sh)) + ) + , shape => + if !nw.isErroneous then nw.isErroneous = true - resolError(nw, msg"Invalid class expression: ${nw.cls.describe}" -> nw.cls.toLoc :: Nil) + resolError(nw, msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.toLoc :: Nil) + ) def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term): DefineVar = if newResolution then sym match @@ -600,7 +621,7 @@ class NewResolver: // Adding an exit here would create a mismatch because we do not track module captures explicitly. val exited = sym match case _: ModuleOrObjectSymbol => sh - case _ => MarkedShape.exit(sh, sym, S(resSym)) + case _ => MarkedShape.exit(sh, ResolutionBoundary(sym), S(resSym)) exited.exit(markss) match case NoShape => log(s"FILTER OUT ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") @@ -624,16 +645,7 @@ class NewResolver: d.tsym match case ccs: ClassCtorSymbol => val cls = ccs.associatedCls.defn.get - listenExt(cls.ext, extsh => - // Several uses (including deferred opens) can request the same - // constructor shape. Reuse it while checking the cache invariant. - val shape = defnShapes.getOrElseUpdate(sym, DefnShape(d, S(BaseShape(cls, extsh)))) - softAssert(shape.defn is d) - shape.ext match - case S(base: BaseShape) => softAssert((base.defn is cls) && base.ext == extsh) - case _ => softAssert(false, "Constructor shape is missing its class base") - wrappedListener(shape) - ) + listenExt(cls.ext, extsh => wrappedListener(constructorShape(cls, extsh))) case _ => wrappedListener(defnShapes.getOrElseUpdate(sym, DefnShape(d, N))) case S(d: ClassLikeDef) => @@ -841,7 +853,7 @@ class NewResolver: if discardMarks then listen(base)(listener) else listenTerm(base): sh => - listener(MarkedShape.enter(sh, thru, N)) + listener(MarkedShape.enter(sh, ResolutionBoundary(thru), N)) case ref @ Ref(sym: InnerSymbol) => // TODO: remove remaining occurrences of such refs sym.shapeListeners += listener case ref @ Ref(bsym: BlockMemberSymbol) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index e23dddd174..e11e2264ca 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -30,20 +30,49 @@ sealed trait Shape extends ShapeLike: sealed trait NonMarkedShape extends TermShape sealed trait NonAppTermShape extends NonMarkedShape -// type Mark = Opt[AnyDefinitionSymbol] -> Bool -// case class Mark(sym: Opt[AnyDefinitionSymbol], entry: Bool) -// sealed abstract class Marks -// case class MoreMarks(sym: Opt[AnyDefinitionSymbol], entry: Bool, rest: Marks) extends Marks -// case object NoMark extends Marks +/** A lexical resolution boundary, independent of a definition's term/type interpretation. + * A class and its companion constructor enter the same instance scope. Keeping their + * symbol identities distinct is still necessary for overload selection and lowering. + */ +final case class ResolutionBoundary private (symbol: AnyDefinitionSymbol): + def showDbg(using DebugPrinter): Str = symbol.showDbg + override def toString: Str = symbol.toString +object ResolutionBoundary: + def apply(symbol: AnyDefinitionSymbol): ResolutionBoundary = + new ResolutionBoundary(symbol match + case ctor: ClassCtorSymbol => ctor.associatedCls + case symbol => symbol) + +/** A reduced lexical path: entries followed by exits, stored outermost first. + * Exiting cancels the leading entry when their sites agree (an absent site is + * a capture, compatible with any activation). Entering never cancels an exit: + * that pair records an inner value's provenance until a consumer accesses it. + * Each direction traverses distinct lexical scopes, so recursive calls cannot + * lengthen a normalized path indefinitely. Violations are asserted, not widened. + */ sealed abstract class Marks: def showDbg(using DebugPrinter): Str = this match - case EntryMark(sym, id, rest) => s"↘⟨${sym.showDbg}⟩${id.fold("")("%⟨"+_.showDbg+"⟩")}${rest.showDbg}" - case ExitMark(sym, id, rest) => s"↗⟨${sym.showDbg}⟩${id.fold("")("%⟨"+_.showDbg+"⟩")}${rest.showDbg}" + case EntryMark(boundary, id, rest) => s"↘⟨${boundary.showDbg}⟩${id.fold("")("%⟨"+_.showDbg+"⟩")}${rest.showDbg}" + case ExitMark(boundary, id, rest) => s"↗⟨${boundary.showDbg}⟩${id.fold("")("%⟨"+_.showDbg+"⟩")}${rest.showDbg}" case NoMarks => "ϵ" + @scala.annotation.tailrec + final def hasEntry(boundary: ResolutionBoundary): Bool = this match + case EntryMark(b, _, rest) => b == boundary || rest.hasEntry(boundary) + case _ => false + type SomeMarks = EntryMark | ExitMark -case class EntryMark(sym: AnyDefinitionSymbol, id: Opt[FlowSymbol], rest: Marks) extends Marks -sealed abstract class ExitMarks extends Marks -case class ExitMark(sym: AnyDefinitionSymbol, id: Opt[FlowSymbol], rest: ExitMarks) extends ExitMarks +case class EntryMark(boundary: ResolutionBoundary, id: Opt[FlowSymbol], rest: Marks) extends Marks: + // Consecutive entries descend lexical scopes; consecutive exits ascend them. + // Repeating a scope in either direction indicates a missing capture/exit, not + // another recursive activation to retain or silently truncate. + assert(!rest.hasEntry(boundary), "Repeated entry into the same lexical resolution scope") +sealed abstract class ExitMarks extends Marks: + @scala.annotation.tailrec + final def hasExit(boundary: ResolutionBoundary): Bool = this match + case ExitMark(b, _, rest) => b == boundary || rest.hasExit(boundary) + case NoMarks => false +case class ExitMark(boundary: ResolutionBoundary, id: Opt[FlowSymbol], rest: ExitMarks) extends ExitMarks: + assert(!rest.hasExit(boundary), "Repeated exit from the same lexical resolution scope") case object NoMarks extends ExitMarks sealed trait ShapeLike: @@ -76,53 +105,19 @@ case class MarkedShape(sh: NonMarkedShape, mark: SomeMarks) extends TermShape: def describe: Str = sh.describe def toLoc: Opt[Loc] = sh.toLoc object MarkedShape: - def enter(sh: TermShape, sym: AnyDefinitionSymbol, id: Opt[FlowSymbol])(using TL): MarkedShape = + def enter(sh: TermShape, boundary: ResolutionBoundary, id: Opt[FlowSymbol])(using TL): MarkedShape = sh match - case sh: NonMarkedShape => MarkedShape(sh, EntryMark(sym, id, NoMarks)) - case MarkedShape(sh, marks) => - // marks match - // case marks: EntryMark => MarkedShape(sh, EntryMark(sym, id, marks)) - // case ExitMark(sym2, id2, rest) => - // require(sym2 is sym) - // id2 match - // case S(`id`) => MarkedShape(sh, marks) - // if id2 is id then - // rest match - // case NoMarks => sh - // case rest: ExitMark => MarkedShape(sh, rest) - // else - // MarkedShape(sh, EntryMark(sym, id, marks)) - // // MarkedShape(sh, EntryMark(sym, marks)) - // ??? - MarkedShape(sh, EntryMark(sym, id, marks)) - def exit(sh: TermShape, sym: AnyDefinitionSymbol, id: Opt[FlowSymbol])(using TL): TermShape | NoShape = - tl.trace[TermShape | NoShape](s".exit MarkedShape (${sh.shwDbg}, ${sym.showDbg}, ${id.fold("")(_.showDbg)})", res => s"= ${res.shwDbg}"): - tl.log(s"${sym.getClass} ${sym match - case sym: TermSymbol => sym.owner.map(_.showDbg) - case _ => "?" - }") + case sh: NonMarkedShape => MarkedShape(sh, EntryMark(boundary, id, NoMarks)) + case MarkedShape(sh, marks) => MarkedShape(sh, EntryMark(boundary, id, marks)) + def exit(sh: TermShape, boundary: ResolutionBoundary, id: Opt[FlowSymbol])(using TL): TermShape | NoShape = + tl.trace[TermShape | NoShape](s".exit MarkedShape (${sh.shwDbg}, ${boundary.showDbg}, ${id.fold("")(_.showDbg)})", res => s"= ${res.shwDbg}"): sh match - case sh: NonMarkedShape => MarkedShape(sh, ExitMark(sym, id, NoMarks)) + case sh: NonMarkedShape => MarkedShape(sh, ExitMark(boundary, id, NoMarks)) case MarkedShape(sh, marks) => marks match - case marks: ExitMark => MarkedShape(sh, ExitMark(sym, id, marks)) - case EntryMark(sym2, id2, rest) => - require(sym2 is sym, s"Expected symbol ${sym.showDbg} but got ${sym2.showDbg}") - // tl.log(s"!? ${id} vs ${id2}") - // // id2 match - // (id,id2) match - // // case S(`id`) | N => - // // case `id` | N => - // case (N, _) | (_, N) | (S(_), S(_)) if id === id2 => - // rest match - // case NoMarks => sh - // case rest: SomeMarks => MarkedShape(sh, rest) - // // case S(id2) => - // case _ => - // // MarkedShape(sh, ExitMark(sym, id, marks)) - // NoShape - // // MarkedShape(sh, EntryMark(sym, marks)) - // // ??? + case marks: ExitMark => MarkedShape(sh, ExitMark(boundary, id, marks)) + case EntryMark(entered, id2, rest) => + assert(entered == boundary, "Entry and exit cross different lexical resolution scopes") if id.forall(i1 => id2.forall(i2 => i1 is i2)) then rest match case NoMarks => sh @@ -141,13 +136,15 @@ sealed trait TermShape extends Shape: * value (e.g. a class without parameters) is still not an instance. */ def isInstanceOfClass(cls: ClassLikeDef): Bool = false + // Context fragments run from the callable definition to its consumer, just as + // MemberLookup.withMarks does. Wrapping an applied shape appends its context. lazy val applicationHead: (NonAppTermShape, Ls[Marks]) = this match // case ds: DefnShape => ds // case as: AppShape => as.receiver.applicationHead case as: AppShape => as.receiver.applicationHead - case ns: NewShape => ns.receiver.applicationHead + case ns: NewShape => (ns.receiver, ns.clsMarks) case na: NonAppTermShape => (na, Nil) - case MarkedShape(sh, mark) => sh.applicationHead.mapSecond(mark :: _) + case MarkedShape(sh, mark) => sh.applicationHead.mapSecond(_ ::: mark :: Nil) lazy val unappliedParams: Ls[(ParamList, Ls[Marks])] = this match case ds: DefnShape => ds.defn match case defn: TermDefinition => defn.params.map(_ -> Nil) @@ -159,8 +156,9 @@ sealed trait TermShape extends Shape: else Nil case _ => Nil case as: AppShape => as.receiver.unappliedParams.drop(1) - case ns: NewShape => ns.receiver.unappliedParams.drop(ns.argss.length) - case MarkedShape(sh, mark) => sh.unappliedParams.map(p => p._1 -> (mark :: p._2)) + case ns: NewShape => ns.receiver.unappliedParams.drop(ns.argss.length).map: + case (ps, marks) => ps -> (marks ::: ns.clsMarks) + case MarkedShape(sh, mark) => sh.unappliedParams.map(p => p._1 -> (p._2 ::: mark :: Nil)) case is: IntroShape => is.trm match case Term.Lam(params, body) => (params -> Nil) :: @@ -218,7 +216,9 @@ sealed trait TermShape extends Shape: def enter(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = revMarkss match case Nil => this case mark :: rest => - enter(mark).enter(rest) + // Fragments describe travel from the definition out to its consumer. + // Passing an argument back in must undo that composition in reverse order. + enter(rest).enter(mark) def isSaturated: Bool = unappliedParams.isEmpty @@ -283,15 +283,12 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using override def toString: String = s"AppShape($receiver, ${args.showDbg})" // def target: Opt[AppTarget] -class NewShape(val receiver: TermShape, val cls: ClassLikeSymbol, clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New)(using DebugPrinter) extends NonMarkedShape: +class NewShape(val receiver: DefnShape, val cls: ClassLikeSymbol, val clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New)(using DebugPrinter) extends NonMarkedShape: protected def getMemberImpl(name: Str): MemberLookup = - receiver match - case ds: DefnShape => ds.getInstanceMember(name).withMarks(clsMarks) - case _ => MemberLookup.Missing + if isSaturated then receiver.getInstanceMember(name).withMarks(clsMarks) + else MemberLookup.Missing override def isInstanceOfClass(cls: ClassLikeDef): Bool = - isSaturated && (receiver match - case ds: DefnShape => ds.classExtends(cls) - case _ => false) + isSaturated && receiver.classExtends(cls) def describe: Str = // s"instantiation of ${receiver.describe}" s"instance of ${cls.defn.get.describeRef}" diff --git a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls index 4a275c91c1..1bc7f592a8 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls @@ -9,9 +9,9 @@ new Foo //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.8: new Foo //│ ║ ^^^^^^^ -//│ ╟── Object symbol 'Foo' cannot be instantiated with keyword 'new'. +//│ ╟── Object 'Foo' cannot be instantiated with keyword 'new'. //│ ║ l.5: object Foo -//│ ╙── ^^^^^^^^^^ +//│ ╙── ^^^ //│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -37,9 +37,9 @@ object Bar extends Foo //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.36: object Bar extends Foo //│ ║ ^^^ -//│ ╟── Object symbol 'Foo' cannot be instantiated with keyword 'new'. +//│ ╟── Object 'Foo' cannot be instantiated with keyword 'new'. //│ ║ l.5: object Foo -//│ ╙── ^^^^^^^^^^ +//│ ╙── ^^^ //│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls b/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls index 0952c4cbf9..597395ac32 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls @@ -106,12 +106,6 @@ let xs = new Oopsie //│ ╔══[COMPILATION ERROR] Name not found: Oopsie //│ ║ l.105: let xs = new Oopsie //│ ╙── ^^^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.105: let xs = new Oopsie -//│ ║ ^^^^^^^^^^ -//│ ╟── Invalid class expression: ‹error› -//│ ║ l.105: let xs = new Oopsie -//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls index 5a4882a0c5..e68da28e4b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls @@ -72,9 +72,9 @@ x.r //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.67: let x = new A(1, 2, 3) //│ ║ ^^^^^^^^^^^^^^ -//│ ╟── Class value expected 0 arguments, but got 3 +//│ ╟── Class constructor 'A' expected 0 arguments, but got 3 //│ ║ l.66: data class A(...r) -//│ ╙── ^ +//│ ╙── ^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.68: x.r //│ ║ ^^^ @@ -96,9 +96,11 @@ new A(1, 2, 3).getR //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.92: new A(1, 2, 3).getR //│ ║ ^^^^^^^^^^^^^^ -//│ ╟── Class value expected 0 arguments, but got 3 +//│ ╟── Class constructor 'A' expected 0 arguments, but got 3 //│ ║ l.90: data class A(...r) with -//│ ╙── ^ +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ║ l.91: fun getR = r +//│ ╙── ^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'r' //│ ╟── which references the symbol introduced here //│ ║ l.90: data class A(...r) with diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls index cd41f6743e..5da0ad0398 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -51,7 +51,6 @@ Predef.id //│ | | | listenedBMS: bms = function //│ | | | fromBMS: bms = member:id¹, sh = member:id¹, flow = ⋅id⁰, markss = List() //│ | | | .exit MarkedShape (member:id¹, term:Predef¹/id⁰, ⋅id⁰) -//│ | | | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) //│ | | | = MarkedShape(member:id¹, ↗⟨term:Predef¹/id⁰⟩%⟨⋅id⁰⟩ϵ) //│ | | | PUSH MarkedShape(member:id¹, ↗⟨term:Predef¹/id⁰⟩%⟨⋅id⁰⟩ϵ) //│ | | ~> member:Predef⁰.id @@ -81,7 +80,7 @@ Predef.id(1) :e Predef.idd(1) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.82: Predef.idd(1) +//│ ║ l.81: Predef.idd(1) //│ ║ ^^^^^^^^^^ //│ ╟── Module 'Predef' does not contain member 'idd' //│ ║ in ../../mlscript-compile/Predef.mls @@ -134,30 +133,30 @@ p.id :e (1).id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.135: (1).id +//│ ║ l.134: (1).id //│ ║ ^^^^^^ //│ ╟── Integer literal does not contain member 'id' -//│ ║ l.135: (1).id +//│ ║ l.134: (1).id //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme // parse 1.id //│ ╔══[LEXICAL ERROR] Expected at least one digit after the decimal point -//│ ║ l.145: 1.id +//│ ║ l.144: 1.id //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). -//│ ║ l.145: 1.id +//│ ║ l.144: 1.id //│ ╙── ^^ //│ = 1 :fixme "ha ".repeat(2) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.155: "ha ".repeat(2) +//│ ║ l.154: "ha ".repeat(2) //│ ║ ^^^^^^^^^^^^ //│ ╟── String literal does not contain member 'repeat' -//│ ║ l.155: "ha ".repeat(2) +//│ ║ l.154: "ha ".repeat(2) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -170,9 +169,9 @@ print(1) :e ().id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.171: ().id +//│ ║ l.170: ().id //│ ║ ^^^^^ //│ ╟── Unit value does not contain member 'id' -//│ ║ l.171: ().id +//│ ║ l.170: ().id //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Captures.mls b/hkmc2/shared/src/test/mlscript/newres/Captures.mls index 60a11f2e70..852d23447a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Captures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Captures.mls @@ -86,7 +86,7 @@ class foo(x) { //│ fun foo⁶ //│ class foo⁷::foo⁸(x³) { //│ @private val x⁴::x⁵ ‹flow:x⁰› = x³^x⁵, -//│ Predef⁰.print⁰(foo⁸.this.x⁴)‹app¹⁰›, +//│ Predef⁰.print⁰^foo⁸(foo⁸.this.x⁴)‹app¹⁰›, //│ fun bar²::bar³(y¹) ‹flow:bar¹› = +(foo⁸.this.x⁴^bar³, y¹)‹app¹¹› //│ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls index 3957f95687..3697f44947 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls @@ -56,17 +56,7 @@ fun openedRecord(x) = constructors.make(35).value //│ = 35 -// Static `new` currently requires a class declaration. The `make` record field -// holds a constructor function, so it cannot be used with this syntax. -:e -new constructors.make(35) -//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.62: new constructors.make(35) -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── Record field symbol 'make' cannot be instantiated with keyword 'new'. -//│ ║ l.36: let constructors = {make: Direct} -//│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.62: new constructors.make(35) -//│ ╙── ^^^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +// Static `new` can also recover the known class through a constructor-valued field. +:expect 35 +(new constructors.make(35)).value +//│ = 35 diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls index 202df05ce2..f5f09fc694 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls @@ -72,24 +72,24 @@ if GoodObject is ObjectBase(x) then x else 0 //│ = 43 -// TODO: Constructor-pattern bindings currently merge arguments from different -// subclass instantiations. Retain this limitation separately from recovery tests. -:fixme +// Class exits keep the two inherited constructor-field contexts distinct. +:expect [1, 2] class SharedBase(val field) class LeftChild extends SharedBase({left: 1}) class RightChild extends SharedBase({right: 2}) -if (new LeftChild) is SharedBase(x) then x.left else 0 -if (new RightChild) is SharedBase(x) then x.right else 0 -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.81: if (new LeftChild) is SharedBase(x) then x.left else 0 -//│ ║ ^^^^^^ -//│ ╟── Record literal does not contain member 'left' -//│ ║ l.80: class RightChild extends SharedBase({right: 2}) -//│ ╙── ^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.82: if (new RightChild) is SharedBase(x) then x.right else 0 -//│ ║ ^^^^^^^ -//│ ╟── Record literal does not contain member 'right' -//│ ║ l.79: class LeftChild extends SharedBase({left: 1}) -//│ ╙── ^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +let left = if (new LeftChild) is SharedBase(x) then x.left else 0 +let right = if (new RightChild) is SharedBase(x) then x.right else 0 +[left, right] +//│ = [1, 2] +//│ left = 1 +//│ right = 2 + +// Separate sites of one constructor retain distinct fields, including when nested. +:expect [3, 4] +class NestedField(val field) +let first = if (new NestedField(new SharedBase({first: 3}))) is NestedField(SharedBase(x)) then x.first else 0 +let second = if (new NestedField(new SharedBase({second: 4}))) is NestedField(SharedBase(x)) then x.second else 0 +[first, second] +//│ = [3, 4] +//│ first = 3 +//│ second = 4 diff --git a/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls b/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls index 9ee64c9220..1de7a9e537 100644 --- a/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls @@ -76,7 +76,8 @@ class Foo(val a) { val b = A } //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun Foo⁰ class Foo¹::Foo²(val a⁰) { val a¹::a² ‹flow:a⁰› = a⁰^a², val b⁰::b¹ ‹flow:b⁰› = A⁰^b¹ } +//│ fun Foo⁰ +//│ class Foo¹::Foo²(val a⁰) { val a¹::a² ‹flow:a⁰› = a⁰^a², val b⁰::b¹ ‹flow:b⁰› = A⁰^Foo²^b¹ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— let x = Foo(A) diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index ff68748507..5ebbc01b14 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -94,13 +94,11 @@ callid1(Predef) //│ | listenedBMS: bms = function //│ | fromBMS: bms = member:callid1⁰, sh = member:callid1⁰, flow = callid1¹, markss = List() //│ | .exit MarkedShape (member:callid1⁰, term:callid1², callid1¹) -//│ | | class hkmc2.semantics.TermSymbol None //│ | = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | PUSH MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | listenedBMS: bms = function //│ | fromBMS: bms = member:callid1⁰, sh = member:callid1⁰, flow = callid1¹, markss = List() //│ | .exit MarkedShape (member:callid1⁰, term:callid1², callid1¹) -//│ | | class hkmc2.semantics.TermSymbol None //│ | = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | PUSH MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | appShape: lhs = MarkedShape(member:callid1⁰, ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ), args = [member:Predef⁰], res = member:callid1⁰(member:Predef⁰) @@ -124,7 +122,6 @@ callid1(Predef) //│ | listenedBMS: bms = function //│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) -//│ | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) //│ | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) @@ -135,7 +132,6 @@ callid1(Predef) //│ | listenedBMS: bms = function //│ | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List(↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) -//│ | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) //│ | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | | .exit MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) (ϵ) @@ -152,7 +148,6 @@ callid1(Predef) //│ | = IntroShape(1) //│ | .enter IntroShape(1) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | .exit MarkedShape (IntroShape(1), term:callid1², callid1¹) -//│ | | | class hkmc2.semantics.TermSymbol None //│ | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | | .enter MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | | .enter MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) diff --git a/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls b/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls index 678c2c05f2..701d696cd3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls @@ -16,7 +16,7 @@ class Bar(y) extends Foo(y + 1) { } //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ fun Bar⁰ -//│ class Bar¹::Bar²(y⁰) extends new Foo¹(+(y⁰, 1)‹app⁰›) { +//│ class Bar¹::Bar²(y⁰) extends new Foo¹^Bar²(+(y⁰, 1)‹app⁰›) { //│ @private val y¹::y² ‹flow:y⁰› = y⁰^y², //│ val z⁰::z¹ ‹flow:z⁰› = +(Bar².x¹, 1)‹app¹› //│ } diff --git a/hkmc2/shared/src/test/mlscript/newres/Opens.mls b/hkmc2/shared/src/test/mlscript/newres/Opens.mls index 932d19f185..17964ff260 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Opens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Opens.mls @@ -279,9 +279,9 @@ new Shadowed //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.278: new Shadowed //│ ║ ^^^^^^^^^^^^ -//│ ╟── Value symbol 'Shadowed' cannot be instantiated with keyword 'new'. +//│ ╟── Integer literal cannot be instantiated with keyword 'new'. //│ ║ l.276: val Shadowed = 20 -//│ ╙── ^^^^^^^^^^^^^^^^^ +//│ ╙── ^^ //│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ Shadowed = 20 diff --git a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls index 2ed9cc8c11..b620a4dc3b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls @@ -176,7 +176,7 @@ class Bar(b) with //│ fun Bar⁰ //│ class Bar¹::Bar²(b²) { //│ @private val b³::b⁴ ‹flow:b¹› = b²^b⁴, -//│ val foo²::foo³ ‹flow:foo¹› = Foo⁴^foo³(Bar².this.b³^foo³)‹app⁸› +//│ val foo²::foo³ ‹flow:foo¹› = Foo⁴^Bar²^foo³(Bar².this.b³^foo³)‹app⁸› //│ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -238,7 +238,7 @@ g(2) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.237: g(2) //│ ║ ^^^^ -//│ ╟── Instance of class value cannot receive more argument lists. +//│ ╟── Instance of class constructor 'Foo' cannot receive more argument lists. //│ ║ l.233: let g = f(1) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] TypeError: g is not a function @@ -248,19 +248,23 @@ let f = new Foo() //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.247: let f = new Foo() //│ ║ ^^^^^^^^^ -//│ ╟── Class value expected 1 argument, but got 0 +//│ ╟── Class constructor 'Foo' expected 1 argument, but got 0 //│ ║ l.84: class Foo(val a) { -//│ ╙── ^^^ +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ║ l.85: val b = a + 1 +//│ ║ ^^^^^^^^^^^^^^^ +//│ ║ l.86: } +//│ ╙── ^ //│ f = Foo(undefined) :e new 42 //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.258: new 42 +//│ ║ l.262: new 42 //│ ║ ^^^^^^ //│ ╟── Invalid class expression: integer literal -//│ ║ l.258: new 42 +//│ ║ l.262: new 42 //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls index b253053714..6e03199226 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls @@ -1,13 +1,78 @@ :. -:!js -// Compilation grows constructor exit marks through recursive method selection. -// JavaScript is disabled; with execution enabled this call would return 0. +// Capturing the outer constructor crosses both the method and instance scopes. +// The old instance exit cancels the class capture, leaving the fresh instance exit. data class Env(bindings) with fun lookup(name) = bindings fun extend(name) = Env(0) -:fixme +:expect 0 fun infer(env, x) = if x then env.lookup(x) else infer(env.extend(x), x) infer(Env(0), true) -//│ /!!!\ Uncaught error: java.lang.StackOverflowError +//│ = 0 + +// Exercise the recursive branch at runtime as well as during resolution. +:expect 0 +fun advance(env, n) = if n == 0 then env.lookup(n) else advance(env.extend(n), n - 1) +advance(Env(0), 3) +//│ = 0 + +// Explicit construction must carry the same instance boundary and captures. +data class NewEnv(bindings) with + fun lookup(name) = bindings + fun extend(name) = new NewEnv(0) + +:expect 0 +fun advanceNew(env, n) = if n == 0 then env.lookup(n) else advanceNew(env.extend(n), n - 1) +advanceNew(new NewEnv(0), 3) +//│ = 0 + +// The instance capture matters even without a recursive source-level call. +:expect 42 +class Box(val value) with + fun copy() = Box(value) +Box({answer: 42}).copy().value.answer +//│ = 42 + +// Rebuilding a nested class must also retain its enclosing function's capture. +:expect 43 +fun nested(enclosing) = + class Inner(val value) with + fun copy() = new Inner(value) + fun read() = enclosing.answer + (new Inner(0)).copy().read() +nested({answer: 43}) +//│ = 43 + +// Captures of inherited wildcard opens cross instance scopes too. +:expect 44 +class OpenBox(val value) +let makers = {create: OpenBox} +fun opened(value) = + open makers + class Wrapper with + fun make() = create(value) + (new Wrapper).make().value.answer +opened({answer: 44}) +//│ = 44 +//│ makers = {create: fun OpenBox { class: class OpenBox }} + +// Remaining argument lists retain the context of an explicitly constructed class. +:expect [45, 46] +fun partial(a) = + class Pair(val first)(val second) + new Pair(a) +let finish = partial({answer: 45}) +let pair = finish({answer: 46}) +[pair.first.answer, pair.second.answer] +//│ = [45, 46] +//│ finish = fun +//│ pair = Pair({answer: 45}) + +// Recursive data stays a finite graph of shapes even when it retains old instances. +:expect 1 +class Chain(val value, val previous) with + fun prepend(value) = Chain(value, this) +fun build(chain, n) = if n == 0 then chain else build(chain.prepend(n), n - 1) +build(Chain(0, ()), 3).value +//│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 4d3b269032..38f9ccbc95 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -42,7 +42,7 @@ class Bar(a) with //│ @private val a¹::a² ‹flow:a⁰› = a⁰^a², //│ let x⁰, //│ x⁰ = Bar².this.a¹, -//│ Predef⁰.print⁰(x⁰)‹app⁰› +//│ Predef⁰.print⁰^Bar²(x⁰)‹app⁰› //│ } //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: @@ -77,9 +77,11 @@ class Bar(a) with //│ prefix = SelfRef of class:Bar³ //│ id = Ident of "a" //│ res = App: -//│ lhs = NewSel: -//│ prefix = MemberRef of member:Predef⁰ -//│ id = Ident of "print" +//│ lhs = Capture: +//│ base = NewSel: +//│ prefix = MemberRef of member:Predef⁰ +//│ id = Ident of "print" +//│ thru = class:Bar³ //│ rhs = Tup of Ls of //│ Fld: //│ term = MemberRef of term:Bar³/x⁰ diff --git a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls index ce8d5145a3..d5f9f973b6 100644 --- a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls +++ b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls @@ -133,7 +133,6 @@ M.p.id //│ | | | | PUSH module:Predef¹ //│ | | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = ⋅p⁰, markss = List() //│ | | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, ⋅p⁰) -//│ | | | | | class hkmc2.semantics.TermSymbol Some(module:M⁰) //│ | | | | = module:Predef¹ //│ | | | | MATCH module:Predef¹ for term:M⁰/p⁰ % ⋅p⁰ //│ | | | ~> member:M¹.p @@ -153,7 +152,6 @@ M.p.id //│ | | | PUSH module:Predef¹ //│ | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = ⋅p⁰, markss = List() //│ | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, ⋅p⁰) -//│ | | | | class hkmc2.semantics.TermSymbol Some(module:M⁰) //│ | | | = module:Predef¹ //│ | | | MATCH module:Predef¹ for term:M⁰/p⁰ % ⋅p⁰ //│ | | | newSel: sel = member:M¹.p.id, shape = module:Predef¹ @@ -164,7 +162,6 @@ M.p.id //│ | | | listenedBMS: bms = function //│ | | | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List() //│ | | | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) -//│ | | | | class hkmc2.semantics.TermSymbol Some(module:Predef¹) //│ | | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | | PUSH MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | ~> member:M¹.p.id diff --git a/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls b/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls index 2964f1ebfe..35abe0f1f5 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls @@ -175,15 +175,14 @@ c.Counter#inc().Counter#double().Counter#get() :todo let d = Counter(c.Counter#get()) -//│ /!!!\ Uncaught error: java.lang.StackOverflowError +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Missing field `term:Counter/get` in struct `member:Counter` with type `(type $Counter (sub $Object (struct (field $$typeinfo (mut (ref $TypeInfoBase))) (field $x (mut (ref null any))))))` :todo d.Counter#inc().Counter#double().Counter#get() -//│ ╔══[COMPILATION ERROR] Name not found: d -//│ ║ l.181: d.Counter#inc().Counter#double().Counter#get() -//│ ╙── ^ -//│ Wasm result: -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ╔══[COMPILATION ERROR] Cannot find variable `d` (VarSymbol) in local or global scope. +//│ ║ l.177: let d = Counter(c.Counter#get()) +//│ ╙── ^ +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Missing type definition for symbol `member:Counter` :todo c.Counter#get() @@ -202,7 +201,7 @@ class A(val x) with fun get() = x A(1).get //│ ╔══[COMPILATION ERROR] `member:get` is neither a field access nor a callable method -//│ ║ l.203: A(1).get +//│ ║ l.202: A(1).get //│ ╙── ^^^^ From 92afcaf77fc481bc6453f54d534babaf55c1b666 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 24 Sep 2026 10:40:26 +0800 Subject: [PATCH 056/228] Minor --- .../src/test/mlscript/newres/ClonedConstructors.mls | 7 +++++++ .../test/mlscript/newres/ConstructorFieldRecovery.mls | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls index 3697f44947..352b425406 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls @@ -60,3 +60,10 @@ constructors.make(35).value :expect 35 (new constructors.make(35)).value //│ = 35 + +:expect 35 +// :sjs // * `Amazing` properly extends `constructors.make.class`, not the `constructors.make` function +object Amazing extends constructors.make(35) +Amazing.value +//│ = 35 + diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls index f5f09fc694..d8ee085a91 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls @@ -55,7 +55,7 @@ recover(new Child) :expect 42 class GoodBase(val field) class GoodChild extends GoodBase({answer: 42}) -if (new GoodChild) is GoodBase(x) then x.answer else 0 +if new GoodChild is GoodBase(x) then x.answer else 0 //│ = 42 @@ -77,8 +77,8 @@ if GoodObject is ObjectBase(x) then x else 0 class SharedBase(val field) class LeftChild extends SharedBase({left: 1}) class RightChild extends SharedBase({right: 2}) -let left = if (new LeftChild) is SharedBase(x) then x.left else 0 -let right = if (new RightChild) is SharedBase(x) then x.right else 0 +let left = if new LeftChild is SharedBase(x) then x.left else 0 +let right = if new RightChild is SharedBase(x) then x.right else 0 [left, right] //│ = [1, 2] //│ left = 1 @@ -87,8 +87,8 @@ let right = if (new RightChild) is SharedBase(x) then x.right else 0 // Separate sites of one constructor retain distinct fields, including when nested. :expect [3, 4] class NestedField(val field) -let first = if (new NestedField(new SharedBase({first: 3}))) is NestedField(SharedBase(x)) then x.first else 0 -let second = if (new NestedField(new SharedBase({second: 4}))) is NestedField(SharedBase(x)) then x.second else 0 +let first = if new NestedField(new SharedBase({first: 3})) is NestedField(SharedBase(x)) then x.first else 0 +let second = if new NestedField(new SharedBase({second: 4})) is NestedField(SharedBase(x)) then x.second else 0 [first, second] //│ = [3, 4] //│ first = 3 From f7e7023511d38618bee4ee4d551820b0f78aed0f Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 10:54:47 +0800 Subject: [PATCH 057/228] Avoid constructing cached constructor shape bases eagerly --- .../scala/hkmc2/semantics/NewResolver.scala | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 6cb84d380a..a35a152143 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -521,15 +521,19 @@ class NewResolver: * therefore retains the same callable head and contexts as a partial C(...). */ private def constructorShape(cls: ClassDef, ext: Opt[TermShape]): DefnShape = - val (symbol, definition, base) = cls.ctorSym match - case S(ctor) => (ctor, ctor.defn.get, S(BaseShape(cls, ext))) - case N => (cls.sym, cls, ext) - val shape = defnShapes.getOrElseUpdate(symbol, DefnShape(definition, base)) + // The shared cache distinguishes the constructor's TermDefinition from its + // ClassDef, even though both definitions cross the same resolution boundary. + val symbol: ClassCtorSymbol | ClassSymbol = cls.ctorSym.getOrElse(cls.sym) + val definition = symbol.defn.get + val shape = defnShapes.getOrElseUpdate(symbol, { + val base = if cls.ctorSym.isDefined then S(BaseShape(cls, ext)) else ext + DefnShape(definition, base) + }) softAssert(shape.defn is definition) - (shape.ext, base) match - case (S(left: BaseShape), S(right: BaseShape)) => - softAssert((left.defn is right.defn) && left.ext == right.ext) - case (left, right) => softAssert(left == right) + if cls.ctorSym.isDefined then shape.ext match + case S(base: BaseShape) => softAssert((base.defn is cls) && base.ext == ext) + case _ => softAssert(false, "Constructor shape is missing its class base") + else softAssert(shape.ext == ext) shape def resolveNew(nw: Term.New): Unit = nw.cls.classHead match From 185f15a9bc18176faaeeb85661a957ccc67762db Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 11:01:16 +0800 Subject: [PATCH 058/228] Guard mark chain scans with a disabled debug flag --- doc/new-resolution-suite-migration.md | 6 ++++-- .../src/main/scala/hkmc2/semantics/Shape.scala | 13 ++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 14168257fa..a5d2c7854a 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -239,8 +239,10 @@ resolution boundary. Consuming a reconstructed instance cancels the old instance exit against its capture, retaining the fresh constructor exit. Explicit `new` uses the same constructor context, including for arguments and later parameter lists. Context fragments compose from the definition to its consumer; argument -flow traverses that composition in reverse. Repeated boundaries in either -direction assert an invariant violation; there is no truncation or depth limit. +flow traverses that composition in reverse. Optional debugging assertions detect +repeated boundaries in either direction; the private `checkMarkPaths` flag disables +them by default because scanning each new mark's tail makes chain construction +quadratic in its depth. There is no truncation or depth limit. These changes also fix the sibling-subclass field-extraction regression in `newres/ConstructorFieldRecovery.mls`, whose independent results are now checked. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index e11e2264ca..54570668f5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -43,12 +43,17 @@ object ResolutionBoundary: case ctor: ClassCtorSymbol => ctor.associatedCls case symbol => symbol) +// Scanning every new mark's tail makes chain construction quadratic in its depth. +// Enable these invariant checks only when debugging mark propagation. +private val checkMarkPaths = false + /** A reduced lexical path: entries followed by exits, stored outermost first. * Exiting cancels the leading entry when their sites agree (an absent site is * a capture, compatible with any activation). Entering never cancels an exit: * that pair records an inner value's provenance until a consumer accesses it. * Each direction traverses distinct lexical scopes, so recursive calls cannot - * lengthen a normalized path indefinitely. Violations are asserted, not widened. + * lengthen a normalized path indefinitely. Debug assertions check this invariant + * without widening paths. */ sealed abstract class Marks: def showDbg(using DebugPrinter): Str = this match @@ -65,14 +70,16 @@ case class EntryMark(boundary: ResolutionBoundary, id: Opt[FlowSymbol], rest: Ma // Consecutive entries descend lexical scopes; consecutive exits ascend them. // Repeating a scope in either direction indicates a missing capture/exit, not // another recursive activation to retain or silently truncate. - assert(!rest.hasEntry(boundary), "Repeated entry into the same lexical resolution scope") + if checkMarkPaths then + assert(!rest.hasEntry(boundary), "Repeated entry into the same lexical resolution scope") sealed abstract class ExitMarks extends Marks: @scala.annotation.tailrec final def hasExit(boundary: ResolutionBoundary): Bool = this match case ExitMark(b, _, rest) => b == boundary || rest.hasExit(boundary) case NoMarks => false case class ExitMark(boundary: ResolutionBoundary, id: Opt[FlowSymbol], rest: ExitMarks) extends ExitMarks: - assert(!rest.hasExit(boundary), "Repeated exit from the same lexical resolution scope") + if checkMarkPaths then + assert(!rest.hasExit(boundary), "Repeated exit from the same lexical resolution scope") case object NoMarks extends ExitMarks sealed trait ShapeLike: From c0b1f66ff560ecb29044e5728d99b7cb5be65619 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 11:09:38 +0800 Subject: [PATCH 059/228] Build cached application contexts in reverse order --- .../main/scala/hkmc2/semantics/Shape.scala | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 54570668f5..211d1c9ab4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -143,36 +143,51 @@ sealed trait TermShape extends Shape: * value (e.g. a class without parameters) is still not an instance. */ def isInstanceOfClass(cls: ClassLikeDef): Bool = false - // Context fragments run from the callable definition to its consumer, just as - // MemberLookup.withMarks does. Wrapping an applied shape appends its context. + // Cache the head independently: following receivers does not need to construct + // or unpack a (head, marks) pair at every intermediate shape. + private lazy val applicationHeadShape: NonAppTermShape = this match + case as: AppShape => as.receiver.applicationHeadShape + case ns: NewShape => ns.receiver + case na: NonAppTermShape => na + case MarkedShape(sh, _) => (sh: TermShape).applicationHeadShape + // Public contexts run from the callable definition to its consumer, as in + // MemberLookup.withMarks. Build the reverse privately so each wrapper prepends + // in constant time; materialize the forward view only when a consumer needs it. + private lazy val reversedApplicationMarks: Ls[Marks] = this match + case as: AppShape => as.receiver.reversedApplicationMarks + case ns: NewShape => ns.clsMarks.reverse + case _: NonAppTermShape => Nil + case MarkedShape(sh, mark) => mark :: (sh: TermShape).reversedApplicationMarks + private lazy val applicationMarks: Ls[Marks] = this match + case as: AppShape => as.receiver.applicationMarks + case ns: NewShape => ns.clsMarks + case _ => reversedApplicationMarks.reverse lazy val applicationHead: (NonAppTermShape, Ls[Marks]) = this match - // case ds: DefnShape => ds - // case as: AppShape => as.receiver.applicationHead case as: AppShape => as.receiver.applicationHead - case ns: NewShape => (ns.receiver, ns.clsMarks) - case na: NonAppTermShape => (na, Nil) - case MarkedShape(sh, mark) => sh.applicationHead.mapSecond(_ ::: mark :: Nil) - lazy val unappliedParams: Ls[(ParamList, Ls[Marks])] = this match + case _ => (applicationHeadShape, applicationMarks) + // All remaining parameter lists belong to the same callable and carry its + // context. Keep the lists separate until the public view needs their pairs. + private lazy val unappliedParamLists: Ls[ParamList] = this match case ds: DefnShape => ds.defn match - case defn: TermDefinition => defn.params.map(_ -> Nil) + case defn: TermDefinition => defn.params case defn: ClassDef => - // println(defn.ctorSym) - // if defn.ctorSym.isDefined then if defn.paramsOpt.isDefined then // whether the class can receive direct applications - (defn.paramsOpt.toList ::: defn.auxParams).map(_ -> Nil) + defn.paramsOpt.toList ::: defn.auxParams else Nil case _ => Nil - case as: AppShape => as.receiver.unappliedParams.drop(1) - case ns: NewShape => ns.receiver.unappliedParams.drop(ns.argss.length).map: - case (ps, marks) => ps -> (marks ::: ns.clsMarks) - case MarkedShape(sh, mark) => sh.unappliedParams.map(p => p._1 -> (p._2 ::: mark :: Nil)) + case as: AppShape => as.receiver.unappliedParamLists.drop(1) + case ns: NewShape => (ns.receiver: TermShape).unappliedParamLists.drop(ns.argss.length) + case MarkedShape(sh, _) => (sh: TermShape).unappliedParamLists case is: IntroShape => is.trm match - case Term.Lam(params, body) => (params -> Nil) :: - // body.unappliedParams - Nil + case Term.Lam(params, _) => params :: Nil case _ => Nil case _ => Nil + lazy val unappliedParams: Ls[(ParamList, Ls[Marks])] = this match + // Applying arguments removes a list without changing the context; retain + // sharing of the already paired tail when this public view is requested. + case as: AppShape => as.receiver.unappliedParams.drop(1) + case _ => unappliedParamLists.map(_ -> applicationMarks) def exit(marks: Marks)(using TL): TermShape | NoShape = tl.trace[TermShape | NoShape](s".exit $shwDbg (${marks.showDbg})", res => s"= ${res.shwDbg}"): @@ -227,7 +242,7 @@ sealed trait TermShape extends Shape: // Passing an argument back in must undo that composition in reverse order. enter(rest).enter(mark) - def isSaturated: Bool = unappliedParams.isEmpty + def isSaturated: Bool = unappliedParamLists.isEmpty end TermShape From 7d5d5fa73aad64053a037caaf4649b408491c6f5 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 24 Sep 2026 11:14:39 +0800 Subject: [PATCH 060/228] Minor --- .../src/test/mlscript/newres/Dynamic.mls | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls index c9b66002fa..268a574c42 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls @@ -14,11 +14,16 @@ path.posix.join("/a", "b") // An ascription forgets the initializer's shape; successive selections and calls // remain dynamic rather than acquiring a fabricated member definition. :expect 3 -let d = ({nested: {get: () => 3}} as dyn) +let d = ({nested: {get(): 3}} as dyn) d.nested.get() //│ = 3 //│ d = {nested: {get: fun get}} +// Notice: no resolution error for dynamic `d` +:re +d.nasted.get() +//│ ═══[RUNTIME ERROR] Error: Access to required field 'nasted' yielded 'undefined' + :expect 4 fun readDynamic(x: dyn) = x.value readDynamic({value: 4}) @@ -73,27 +78,27 @@ explicit."nested".value :e {value: 1}.missing //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.74: {value: 1}.missing +//│ ║ l.79: {value: 1}.missing //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'missing' -//│ ║ l.74: {value: 1}.missing +//│ ║ l.79: {value: 1}.missing //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e 1() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.84: 1() +//│ ║ l.89: 1() //│ ║ ^^^ //│ ╟── Integer literal cannot be called like a function. -//│ ║ l.84: 1() +//│ ║ l.89: 1() //│ ╙── ^ //│ ═══[RUNTIME ERROR] TypeError: 1 is not a function :e dyn //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (dynamic type) -//│ ║ l.94: dyn +//│ ║ l.99: dyn //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -115,10 +120,10 @@ fun notCalled(x: dyn) = x.anything(1).result let mixed = if hide(true) then ({value: 1} as dyn) else {other: 2} mixed.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.116: mixed.value +//│ ║ l.121: mixed.value //│ ║ ^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'value' -//│ ║ l.115: let mixed = if hide(true) then ({value: 1} as dyn) else {other: 2} +//│ ║ l.120: let mixed = if hide(true) then ({value: 1} as dyn) else {other: 2} //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ mixed = {value: 1} From b9874a86c76df710dcbfd81508bc91001bcd275e Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 11:48:59 +0800 Subject: [PATCH 061/228] Move signature erasure into a dedicated compiler phase --- .../src/test/scala/hkmc2/ErasureTest.scala | 39 ++++ .../src/main/scala/hkmc2/CompilerCtx.scala | 8 +- .../src/main/scala/hkmc2/codegen/Block.scala | 2 +- .../main/scala/hkmc2/codegen/ErasedType.scala | 31 +-- .../main/scala/hkmc2/codegen/Erasure.scala | 178 ++++++++++++++++++ .../src/main/scala/hkmc2/codegen/Lifter.scala | 4 +- .../main/scala/hkmc2/codegen/Lowering.scala | 24 +-- .../main/scala/hkmc2/invalml/InvalML.scala | 2 +- .../scala/hkmc2/semantics/Elaborator.scala | 110 ++--------- .../scala/hkmc2/semantics/NewResolver.scala | 20 +- .../main/scala/hkmc2/semantics/Resolver.scala | 4 +- .../scala/hkmc2/semantics/SimpleSplit.scala | 6 +- .../main/scala/hkmc2/semantics/Split.scala | 6 +- .../main/scala/hkmc2/semantics/Symbol.scala | 17 +- .../src/main/scala/hkmc2/semantics/Term.scala | 28 +-- .../scala/hkmc2/semantics/TypeShape.scala | 27 +-- .../hkmc2/semantics/ucs/FlatPattern.scala | 2 +- .../hkmc2/semantics/ucs/Normalization.scala | 5 +- .../scala/hkmc2/semantics/ups/Compiler.scala | 4 +- .../semantics/ups/FixedPointCompiler.scala | 2 +- .../hkmc2/semantics/ups/Instantiator.scala | 2 +- .../hkmc2/semantics/ups/SplitCompiler.scala | 2 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 2 +- .../src/main/scala/hkmc2/utils/utils.scala | 3 +- .../mlscript/ctx/NestedUsingInstances.mls | 1 + .../src/test/mlscript/ctx/TypeResolution.mls | 1 + .../src/test/mlscript/newres/Erasure.mls | 54 ++++++ .../src/test/mlscript/newres/wasm/Erasure.mls | 26 +++ .../test/scala/hkmc2/InvalmlDiffMaker.scala | 3 +- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 1 + 30 files changed, 411 insertions(+), 203 deletions(-) create mode 100644 hkmc2/jvm/src/test/scala/hkmc2/ErasureTest.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala create mode 100644 hkmc2/shared/src/test/mlscript/newres/Erasure.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/wasm/Erasure.mls diff --git a/hkmc2/jvm/src/test/scala/hkmc2/ErasureTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/ErasureTest.scala new file mode 100644 index 0000000000..f07c48167a --- /dev/null +++ b/hkmc2/jvm/src/test/scala/hkmc2/ErasureTest.scala @@ -0,0 +1,39 @@ +package hkmc2 + +import org.scalatest.funsuite.AnyFunSuite + +import hkmc2.codegen.ErasedType +import hkmc2.semantics.{Elaborator, TermSymbol, VarSymbol} +import hkmc2.syntax.{Fun, Tree} +import hkmc2.utils.*, shorthands.* + + +class ErasureTest extends AnyFunSuite: + test("parameter type reads fail before erasure without caching an unknown representation"): + given Elaborator.State = new Elaborator.State + val parameter = VarSymbol(Tree.Ident("parameter")) + intercept[AssertionError](parameter.erasedType) + intercept[AssertionError](parameter.erasedValueType) + intercept[AssertionError](parameter.erasedValueType_!) + intercept[AssertionError](parameter.erasedType_!) + parameter.erasedType = S(ErasedType.Primitive(codegen.PrimitiveType.Int32)) + assert(parameter.erasedValueType == parameter.erasedType) + assert(parameter.erasedValueType_! == ErasedType.Primitive(codegen.PrimitiveType.Int32)) + assert(parameter.erasedType_! == parameter.erasedValueType_!) + intercept[AssertionError](parameter.erasedType = N) + + test("an unknown representation is initialized and cannot be initialized twice"): + given Elaborator.State = new Elaborator.State + val parameter = VarSymbol(Tree.Ident("unannotated")) + parameter.erasedType = N + assert(parameter.isErased) + assert(parameter.erasedValueType.isEmpty) + assert(parameter.erasedValueType_! == ErasedType.Unknown) + intercept[AssertionError](parameter.erasedType = S(ErasedType.Unknown)) + + test("definition result types cannot be read before erasure"): + given Elaborator.State = new Elaborator.State + val definition = TermSymbol(Fun, N, Tree.Ident("function")) + intercept[AssertionError](definition.declaredResultType) + definition.erasedType = S(ErasedType.FuncRef(S(false), Nil :: Nil, S(ErasedType.Unknown))) + assert(definition.declaredResultType.contains(ErasedType.Unknown)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala index 590eb26a68..8010f6e1d8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala @@ -164,6 +164,7 @@ class CompilerCtx( val ir = artifactConfig.givenIn: given Elaborator.State = state + given codegen.Erasure = codegen.Erasure(blk)(using artifactConfig, prelude, state) val low = backendTL.givenIn: new codegen.Lowering()(using artifactConfig, backendTL, summon[Raise], state, prelude, summon[SymbolPrinter]) optimize(low.program(blk, Set.empty)) @@ -221,11 +222,10 @@ class CompilerCtx( val elab = Elaborator(tl, file.up, Ctx.empty) val initCtx = State.init.nestLocal("prelude") val (blk, ctx) = elab.importFrom(parse.resultBlk)(using initCtx) - // Prelude declarations have no executable program, but their nominal hierarchy - // is needed by erased-type operations in every compilation unit. + // Prelude declarations have no executable program, but their signatures and nominal hierarchy + // must be erased before any compilation unit can use them. given Ctx = ctx - given SymbolPrinter = new SymbolPrinter(Scope.empty(Scope.Cfg.default)) - new codegen.Lowering().prepareTypes(blk) + codegen.Erasure(blk) PreludeArtifact(parse.resultBlk, blk, ctx, state, rootConfig, lastMod), ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 53e0282500..f946d734f7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -29,7 +29,7 @@ This means that if a program fragment containing definitions is rewritten at all then the rewritten version must be kept as the "currently valid" version, and the old one must no longer be reused, as a given symbol can never correspond to more than one IR definition. -Class-like symbols also retain a nominal `ClassHeader`: lowering publishes these before executable bodies, +Class-like symbols also retain a nominal `ClassHeader`: erasure publishes these before executable bodies, including for external declarations, and rewritten IR class definitions refresh them. This lets inheritance queries operate solely on IR even when a parent has no executable definition or has not been lowered yet. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala index e035910dcb..69e95d1332 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala @@ -55,15 +55,6 @@ object ErasedType: // Ensures `toString` returns a stable string override def toString: Str = "ValueLike(?)" - /** Erasure of a type whose interpretation is still being published during elaboration. - * The thunk reads an already-resolved type graph when the backend needs its representation. - */ - final class Deferred(compute: (Ctx, State) ?=> ErasedValueType) extends ErasedValueType: - override type Canonical = CanonicalErasedValueType - override def sym(using Ctx, State): TypeSymbol | NoSymbol = canonicalize.sym - override protected def computeCanonicalize(using Ctx, State): CanonicalErasedValueType = compute.canonicalize - override def toString: Str = "Deferred(?)" - /** A reference to a function of a possibly-known shape. * * - `rsc` is true if this reference is a resource function. @@ -246,10 +237,10 @@ object ErasedType: /** Erases a type-annotated term to an [[ErasedType]]. * - * Note that the resulting erased type is **not** canonicalized to avoid using `ctx.builtins` during elaboration - * of `Prelude`. + * Used by the legacy resolver after resolution. Canonicalization is separate and may inspect the + * nominal hierarchy published by Erasure. */ - def eraseSign(sign: Term): Opt[ErasedValueType] = sign match + def eraseSign(sign: Term)(using Erasure): Opt[ErasedValueType] = sign match case CompType(lhs, rhs, true) => // * A union is kept as a transient `Union` surface form; `canonicalize` collapses it to the members' LUB. for @@ -510,6 +501,22 @@ trait HasErasedType: /** Similar to `erasedValueType`, but coerces to the top type if the specific erased value type is not known. */ lazy val erasedValueType_! : ErasedValueType = erasedValueType.getOrElse(ErasedType.Unknown) +/** Erased types of source declarations are initialized by Erasure after resolution finishes. + * `null` means no representation has been assigned; `N` is an initialized, unknown representation. + * The checked getter also protects HasErasedType's lazy derived values from caching an early `N`. + * IR-generated symbols may initialize the same slot directly at construction. + */ +trait HasDelayedErasedType[T <: ErasedType] extends HasErasedType: + private var assignedErasedType: Opt[T] = null + final def isErased: Bool = assignedErasedType != null + final def erasedType: Opt[T] = + assert(isErased, "Erased type read before erasure") + assignedErasedType + final def erasedType_=(value: Opt[T]): Unit = + assert(!isErased, "Erased type initialized more than once") + assert(value != null, "An initialized erased type cannot be null") + assignedErasedType = value + /** A [[HasErasedType]] whose erased type can be populated exactly once post-construction. */ trait HasOnceMutableErasedType extends HasErasedType: // Implementation Note: Provided for overriding classes to implement `erasedType` directly as an `override var` diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala new file mode 100644 index 0000000000..0a73ffbd63 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala @@ -0,0 +1,178 @@ +package hkmc2 +package codegen + +import scala.collection.mutable + +import hkmc2.utils.*, shorthands.* +import semantics.* +import semantics.Elaborator.{Ctx, State} +import semantics.Term.* + +/** The capability to inspect completed resolution. Created only after elaboration/resolution; + * executable lowering additionally requires this instance's whole-program pass to have finished. + */ +final class Erasure private (using Config, Ctx, State): + private given Erasure = this + private var completed: Opt[Statement] = N + + def requireCompleted(statement: Statement): Unit = + assert(completed.exists(_ is statement), "Lowering requires completed erasure of this program") + + /** Erase an interpretation registered during elaboration, without installing new listeners. */ + private def erase(resolution: TypeResolution, seen: Set[TypeResolution]): ErasedValueType = + import TypeShape.* + if seen(resolution) || resolution.hasErrors then ErasedType.Unknown + else + val next = seen + resolution + resolution.shapes.toList match + case Nominal(defn) :: Nil => defn.sym match + case symbol: (ClassSymbol | ModuleOrObjectSymbol) => ErasedType.ValueLike(S(false), symbol) + case _ => ErasedType.Unknown + case Alias(_, rhs) :: Nil => rhs.fold(ErasedType.Unknown)(erase(_, next)) + case Union(left, right) :: Nil => ErasedType.union(erase(left, next), erase(right, next)) + case Function :: Nil => ErasedType.Function(S(false)) + case Unit :: Nil => ErasedType.Unit + case _ => ErasedType.Unknown + + private def eraseSignature(sign: Term): Opt[ErasedValueType] = + if config.language.useNewResolution then + assert(sign.typeInterpretation.isDefined, "Signature was not registered during elaboration") + S(erase(sign.typeInterpretation.get, Set.empty)) + else ErasedType.eraseSign(sign) + + private def declaredType(sign: Opt[Term], modulefulness: Modulefulness): Opt[ErasedValueType] = + modulefulness.msym match + case S(msym) => S(ErasedType.ValueLike(rsc = S(false), msym)) + case N => sign.flatMap(eraseSignature) + + /** Splits written arrows, retaining an unknown arity when a parameter tuple contains a spread. */ + private def splitSignature(sign: Term): Opt[(Ls[Ls[Opt[ErasedValueType]]], Term)] = + def paramsOf(lhs: Term): Opt[Ls[Opt[ErasedValueType]]] = lhs match + case Tup(fields) => + val noParams: Opt[Ls[Opt[ErasedValueType]]] = S(Nil) + fields.foldRight(noParams): (fld, acc) => + (fld, acc) match + case (Fld(_, t, _), S(rest)) => S(eraseSignature(t) :: rest) + case _ => N + case single => S(eraseSignature(single) :: Nil) + sign match + case Forall(_, _, body) => splitSignature(body) + case FunTy(lhs, rhs, _) => paramsOf(lhs).map: ps => + splitSignature(rhs) match + case S((rest, ret)) => (ps :: rest, ret) + case N => (ps :: Nil, rhs) + case _ => N + + private def definitionType(td: TermDefinition): Opt[ErasedType] = + val declared = Annot.declareModifierOf(td.annotations).isDefined + // A declared function with no written parameters obtains its calling convention from its arrows. + // An ordinary paramless function instead returns the value denoted by the entire signature. + val sigShape = + if (td.k is syntax.Fun) && td.params.isEmpty && declared then td.sign.flatMap(splitSignature) + else N + def stripSignatureParams(sign: Term, count: Int): Term = (sign, count) match + case (Forall(_, _, body), _) => stripSignatureParams(body, count) + case (FunTy(_, rhs, _), n) if n > 0 => stripSignatureParams(rhs, n - 1) + case _ => sign + val resultSign = td.sign.map: sign => + if (td.k is syntax.Fun) && !td.flags.hasResultAnnotation + then stripSignatureParams(sign, td.params.length) + else sigShape.map(_._2).getOrElse(sign) + val resultType = declaredType(resultSign, td.modulefulness) + td.k match + case syntax.Fun => + val paramLists = sigShape match + case S((ps, _)) => ps + case N => td.params.map(_.params.map(_.sym.erasedType)) + // Block-level nullary functions take an implicit empty list and references auto-invoke them. + // Member getters and global declarations denote their result directly. + val physicalParamLists = + if paramLists.isEmpty && td.owner.isEmpty && !declared then Nil :: Nil else paramLists + if physicalParamLists.isEmpty then resultType + else S(ErasedType.FuncRef(rsc = S(false), physicalParamLists, resultType)) + case _: syntax.Val => resultType + case _ => N + + private def run(root: Statement): Unit = + val visited = mutable.Set.empty[Identity[Statement]] + val statements = mutable.ArrayBuffer.empty[Statement] + val parameters = mutable.ArrayBuffer.empty[Param] + def params(ps: ParamList): Unit = ps.foreach(parameters += _) + // Handler continuations and rejected class rest parameters can survive as references after their + // parameter list has been removed. The symbol retains the source declaration needed for erasure. + def referencedParameter(sym: VarSymbol): Unit = sym.decl match + case S(p: Param) => + parameters += p + p.subTerms.foreach(visit) + case _ => () + def visit(statement: Statement): Unit = if visited.add(Identity(statement)) then + statements += statement + statement match + case td: TermDefinition => td.params.foreach(params); td.tparams.foreach(_.foreach(parameters += _)) + case cls: ClassLikeDef => + cls.paramsOpt.foreach(params) + cls.auxParams.foreach: ps => + params(ps) + ps.subTerms.foreach(visit) + cls.ext.foreach(visit) + cls match + case pat: PatternDef => + parameters ++= pat.parameters + pat.parameters.flatMap(_.subTerms).foreach(visit) + pat.pattern.subTerms.foreach(visit) + case _ => () + case Lam(ps, _) => params(ps) + case Handle(_, _, _, _, definitions, _) => definitions.foreach: d => + referencedParameter(d.resumeSym) + visit(d.td) + case Ref(sym: VarSymbol) => referencedParameter(sym) + case SimpleRef(sym: VarSymbol) => referencedParameter(sym) + case New(_, _, refinement) => refinement.foreach(r => visit(r._2.blk)) + case Rcd(_, stats) => stats.foreach(visit) + case _ => () + statement.subStatements.foreach(visit) + // Legacy resolution may insert executable terms (for example implicit applications). + // Expanded nodes may share their children with the source tree; visit their identities only once. + statement match + case term: Resolvable => visit(term.expanded) + case _ => () + visit(root) + + // No erased representation is read until every interpretation has been validated and every + // nominal parent header has been published, including forward and external declarations. + statements.foreach: + case term: Term => term.typeInterpretation.foreach(_.validate(Set.empty)) + case _ => () + statements.foreach: + case cls: ClassLikeDef => cls.sym match + case sym: ClassLikeSymbol if sym.irClassHeader.isEmpty => + sym.irClassHeader = cls.ext match + case N => S(ClassHeader(N)) + case S(parent) => parent.cls.resolvedSym.flatMap(_.asClsOrMod).map(p => ClassHeader(S(p))) + case _ => () + case _ => () + + parameters.foreach: p => + if !p.sym.isErased then p.sym.erasedType = declaredType(p.sign, p.modulefulness) + // Public fields have a BlockMemberSymbol; private/auxiliary constructor fields have a TermSymbol. + // Their source signatures belong to the parameter, not to the synthesized field definition. + val field = p.fldSym.flatMap: + case sym: TermSymbol => S(sym) + case sym: BlockMemberSymbol => sym.tsym + case _ => N + field.foreach: sym => + if !sym.isErased then sym.erasedType = p.sym.erasedType + statements.foreach: + case td: TermDefinition if !td.tsym.isErased => td.tsym.erasedType = definitionType(td) + case _ => () + statements.foreach: + case LetDecl(sym: TermSymbol, _) if !sym.isErased => sym.erasedType = N + case _ => () + completed = S(root) + +object Erasure: + /** Prelude declarations use the same pass even though they have no executable program. */ + def apply(root: Statement)(using Config, Ctx, State): Erasure = + val erasure = new Erasure + erasure.run(root) + erasure diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index 6f77793523..aadfae1ddb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -1181,7 +1181,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): val cls = obj.cls val flattenedSym = BlockMemberSymbol(obj.cls.sym.nme + "$", Nil, true) - val flattenedDSym = TermSymbol.fromFunBms(flattenedSym, N, erasedType = N) + val flattenedDSym = TermSymbol(syntax.Fun, N, Tree.Ident(flattenedSym.nme)) // Contains *all* parameters, and applies them all at once in a single `Instantiate` def mkFlattenedDefn: FunDefn = @@ -1199,7 +1199,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // * The flattened definition takes every parameter list at once and returns an instance of the // * class, so its erased type is only known here, once the parameter lists are assembled. - flattenedDSym.populateErasedType(ErasedType.FuncRef( + flattenedDSym.erasedType = S(ErasedType.FuncRef( rsc = S(false), paramLists = allParamLists.map(_.params.map(_.sym.erasedType)), ret = cls.instanceType, diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index cde8dfaabe..ec916a4c2c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -85,8 +85,7 @@ object Lowering: import Lowering.* -class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): - private given Lowering = this +class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasure): val newResolution: Bool = config.language.useNewResolution val strictResolution: Bool = config.language.strictResolution @@ -1695,27 +1694,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): ps.params.map(_.sym.erasedType) - /** Validate elaborated type interpretations and publish nominal parent links before - * lowering bodies can ask erased-type questions. - * This also handles declarations, whose executable definitions are deliberately omitted. - * An unresolved or ambiguous parent leaves the header absent, rather than inventing a root. - */ - def prepareTypes(statement: Statement): Unit = - statement match - case term: Term => term.typeInterpretation.foreach(_.validate(Set.empty)) - case _ => () - statement match - case cls: ClassLikeDef => cls.sym match - case sym: ClassLikeSymbol if sym.irClassHeader.isEmpty => - sym.irClassHeader = cls.ext match - case N => S(ClassHeader(N)) - case S(parent) => parent.cls.resolvedSym.flatMap(_.asClsOrMod).map(p => ClassHeader(S(p))) - case _ => () - case _ => () - statement.subStatements.foreach(prepareTypes) - def program(main: st.Blk, symbolsToPreserve: Set[BoundSymbol]): Program = - prepareTypes(main) + summon[Erasure].requireCompleted(main) val (imps, funs, rest) = splitBlock(main.stats, Nil, Nil, Nil) diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala index b6244a6443..bc911a893b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala @@ -69,7 +69,7 @@ object InvalCtx: end InvalCtx -class InvalTyper(using codegen.Lowering)(using elState: Elaborator.State, tl: TL)(using Ctx, Config): +class InvalTyper(using codegen.Erasure)(using elState: Elaborator.State, tl: TL)(using Ctx, Config): import tl.{trace, log} // InvalML expands patterns while typing. Its early use of lowering is valid only diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index e6dff78185..0cf0efe383 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -17,7 +17,6 @@ import hkmc2.Message.MessageContext import Keyword.{`and`, `case`, `do`, `else`, `if`, `is`, `let`, `or`, `set`, `then`, `while`} import hkmc2.utils.Scope -import codegen.{ErasedType, ErasedValueType} import SimpleSplit.* import ucs.{error, unapply} @@ -822,7 +821,7 @@ extends Importer: val valueSym = spec.valueParamName.map(nme => VarSymbol(Ident(nme), erasedType = N)) val resumeSym = VarSymbol(Ident("resume"), erasedType = N) val mtdSym = BlockMemberSymbol(spec.methodName, Nil, true) - val tsym = TermSymbol(Fun, N, Ident(spec.methodName), erasedType = N) + val tsym = TermSymbol(Fun, N, Ident(spec.methodName)) val td = TermDefinition( Fun, mtdSym, @@ -1377,21 +1376,21 @@ extends Importer: })(N).withLocOf(tree) case InfixApp(TyTup(tvs), Keywrd(Keyword.`->`), body) => val boundVars = mutable.HashMap.empty[Str, VarSymbol] - def genSym(id: Tree.Ident, erasedType: Opt[ErasedValueType]) = - val sym = VarSymbol(id, erasedType) + def genSym(id: Tree.Ident) = + val sym = VarSymbol(id, erasedType = N) sym.decl = S(TyParam(FldFlags.empty, N, sym)) // TODO vce boundVars += id.name -> sym sym val syms = (tvs.collect: - case id: Tree.Ident => (genSym(id, erasedType = N), N, N) - case InfixApp(id: Tree.Ident, Keywrd(Keyword.`extends`), ub) => (genSym(id, erasedType = N), S(ub), N) - case InfixApp(id: Tree.Ident, Keywrd(Keyword.`restricts`), lb) => (genSym(id, erasedType = N), N, S(lb)) + case id: Tree.Ident => (genSym(id), N, N) + case InfixApp(id: Tree.Ident, Keywrd(Keyword.`extends`), ub) => (genSym(id), S(ub), N) + case InfixApp(id: Tree.Ident, Keywrd(Keyword.`restricts`), lb) => (genSym(id), N, S(lb)) case InfixApp(InfixApp(id: Tree.Ident, Keywrd(Keyword.`extends`), ub), Keywrd(Keyword.`restricts`), lb) => - (genSym(id, erasedType = N), S(ub), S(lb)) + (genSym(id), S(ub), S(lb)) ) val outer = (tvs.collect: - case Outer(S(name: Tree.Ident)) => genSym(name, erasedType = N) - case Outer(N) => genSym(Tree.Ident("outer"), erasedType = N) + case Outer(S(name: Tree.Ident)) => genSym(name) + case Outer(N) => genSym(Tree.Ident("outer")) ) match case ot :: Nil => S(ot) case _ :: rest => @@ -2146,7 +2145,7 @@ extends Importer: return go(sts, Nil, acc) val isMethod = owner.exists(_.isInstanceOf[ClassSymbol]) - val tsym = TermSymbol(k, owner, id, erasedType = N) // TODO? + val tsym = TermSymbol(k, owner, id) // TODO? val tdf = ctx.nest(OuterCtx.NonReturnContext(S(tsym))).givenIn: newCtx ?=> // * Add type parameters to context @@ -2200,80 +2199,10 @@ extends Importer: case _ => Modulefulness.none - /** Splits a signature's arrow chain into the parameter lists it describes and the type it returns. - * Yields `N` if the signature is not an arrow or if some parameter list's arity cannot be read. - */ - def splitSignature(sign: Term): Opt[(Ls[Ls[Opt[ErasedValueType]]], Term)] = - def paramsOf(lhs: Term): Opt[Ls[Opt[ErasedValueType]]] = lhs match - // * A spread parameter leaves the list's arity unknown, so the signature is left unsplit. - case Term.Tup(fields) => - val noParams: Opt[Ls[Opt[ErasedValueType]]] = S(Nil) - fields.foldRight(noParams): (fld, acc) => - (fld, acc) match - case (Fld(_, t, _), S(rest)) => S(eraseSignature(t) :: rest) - case _ => N - // * An unparenthesized type is a single parameter. - case single => S(eraseSignature(single) :: Nil) - sign match - case Term.Forall(_, _, body) => splitSignature(body) - case Term.FunTy(lhs, rhs, _) => paramsOf(lhs).map: ps => - splitSignature(rhs) match - case S((rest, ret)) => (ps :: rest, ret) - case N => (ps :: Nil, rhs) - case _ => N - - // * A signature's arrows are the definition's own parameter lists when a reference to it is not - // * auto-invoked. - // * - // * - A `fun` writing no parameter lists is a getter, so `fun bar: A -> Int` yields - // * the arrow itself; - // * - A `declare`d `fun` becomes a `globalThis` selection, so its arrows are its parameters. - val sigShape: Opt[(Ls[Ls[Opt[ErasedValueType]]], Term)] = - if (k is syntax.Fun) && pss.isEmpty && Annot.declareModifierOf(annotations).isDefined - then s.flatMap(splitSignature) - else N - - // * A moduleful signature (`fun f: module M`) denotes the module itself. - val retTpe = mfn.msym match - case S(msym) => S(ErasedType.ValueLike(rsc = S(false), msym)) - case N => s.flatMap: s => - // * A function that inherits a signature with leading arrows consumes those arrows as its own - // * parameter lists. The exception is a `declare`d function, whose arrows are always its own - // * parameters (see `sigShape`). - def stripSignatureParams(s: Term, n: Int): Term = (s, n) match - case (Term.Forall(_, _, body), _) => stripSignatureParams(body, n) - case (Term.FunTy(_, rhs, _), n) if n > 0 => stripSignatureParams(rhs, n - 1) - case _ => s - val resultSign: Term = - if (k is syntax.Fun) && td.annotatedResultType.isEmpty - then stripSignatureParams(s, pss.length) - else sigShape.map(_._2).getOrElse(s) - eraseSignature(resultSign) - val erasedTpe = k match - case syntax.Fun => - // * A `declare`d function's parameter lists are derived from its signature when it writes none. - val paramLists = sigShape match - case S((ps, _)) => ps - case N => pss.map(_.params.map(_.sym.erasedType)) - // * An erased type must describe what a definition is *compiled to*, and a paramless `fun` is - // * compiled in two different ways: - // * - // * - As a class-like member, it becomes either a getter method or a `globalThis` selection depending - // * on if it is `declare`d or not - neither denotes a function value, so the erased type is its - // * result; - // * - At block level, it is lowered to a function with an implicit empty parameter list which every - // * reference auto-invokes, so the erased type will carry that parameter list. - val isCompiledAsGetter = owner.isDefined || Annot.declareModifierOf(annotations).isDefined - val physicalParamLists = - if paramLists.isEmpty && !isCompiledAsGetter then Nil :: Nil else paramLists - if physicalParamLists.isEmpty then retTpe - else S(ErasedType.FuncRef(rsc = S(false), physicalParamLists, retTpe)) - case _: syntax.Val => retTpe - case _ => N - // val tsym = TermSymbol(k, owner, id, erasedType = erasedTpe) // TODO? - tsym.erasedType = erasedTpe + // Retain the source signature's meaning; Erasure decides which arrows are physical parameters. + if newResolution then s.foreach(registerSignature) val tdf = TermDefinition(k, sym, tsym, pss, tps, s, body, - TermDefFlags.empty.copy(isMethod = isMethod), mfn, annotations, N).withLocOf(td) + TermDefFlags.empty.copy(isMethod = isMethod, hasResultAnnotation = td.annotatedResultType.isDefined), mfn, annotations, N).withLocOf(td) sym.tsym = S(tsym) tsym.defn = S(tdf) @@ -2400,11 +2329,10 @@ extends Importer: p.fldSym = S(fsym) fsym.tsym = S(tsym) tsym.defn = S(fdef) - p.sym.erasedType.foreach(tsym.populateErasedType) fsym.complete() fdef :: Nil else - val psym = TermSymbol(LetBind, owner, p.sym.id, erasedType = p.sym.erasedType) + val psym = TermSymbol(LetBind, owner, p.sym.id) psym.sourceAliases = p.sym.sourceAliases val decl = LetDecl(psym, Nil) // TODO: never use term symbols on LetDecl LHS val defn = defineVar(psym, p.sym.ref()) @@ -2417,7 +2345,7 @@ extends Importer: val owner = td.symbol match case s: InnerSymbol => S(s) case _: TypeAliasSymbol => die - val psym = TermSymbol(LetBind, owner, p.sym.id, erasedType = p.sym.erasedType) + val psym = TermSymbol(LetBind, owner, p.sym.id) psym.sourceAliases = p.sym.sourceAliases val decl = LetDecl(psym, Nil) val defn = defineVar(psym, p.sym.ref()) @@ -2699,12 +2627,8 @@ extends Importer: id -> Nil val sig = sign.map(term(_, Tpe)) val mfn = Modulefulness.ofSign(sig)(Mod in modifiers) - // * As for return signatures, a moduleful parameter (`module m: M`) denotes the module itself, which - // * `eraseSign` would miss by resolving the name through `asTpe`. - val erasedTpe = mfn.msym match - case S(msym) => S(ErasedType.ValueLike(rsc = S(false), msym)) - case N => sig.flatMap(eraseSignature) - val sym = VarSymbol(canonicalId, erasedType = erasedTpe) + if newResolution then sig.foreach(registerSignature) + val sym = VarSymbol(canonicalId) sym.sourceAliases = aliases val p = Param(flg, sym, sig, mfn) sym.decl = S(p) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index a35a152143..46b855a395 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -98,11 +98,21 @@ class NewResolver: case shape: TermShape => reject(shape) result - def eraseSignature(sign: Term): Opt[codegen.ErasedValueType] = - if newResolution then - val resolution = typeResolution(sign) - S(new codegen.ErasedType.Deferred(resolution.erase(Set.empty))) - else codegen.ErasedType.eraseSign(sign) + /** Register complete signatures while resolution can still publish candidates. Arrow operands + * need their own interpretations: erasure may consume the arrows as physical parameter lists. + */ + def registerSignature(sign: Term): Unit = + typeResolution(sign) + sign match + case Forall(_, _, body) => registerSignature(body) + case FunTy(lhs, rhs, _) => + lhs match + case Tup(fields) => fields.foreach: + case Fld(_, term, _) => registerSignature(term) + case _ => () + case single => registerSignature(single) + registerSignature(rhs) + case _ => () /** Declared types provide instance members before there are any calls or assignments. * Recursive aliases are followed once per subscription; their operands may resolve later. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index af6510bef9..bf07d926dd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -360,7 +360,7 @@ class Resolver(tl: TraceLogger) trace(s"Resolving definition: $defn"): def traverseTermDef(tdf: TermDefinition) = val TermDefinition(_k, _sym, _tsym, - pss, tps, sign, body, TermDefFlags(isMethod), modulefulness, annotations, comp + pss, tps, sign, body, TermDefFlags(isMethod, _), modulefulness, annotations, comp ) = tdf /** * Add the contextual parameters in pss to the ICtx so that they @@ -423,7 +423,7 @@ class Resolver(tl: TraceLogger) defn match // Case: instance definition. Add the instance to the context. - case defn @ TermDefinition(k = Ins, sym = sym, tsym = tsym, flags = TermDefFlags(isMethod), sign = sign) => + case defn @ TermDefinition(k = Ins, sym = sym, tsym = tsym, flags = TermDefFlags(isMethod, _), sign = sign) => softAssert(defn.owner.isEmpty) log(s"Resolving instance definition ${defn.showDbg}") traverseTermDef(defn) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala index 4d004d1a56..e31d8e6475 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala @@ -90,13 +90,13 @@ enum SimpleSplit extends AutoLocated with ProductWithTail: /** This field is designed to be compatible with bbML. */ private var _expandedSplit: Opt[Split] = N - /** Expand patterns after resolution, with symbol queries authorized by lowering. */ - def getExpandedSplit(using codegen.Lowering, TL, Ctx, State, Raise, Config): Split = _expandedSplit.getOrElse: + /** Expand patterns after resolution, with symbol queries authorized by erasure. */ + def getExpandedSplit(using codegen.Erasure, TL, Ctx, State, Raise, Config): Split = _expandedSplit.getOrElse: val split = Split.from(this) _expandedSplit = S(split) split - def mkClone(using State, codegen.Lowering): SimpleSplit = this match + def mkClone(using State, codegen.Erasure): SimpleSplit = this match case Cons(head, tail) => Cons(head match case Head.Match(scrutinee, pattern, consequent) => Head.Match(scrutinee.mkClone.asInstanceOf, pattern, consequent.mkClone) // TODO: clone `pattern`? diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index 9eb39494c1..55419f4a17 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -5,7 +5,7 @@ import hkmc2.utils.*, shorthands.* import syntax.*, Elaborator.State, ucs.FlatPattern final case class Branch(scrutinee: Term.Ref, pattern: FlatPattern, continuation: Split) extends AutoLocated: - def mkClone(using State, codegen.Lowering): Branch = + def mkClone(using State, codegen.Erasure): Branch = val scrutineeClone = new Term.Ref(scrutinee.sym) (Tree.Ident(scrutinee.tree.name), scrutinee.typ) Branch(scrutineeClone, pattern.mkClone, continuation.mkClone) @@ -31,7 +31,7 @@ enum Split extends AutoLocated with ProductWithTail: inline def ~:(head: Branch): Split = Split.Cons(head, this) - def mkClone(using State, codegen.Lowering): Split = this match + def mkClone(using State, codegen.Erasure): Split = this match case Cons(head, tail) => Cons(head.mkClone, tail.mkClone) case Let(sym, term, tail) => Let(sym, term.mkClone, tail.mkClone) case Else(default) => Else(default.mkClone) @@ -186,7 +186,7 @@ object Split: import Term.Ref /** Desugar a `SimpleSplit` into a `Split`. */ - def from(rootSplit: SS)(using codegen.Lowering)(using tl: TL)(using Ctx, Raise, State, Config): Split = + def from(rootSplit: SS)(using codegen.Erasure)(using tl: TL)(using Ctx, Raise, State, Config): Split = val compiler = new SplitCompiler() val scrutCache = MutMap.empty[Ref, Scrut] def go(split: SS): Split = split match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index cb58993363..713f5b33bb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -10,7 +10,7 @@ import hkmc2.utils.* import Elaborator.State import Tree.Ident -import hkmc2.codegen.{ErasedType, ErasedFuncType, ErasedValueType, HasErasedType, HasOnceMutableErasedType} +import hkmc2.codegen.{ErasedType, ErasedFuncType, ErasedValueType, HasErasedType, HasOnceMutableErasedType, HasDelayedErasedType} import hkmc2.utils.SymbolSubst @@ -271,9 +271,13 @@ class InstSymbol(val origin: Symbol)(using State) extends LocalSymbol: def subst(using sub: SymbolSubst): InstSymbol = sub.mapInstSym(this) -class VarSymbol(val id: Ident, override val erasedType: Opt[ErasedValueType])(using State) +class VarSymbol(val id: Ident)(using State) extends LocalVarSymbol(id.name) + with HasDelayedErasedType[ErasedValueType] with NamedSymbol: + def this(id: Ident, erasedType: Opt[ErasedValueType])(using State) = + this(id) + this.erasedType = erasedType val name: Str = id.name var sourceAliases: Ls[Str] = Nil override def toLoc: Opt[Loc] = id.toLoc @@ -378,11 +382,14 @@ sealed abstract class MemberSymbol(using State) extends Symbol: def subst(using SymbolSubst): MemberSymbol -class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.Ident, override var erasedType: Opt[ErasedType])(using State) +class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.Ident)(using State) extends MemberSymbol with DefinitionSymbol[TermDefinition] - with HasOnceMutableErasedType + with HasDelayedErasedType[ErasedType] with NamedSymbol: + def this(k: TermDefKind, owner: Opt[InnerSymbol], id: Tree.Ident, erasedType: Opt[ErasedType])(using State) = + this(k, owner, id) + this.erasedType = erasedType var sourceAliases: Ls[Str] = Nil def nme: Str = id.name def name: Str = nme @@ -473,7 +480,7 @@ sealed trait ClassLikeSymbol extends IdentifiedSymbol, HasErasedType: val tree: Tree.TypeDef def subst(using sub: SymbolSubst): ClassLikeSymbol - /** Published by lowering before class bodies are lowered, including for external declarations. + /** Published by erasure before class bodies are lowered, including for external declarations. * Full IR class definitions refresh this header when rewritten, just as they own `irDefn`. * Keeping the header separately is necessary for forward parents and declarations without * executable bodies; consumers must not reconstruct it from semantic terms. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index fe919b3415..33b48721b6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -15,6 +15,7 @@ import hkmc2.typing.Type import hkmc2.semantics.Elaborator.{Ctx, ctx} import hkmc2.Message.MessageContext import hkmc2.semantics.flow.{SelectionTarget, AppTarget} +import hkmc2.codegen.Erasure final case class QuantVar(sym: VarSymbol, ub: Opt[Term], lb: Opt[Term]) @@ -77,7 +78,7 @@ enum Annot extends AutoLocated: case Trm(trm) => doc"@${trm.show}" case Config(_) => doc"@config(...)" - def mkClone(using State, codegen.Lowering): Annot = this match + def mkClone(using State, Erasure): Annot = this match case Untyped => Untyped case Modifier(mod) => Modifier(mod) case Trm(trm) => Trm(trm.mkClone) @@ -365,7 +366,7 @@ sealed trait NewSelImpl extends NewResolvableImpl: var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution // Class identity and captures must survive even when candidates share an inherited member. var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil - def hasAmbiguousClass: Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: + def hasAmbiguousClass(using Erasure): Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: _.withoutCaptures match case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.sizeCompare(1) > 0 case _ => false @@ -379,7 +380,7 @@ sealed trait UnresolvedRefImpl extends NewResolvableImpl: enum Term extends Statement, ShapePublisher: - /** Filled by the type interpreter during elaboration; lowering only validates the result. */ + /** Filled by the type interpreter during elaboration; erasure validates the completed result. */ private[hkmc2] var typeInterpretation: Opt[TypeResolution] = N case Error() @@ -524,9 +525,9 @@ enum Term extends Statement, ShapePublisher: /** * The symbol representing the evaluation result of the term. This * symbol is resolved during the resolution stage. Reading its final value requires - * lowering; elaboration must listen for shapes instead. + * erasure; elaboration must listen for shapes instead. */ - def resolvedSym(using codegen.Lowering): Opt[Symbol] = expanded match + def resolvedSym(using Erasure): Opt[Symbol] = expanded match case res: Resolved => S(res.sym) case SimpleRef(sym) => S(sym) case Capture(base, _) => base.resolvedSym @@ -614,7 +615,7 @@ enum Term extends Statement, ShapePublisher: /** Duplicate an elaborated expression after resolution, retaining its semantic * identity and completed results. Listeners belong to elaboration and are not copied. */ - override def mkClone(using State, codegen.Lowering): Term = + override def mkClone(using State, Erasure): Term = def copyShapes[T <: ShapeHost](source: ShapeHost, copy: T): T = copy.shapes ++= source.shapes copy @@ -810,7 +811,7 @@ trait Describable: sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: - def mkClone(using State, codegen.Lowering): Statement = this match + def mkClone(using State, Erasure): Statement = this match case t: Term => lastWords(s"overridden implementation") case d: Definition => ??? case imp: Import => Import(imp.sym, imp.str, imp.file) @@ -1314,17 +1315,18 @@ final case class SetConfig(modify: hkmc2.Config => hkmc2.Config) extends Stateme enum Visibility: case Public, Private -/** - * isMethod: if the term is a method (as opposed to a function) - */ -final case class TermDefFlags(isMethod: Bool): +/** `isMethod` distinguishes methods from functions. `hasResultAnnotation` records whether a + * signature describes just the result; erasure consumes leading arrows only for full signatures. + */ +final case class TermDefFlags(isMethod: Bool, hasResultAnnotation: Bool): def showDbg: Str = val flags = Buffer.empty[String] if isMethod then flags += "method " + if hasResultAnnotation then flags += "result annotation " flags.mkString override def toString: String = "‹" + showDbg + "›" -object TermDefFlags { val empty: TermDefFlags = TermDefFlags(false) } +object TermDefFlags { val empty: TermDefFlags = TermDefFlags(false, false) } /** * A case class representing the modulefulness of a declaration. @@ -1825,7 +1827,7 @@ object Apps: trait BlkImpl: this: Blk => - def mkBlkClone(using State, codegen.Lowering): Blk = Blk(stats.map(_.mkClone), res.mkClone) + def mkBlkClone(using State, Erasure): Blk = Blk(stats.map(_.mkClone), res.mkClone) def showTopLevel(using Scope, ShowCfg, Raise): Document = (stats ::: (res match case Lit(Tree.UnitLit(false)) => Nil diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index d74bca131b..5b09f4fde7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -2,8 +2,7 @@ package hkmc2 package semantics import hkmc2.utils.*, shorthands.* -import codegen.{ErasedType, ErasedValueType} -import Elaborator.{Ctx, State} +import codegen.Erasure /** A type interpretation, separate from the value shapes of the same reference syntax. * Compound types retain links to their operands so forward aliases need not be expanded eagerly. @@ -23,6 +22,7 @@ enum TypeShape: */ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => Unit) extends Host[TypeShape]: private var reported = false + def hasErrors: Bool = reported def showDbg(using DebugPrinter): Str = s"type of ${source.showDbg}" def publish(shape: TypeShape): Unit = if shapes.add(shape) then shapeListeners.foreach(_(shape)) @@ -32,7 +32,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => def fail(messages: Ls[(Message, Opt[Loc])]): Unit = if !reported then reported = true report(messages) - def validate(seen: Set[TypeResolution]): Unit = if !seen(this) then + def validate(seen: Set[TypeResolution])(using Erasure): Unit = if !seen(this) then import TypeShape.* import Message.MessageContext val next = seen + this @@ -55,24 +55,3 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case Union(left, right) => left.validate(next); right.validate(next) case Intersection(left, right) => left.validate(next); right.validate(next) case _ => () - - def erase(seen: Set[TypeResolution])(using Ctx, State): ErasedValueType = - import TypeShape.* - import Message.MessageContext - validate(seen) - if seen(this) || reported then ErasedType.Unknown - else - val next = seen + this - shapes.toList match - case Nominal(defn) :: Nil => defn.sym match - case symbol: (ClassSymbol | ModuleOrObjectSymbol) => ErasedType.ValueLike(S(false), symbol) - case _ => ErasedType.Unknown - case Alias(_, rhs) :: Nil => rhs.fold(ErasedType.Unknown)(_.erase(next)) - case Union(left, right) :: Nil => ErasedType.union(left.erase(next), right.erase(next)) - case Intersection(_, _) :: Nil | Dynamic :: Nil | Abstract :: Nil => ErasedType.Unknown - case Function :: Nil => ErasedType.Function(S(false)) - case Unit :: Nil => ErasedType.Unit - case Nil => - ErasedType.Unknown - case _ => - ErasedType.Unknown diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala index ea17c2efb0..9d632df25a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -37,7 +37,7 @@ enum FlatPattern extends AutoLocated: case Record(entries: List[(Ident -> LocalVarSymbol)]) - def mkClone(using State, codegen.Lowering): FlatPattern = this match + def mkClone(using State, codegen.Erasure): FlatPattern = this match case Lit(literal) => Lit(literal) case pattern @ ClassLike(constructor, symbol, arguments, refined) => ClassLike(constructor.mkClone, symbol, arguments, refined)(Tree.Dummy) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index 740bb40b0f..0d622f26d5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -6,11 +6,10 @@ import hkmc2.utils.*, shorthands.* import syntax.{Literal, Tree, Keyword}, utils.* import Message.MessageContext import Elaborator.{Ctx, State, ctx} -import codegen.Lowering +import codegen.{Lowering, Erasure} -class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, Config) extends TermSynthesizer: - private given Lowering = lowering +class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, Config, Erasure) extends TermSynthesizer: import Normalization.*, Mode.* import tl.* diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala index b05efb12ad..5cea64e37e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -18,7 +18,7 @@ import collection.mutable.{Queue, Map as MutMap, LinkedHashMap}, collection.immu * a few matcher functions. Each matcher function matches a set of patterns * and returns a record that contains the results of each pattern. */ -class Compiler(using Context)(using codegen.Lowering)(using tl: TL)(using Ctx, State, Raise) extends TermSynthesizer: +class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, State, Raise) extends TermSynthesizer: import Compiler.*, tl.* import Pattern.* @@ -573,7 +573,7 @@ object Compiler: /** Clone a source-level constructor reference for insertion into synthesized * matcher code, preserving the path chosen by source resolution. */ - def preservedReference(term: Term)(using State, codegen.Lowering): Term = + def preservedReference(term: Term)(using State, codegen.Erasure): Term = /** To make `Lowering` happy about the terms. */ def fillImplicitArgs(term: Term): Term = term match case ref: Ref => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala index 69d11c327e..4766d845cd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala @@ -154,7 +154,7 @@ object FixedPointCompiler: * machine compilation does not support get a warning and fall back to the * naive backtracking translation. */ -class FixedPointCompiler(using codegen.Lowering)(using tl: TL)(using State, Ctx, Raise) extends TermSynthesizer: +class FixedPointCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise) extends TermSynthesizer: import FixedPointCompiler.*, tl.* /** Try to compile the given `@compile`-annotated pattern into a fixed-point diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala index 96fad6cb49..e757cb8695 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala @@ -14,7 +14,7 @@ import scala.collection.immutable.SeqMap object Instantiator: type Subst = Map[VarSymbol, Pat] -class Instantiator(using codegen.Lowering)(using tl: TL)(using Ctx, State, Raise): +class Instantiator(using codegen.Erasure)(using tl: TL)(using Ctx, State, Raise): import tl.* /** The map contains all instantiated patterns. The key is a pattern symbol diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala index c8faa7768d..0fd438b6b1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala @@ -119,7 +119,7 @@ object SplitCompiler: import SplitCompiler.* /** This class compiles a pattern to a split that matches the pattern. */ -class SplitCompiler(using codegen.Lowering)(using tl: TL)(using State, Ctx, Raise, Config) extends TermSynthesizer: +class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise, Config) extends TermSynthesizer: import tl.*, SP.* val newResolution: Bool = config.language.useNewResolution diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index c6e1074649..db23bac6d0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -673,7 +673,7 @@ trait TypeDefImpl(using State) extends TypeOrTermDef: pts.flatMap(_.desugared.asParam(inUsing = inUsing).toOption).collect: case pt @ ParamTree(ident = id, spd = N) => val k = if pt.flags.mut then MutVal else ImmutVal - TermSymbol(k, symbol.asClsLike, id, erasedType = N) + TermSymbol(k, symbol.asClsLike, id) .toList lazy val allSymbols = definedSymbols ++ diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index fdd206d686..03a60c9649 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -70,9 +70,10 @@ class DebugPrinter: case xs: List[_] => "Ls of\n" + xs.iterator.map(aux(_)).mkString("\n").indent(" ") case xs: Vector[_] => "Vector of\n" + xs.iterator.map(aux(_)).mkString("\n").indent(" ") case s: Str => s.escaped - case TermDefFlags(isMethod) => + case TermDefFlags(isMethod, hasResultAnnotation) => val flags = Buffer.empty[Str] if isMethod then flags += "method" + if hasResultAnnotation then flags += "result annotation" flags.mkString("(", ", ", ")") case FldFlags(mut, spec, pat, value) => val flags = Buffer.empty[Str] diff --git a/hkmc2/shared/src/test/mlscript/ctx/NestedUsingInstances.mls b/hkmc2/shared/src/test/mlscript/ctx/NestedUsingInstances.mls index c8745c63bb..3100172059 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/NestedUsingInstances.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/NestedUsingInstances.mls @@ -34,6 +34,7 @@ module Ex with //│ t = Ref{sym=member:Int¹} of member:Int¹ //│ sym = class:Int⁰ //│ body = S of Lit of IntLit of 1 +//│ flags = (result annotation) //│ modulefulness = Modulefulness of N //│ res = App: //│ lhs = Resolved{sym=term:get⁰}: diff --git a/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls b/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls index 0ade97538c..dad862642c 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls @@ -169,6 +169,7 @@ fun f(module m: M): module M = m //│ t = Ref{sym=member:M¹} of member:M¹ //│ sym = module:M⁰ //│ body = S of Ref{sym=m²,typ=module:M⁰} of m² +//│ flags = (result annotation) //│ modulefulness = Modulefulness of S of module:M⁰ //│ res = Lit of UnitLit of false diff --git a/hkmc2/shared/src/test/mlscript/newres/Erasure.mls b/hkmc2/shared/src/test/mlscript/newres/Erasure.mls new file mode 100644 index 0000000000..2eb594d9c9 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Erasure.mls @@ -0,0 +1,54 @@ +:. + + +// The call is lowered before its callee and the alias declarations. +:expect 42 +fun caller(): Number = later(42) +fun later(value: Number): Number = value +type Number = NextNumber +type NextNumber = Int +caller() +//│ = 42 + + +// A forward signature supplies both constructor parameters and generated fields. +:expect 42 +class Box(val value: Number) with + fun get(): Number = value +type Number = Int +Box(42).get() +//│ = 42 + + +// Lambda parameter annotations must be prepared even inside a record field. +:expect 42 +type Number = Int +let record = {apply: (value: Number) => value} +record.apply(42) +//│ = 42 +//│ record = {apply: fun apply} + + +// Both wildcard sources arrive after the annotation; the unused signature must still be checked. +:ge +open Left +open Right +fun unused(value: Types.Number) = value +module Left with + module Types with + type Number = Int +module Right with + module Types with + type Number = Str +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.36: fun unused(value: Types.Number) = value +//│ ║ ^^^^^^^^^^^^ +//│ ╟── This type is ambiguous, as it has multiple resolved targets +//│ ║ l.36: fun unused(value: Types.Number) = value +//│ ║ ^^^^^^^^^^^^ +//│ ╟── candidate: type alias +//│ ║ l.39: type Number = Int +//│ ║ ^^^^^^ +//│ ╟── candidate: type alias +//│ ║ l.42: type Number = Str +//│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/Erasure.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/Erasure.mls new file mode 100644 index 0000000000..0a01c63a1d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/Erasure.mls @@ -0,0 +1,26 @@ +:. + + +:global +:wasm + + +// Erasure must prepare an unboxed call target before lowering the caller's body. +:expect 42 +fun caller(): Number = later(wasm.i32.const(41)) +fun later(value: Number): Number = wasm.i32.add(value, wasm.i32.const(1)) +type Number = LaterNumber +type LaterNumber = Int32 +caller() +//│ Wasm result: +//│ = 42 + + +// Forward aliases determine both the constructor slot and the generated field's representation. +:expect 42 +class Box(val value: Number) with + fun get(): Number = value +type Number = Int32 +Box(wasm.i32.const(42)).get() +//│ Wasm result: +//│ = 42 diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala index b12226c564..a7d02f5aa2 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/InvalmlDiffMaker.scala @@ -38,9 +38,8 @@ abstract class InvalMLDiffMaker extends JSBackendDiffMaker: if invalmlOpt.isSet then given Scope = Scope.empty(Scope.Cfg.default) given Elaborator.Ctx = curCtx - given codegen.Lowering = new codegen.Lowering() // Typing may lower patterns even when executable code generation is disabled. - summon[codegen.Lowering].prepareTypes(trm) + given codegen.Erasure = codegen.Erasure(trm) if invalmlTyper.isEmpty then invalmlTyper = S(InvalTyper()) given hkmc2.invalml.InvalCtx = invalCtx.copy(raise = summon) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index cc70b4a6a6..796f70cc8a 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -124,6 +124,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: if noCodeGen.isUnset then given Elaborator.Ctx = curCtx + given codegen.Erasure = codegen.Erasure(blk) val low = ltl.givenIn: new codegen.Lowering()(using summon[Config], ltl, summon[Raise], loweringState, curCtx, summon[SymbolPrinter]) with codegen.LoweringTraceLog(traceJS.isSet) From 62cf317c90cc65c8de0b7515bf54325304bc04d4 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 24 Sep 2026 12:18:20 +0800 Subject: [PATCH 062/228] Add Metals MCP config for Codex --- .codex/config.toml | 5 +++++ AGENTS.md | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .codex/config.toml diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000000..32817e2aa6 --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,5 @@ +[mcp_servers.metals] +command = "/opt/homebrew/bin/cs" +args = ["launch", "org.scalameta:metals-mcp_2.13:1.6.9", "--", "--workspace", "/Users/parreaux/work/Research/code/mlscript", "--transport", "stdio"] +startup_timeout_sec = 180 +tool_timeout_sec = 600 diff --git a/AGENTS.md b/AGENTS.md index 9a14ecd652..95b16fe91e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,6 +3,14 @@ ## Workflow +When working on Scala code, use the Metals MCP tools when available for semantic +symbol searches, inspection, documentation, and finding usages, and for quick +file or module compilation feedback. Codex's project configuration in +`.codex/config.toml` can launch the headless Metals server automatically. +If Metals is unavailable, continue with source searches and SBT, and report the +connection problem. Metals compilation and test tools do not replace the SBT +test prerequisites and final validation below. + It is best to leave the SBT shell open (by just typing the `sbt` command line) and issue commands directly in that shell, as SBT startup is very slow. @@ -115,4 +123,3 @@ to ensure that no needless empty-line changes are included in the PR. If you fin When working with IR representations, please refer to the "Important design notes" in `hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala`. - From ceaf3525fdf1070b8279628efb06610ec39f73be Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 12:36:12 +0800 Subject: [PATCH 063/228] Treat declared types as opaque resolution shapes --- doc/new-resolution-suite-migration.md | 49 +- .../main/scala/hkmc2/codegen/Erasure.scala | 28 +- .../scala/hkmc2/semantics/Elaborator.scala | 7 +- .../scala/hkmc2/semantics/NewResolver.scala | 498 ++++++++++++++---- .../main/scala/hkmc2/semantics/Shape.scala | 54 +- .../src/main/scala/hkmc2/semantics/Term.scala | 11 + .../scala/hkmc2/semantics/TypeShape.scala | 17 +- .../test/mlscript/newres/DeclaredTypes.mls | 378 +++++++++++++ .../mlscript/newres/wasm/DeclaredTypes.mls | 18 + 9 files changed, 935 insertions(+), 125 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/wasm/DeclaredTypes.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index a5d2c7854a..254508edd5 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -58,7 +58,8 @@ fixtures and four `mlscript-compile/ups` fixtures need a subsequent dependency-o migration. Their shared consumers make a blanket flag change inappropriate for this worksheet migration. -The typed-constructor-field capture assertion in WASM `Basics` remains outstanding. +The annotated-receiver capture assertion in WASM `Basics` is fixed; a later +unannotated `this.x` initializer still blocks that worksheet (see task 3). Method calls across REPL blocks remain assigned to the other branch. Constructor-pattern field propagation also has a serious context-mixing blocker, documented below; its `:fixme` regression is not evidence that migration is complete. @@ -172,19 +173,35 @@ an untyped constructor field carrying a different capture context. `codegen/CurriedClassInheritance.mls` and `ParamClasses.mls` also expose capture or receiver-shape problems; they are not all necessarily the same bug. -**Question:** does `b: Base` restrict member lookup to Base's declared interface, -or may concrete call arguments expose subclass-only members? This remains the -unanswered question from the preceding phase. - -**Proposal:** use the declared interface for member availability, preserving -virtual dispatch. Keep value-flow provenance separate from that interface so -reading a field cannot feed constructor entry marks into an unrelated function -exit. Publish declared parameter, result, and field types consistently, including -functions with no observed calls. Never fix the assertion by dropping marks. - -Acceptance cases: an unused annotated function, an unannotated constructor field, -subclass overrides, inherited fields, curried constructors, nested captures, and -generic aliases. Coordinate REPL method cases with the other branch. +**Implemented:** annotations are opaque shapes. `b: Base` exposes Base's declared +members and preserves virtual dispatch; an implementation or observed argument +cannot add subclass members. Parameters, result annotations, separate signatures, +and constructor fields use the same declared interfaces, even without observed +calls. Reading an unannotated member through such an interface yields an unknown +shape instead of following its initializer or method body. + +Type interpretations retain arrows, type arguments, and lexical captures. Generic +aliases and inherited declared interfaces substitute their type arguments. Explicit +and inferred function/constructor type arguments flow through the corresponding +type-parameter symbols with entry/exit marks; explicit arguments prevent further +refinement from value arguments. Inference also connects parameters through nested +nominal arguments, as in `Foo[A]` containing a `Box[A]`. Declared member selections +carry those type-argument contexts without reading implementation value flow. + +`newres/DeclaredTypes.mls` covers unused annotated functions, unannotated fields, +subclass overrides, inherited fields, curried constructors, nested captures, +generic aliases, separate signatures, and distinct generic instantiations. Its +function/tuple-constraint example remains an explicit `:fixme`: inferring through +`Foo[[A, Int]]` with a callback requires function-parameter constraints and tuple +projection shapes. REPL method cases remain assigned to the other branch. + +The original annotated-receiver cases from WASM `Basics` now pass and are covered +by `newres/wasm/DeclaredTypes.mls`. A fresh trial reaches a later capture assertion +in the unannotated initializer `class Foo(val x) with val y = this.x`. +`CurriedClassInheritance` still expects runtime failures for operations now rejected +statically; `ParamClasses` still reaches unimplemented constructor-value member +lookup (`Foo.class`). All three worksheets retain their existing configurations +and goldens until these separate migration blockers are addressed. ### 4. Dynamic construction and foreign callable classes @@ -269,7 +286,7 @@ These changes also fix the sibling-subclass field-extraction regression in 1. **Retain this measured batch.** Run compilation fixtures, main difftests, application difftests, and the aggregate suite. Review output changes; keep nonmigrated files intact. WASM `Basics` retains its previous configuration - pending the capture fix, keeping this partial migration green. + pending its remaining unannotated-initializer capture fix. 2. **Model generator results.** Recursive constructor contexts now normalize through lexical captures. Use `GeneratorResults` to specify iterator results rather than propagating the generator body's return shape. The earlier generator @@ -462,5 +479,5 @@ The files themselves retain the pre-trial configuration and output. ### wasm -- `wasm/Basics.mls`: Unexpected exception; java.lang.IllegalArgumentException: requirement failed: Expected symbol term:getX⁰ but got term:Foo⁰ +- `wasm/Basics.mls`: The annotated-receiver failure is fixed. A new trial fails in `val y = this.x` of `class Foo(val x)` with `Entry and exit cross different lexical resolution scopes`. - `wasm/Binaryen.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class 'Instance' does not contain member 'exports' diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala index 0a73ffbd63..a9bb7355b6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala @@ -30,7 +30,9 @@ final class Erasure private (using Config, Ctx, State): case _ => ErasedType.Unknown case Alias(_, rhs) :: Nil => rhs.fold(ErasedType.Unknown)(erase(_, next)) case Union(left, right) :: Nil => ErasedType.union(erase(left, next), erase(right, next)) - case Function :: Nil => ErasedType.Function(S(false)) + case Captured(base, _) :: Nil => erase(base, next) + case Applied(base, _) :: Nil => erase(base, next) + case Function(_, _) :: Nil => ErasedType.Function(S(false)) case Unit :: Nil => ErasedType.Unit case _ => ErasedType.Unknown @@ -70,15 +72,21 @@ final class Erasure private (using Config, Ctx, State): val sigShape = if (td.k is syntax.Fun) && td.params.isEmpty && declared then td.sign.flatMap(splitSignature) else N - def stripSignatureParams(sign: Term, count: Int): Term = (sign, count) match - case (Forall(_, _, body), _) => stripSignatureParams(body, count) - case (FunTy(_, rhs, _), n) if n > 0 => stripSignatureParams(rhs, n - 1) - case _ => sign - val resultSign = td.sign.map: sign => - if (td.k is syntax.Fun) && !td.flags.hasResultAnnotation - then stripSignatureParams(sign, td.params.length) - else sigShape.map(_._2).getOrElse(sign) - val resultType = declaredType(resultSign, td.modulefulness) + val resultSign = sigShape.map(_._2).orElse(td.resultSignature) + def fullResult(res: TypeResolution, count: Int, seen: Set[TypeResolution]): ErasedValueType = + if seen(res) then ErasedType.Unknown + else if count == 0 then erase(res, Set.empty) + else res.shapes.toList match + case TypeShape.Alias(_, S(rhs)) :: Nil => fullResult(rhs, count, seen + res) + case TypeShape.Captured(base, _) :: Nil => fullResult(base, count, seen + res) + case TypeShape.Applied(base, _) :: Nil => fullResult(base, count, seen + res) + case TypeShape.Function(_, ret) :: Nil => fullResult(ret, count - 1, seen + res) + case _ => ErasedType.Unknown + val resultType = + if config.language.useNewResolution && td.sign.nonEmpty && (td.k is syntax.Fun) + && !td.flags.hasResultAnnotation && td.params.nonEmpty && !td.modulefulness.isModuleful + then S(fullResult(td.sign.get.typeInterpretation.get, td.params.length, Set.empty)) + else declaredType(resultSign, td.modulefulness) td.k match case syntax.Fun => val paramLists = sigShape match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 0cf0efe383..fa5572ca36 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -2170,6 +2170,9 @@ extends Importer: case TypeDef(Mod, st, N) => term(st, Tpe)(using newCtx) case st => term(st, Tpe)(using newCtx) + if newResolution && td.annotatedResultType.isEmpty then + s.foreach(registerParameterSignature(pss, _)) + val body: Opt[Term] = rhs match case N => N case _ if ctx.mode is Mode.Light => S(Term.Missing) @@ -2573,10 +2576,10 @@ extends Importer: case S(ext) => listenTerm(ext): esh => val sh = BaseShape(d, S(esh)) // TODO actually use applied shape providing generics? - sym.shapeListeners.foreach(_(sh)) + sym.shapeListeners.toList.foreach(_(sh)) case N => val sh = BaseShape(d, N) // TODO actually use applied shape providing generics? - sym.shapeListeners.foreach(_(sh)) + sym.shapeListeners.toList.foreach(_(sh)) case sym: InnerSymbol => // println(">>> "+sym) // TODO: patterns diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 46b855a395..abc1f2f2c3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -46,6 +46,8 @@ class NewResolver: val newShapes: mutable.Map[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = mutable.Map.empty val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty + private val declaredSymShapes = mutable.Map.empty[ + (BlockMemberSymbol, FlowSymbol, Ls[Marks], Map[VarSymbol, DeclaredType]), DeclaredSymShape] private val selfShapes: mutable.Map[InnerSymbol, BaseShape] = mutable.Map.empty // Tuple and record AST nodes for which we have subscribed to spread operands. // Each node owns its shapes and listeners. Comparing nodes by structural equality @@ -75,19 +77,28 @@ class NewResolver: def reject(shape: Shape): Unit = result.fail(msg"${shape.describe.capitalize} cannot be used as a type" -> shape.toLoc :: Nil) result.publish(TypeShape.Abstract) + def selectMember(member: BlockMemberSymbol): Unit = member.onComplete: () => + member.asTpe.orElse(member.asModOrObj) match + case S(symbol) => select(symbol) + case N => + result.fail(msg"${member.describe.capitalize} cannot be used as a type" -> member.toLoc :: Nil) + result.publish(TypeShape.Abstract) term match - case Capture(base, _) => typeResolution(base).listen(result.publish) - case TyApp(base, _) => typeResolution(base).listen(result.publish) + // Imported prelude declarations can still contain legacy references. + case Ref(symbol: TypeSymbol) => select(symbol) + case Ref(member: BlockMemberSymbol) => selectMember(member) + case Capture(base, thru) => result.publish(TypeShape.Captured(typeResolution(base), thru)) + case TyApp(base, args) => result.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) case Forall(_, _, body) => typeResolution(body).listen(result.publish) case CompType(left, right, union) => val l = typeResolution(left) val r = typeResolution(right) result.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) case DynTy() => result.publish(TypeShape.Dynamic) - case _: FunTy => result.publish(TypeShape.Function) + case FunTy(params, ret, _) => result.publish(TypeShape.Function(params, typeResolution(ret))) case UnitVal() => result.publish(TypeShape.Unit) case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => - result.publish(TypeShape.Abstract) + result.publish(TypeShape.Parameter(sym)) case _: WildcardTy | _: Neg | _: Rcd | _: Tup | _: Lit | Missing | Error() => result.publish(TypeShape.Abstract) case _ => listen(term, discardMarks = true): @@ -114,21 +125,158 @@ class NewResolver: registerSignature(rhs) case _ => () - /** Declared types provide instance members before there are any calls or assignments. - * Recursive aliases are followed once per subscription; their operands may resolve later. + /** Substitute a bound parameter before storing an argument. Otherwise recursive + * interfaces such as Tree[A] = Node[A] | Empty would accumulate equivalent + * A-to-A environments every time a recursive field is selected. + */ + private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType]): DeclaredType = + resolution.shapes.toList match + case TypeShape.Parameter(symbol) :: Nil if bindings.contains(symbol) => bindings(symbol) + case _ => DeclaredType(resolution, bindings) + + private class TypeValues extends Host[TermShape]: + def showDbg(using DebugPrinter): Str = "declared type values" + def publish(shape: TermShape): Unit = + // A callback can subscribe to this same host; listen replays the new shape. + // Snapshot the old listeners so that reentrant subscription is delivered once. + if shapes.add(shape) then shapeListeners.toList.foreach(_(shape)) + def listen(listener: TermShape => Unit): Unit = + shapeListeners += listener + shapes.toList.foreach(listener) + private val typeValues = mutable.Map.empty[DeclaredType, TypeValues] + private val abstractTypes = mutable.Map.empty[TypeResolution, DeclaredType] + // An omitted argument in a nominal annotation must not subscribe to the + // class parameter's constructor inference and silently refine that annotation. + private def abstractType(source: TypeResolution): DeclaredType = abstractTypes.getOrElseUpdate(source, { + val resolution = new TypeResolution(source.source, source.fail) + resolution.publish(TypeShape.Abstract) + declaredType(resolution, Map.empty) + }) + + /** An annotation is an abstraction boundary, even when its implementation is + * available. Subscribe to the type graph, never to values flowing into it. + * Shared hosts retain subscriptions for forward and recursive type references. */ def listenTypeValues(sign: Term)(listener: TermShape => Unit): Unit = - val visited = mutable.Set.empty[TypeResolution] - def follow(resolution: TypeResolution): Unit = if visited.add(resolution) then - resolution.listen: - case TypeShape.Dynamic => listener(DynShape()) - case TypeShape.Nominal(defn) => - listenExt(defn.ext, ext => listener(selfShapes.getOrElseUpdate(defn.sym, BaseShape(defn, ext)))) - case TypeShape.Alias(_, rhs) => rhs.foreach(follow) - case TypeShape.Union(left, right) => follow(left); follow(right) - case TypeShape.Intersection(left, right) => follow(left); follow(right) + listenTypeValues(declaredType(typeResolution(sign), Map.empty))(listener) + + private def listenTypeValues(tpe: DeclaredType)(listener: TermShape => Unit): Unit = + typeValues.get(tpe) match + case S(host) => host.listen(listener) + case N => + val host = new TypeValues + typeValues(tpe) = host + host.listen(listener) + def follow(current: DeclaredType, args: Ls[DeclaredType], aliases: Set[TypeResolution], publish: TermShape => Unit): Unit = + current.resolution.listen: shape => + def bind(params: Ls[TyParam]): Map[VarSymbol, DeclaredType] = + current.bindings ++ params.map(_.sym).zip(args.padTo(params.length, abstractType(current.resolution))) + def next(res: TypeResolution): Unit = follow(declaredType(res, current.bindings), Nil, aliases, publish) + shape match + case TypeShape.Dynamic => publish(DynShape()) + case TypeShape.Nominal(defn) => + val bindings = bind(defn.tparams) + defn.ext match + case N => publish(NominalTypeShape(defn, bindings, N)) + case S(parent) => + // Only the parent's declared type is relevant here. Evaluating + // its constructor arguments would reintroduce implementation flow. + listenTypeValues(declaredType(typeResolution(parent.cls), bindings)): ext => + publish(NominalTypeShape(defn, bindings, S(ext))) + case TypeShape.Alias(symbol, rhs) => + // Revisiting the same alias reference without reaching an outer + // nominal/function shape supplies no additional interface. Track + // references, not symbols: Id[Id[T]] has two distinct references. + // This also terminates aliases that recursively grow their arguments. + if aliases(current.resolution) then publish(OpaqueTypeShape(tpe.resolution.source)) + else rhs match + case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams)), Nil, + aliases + current.resolution, publish) + case N => publish(OpaqueTypeShape(tpe.resolution.source)) + case TypeShape.Applied(base, params) => + follow(declaredType(base, current.bindings), params.map(declaredType(_, current.bindings)), aliases, publish) + case TypeShape.Parameter(symbol) => current.bindings.get(symbol) match + case S(bound) => follow(bound, Nil, aliases, publish) + case N => + def receive(shape: Shape): Unit = shape match + case value: TermShape => publish(value) + case _ => softAssert(false, "A type parameter received a symbolic overload set") + symbol.shapeListeners += receive + symbol.shapes.foreach(receive) + case TypeShape.Captured(base, thru) => + follow(declaredType(base, current.bindings), args, aliases, + shape => publish(shape match + // Nominal/function interfaces are closed descriptions. Only + // parameter flow carries a lexical activation to transport. + case _: MarkedShape => MarkedShape.enter(shape, ResolutionBoundary(thru), N) + case _ => shape)) + case TypeShape.Function(params, ret) => + val ps = params match + case Tup(fields) => DeclaredParams(fields.collect: + case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current.bindings)) + , fields.exists(!_.isInstanceOf[Fld])) + case single => DeclaredParams(S(declaredType(typeResolution(single), current.bindings)) :: Nil, false) + publish(CallableTypeShape(tpe.resolution.source, ps :: Nil, + S(declaredType(ret, current.bindings)))) + case TypeShape.Union(left, right) => next(left); next(right) + case TypeShape.Intersection(left, right) => next(left); next(right) + case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) + follow(tpe, Nil, Set.empty, host.publish) + + // A separate full signature also annotates the implementation's parameters. + // Register these before its body is elaborated, so observed calls can never + // publish inferred alternatives for parameters behind this boundary. + private val signatureParameters = mutable.Map.empty[VarSymbol, DeclaredType] + def registerParameterSignature(paramLists: Ls[ParamList], sign: Term): Unit = + def bind(paramLists: Ls[ParamList], tpe: DeclaredType): Unit = paramLists match + case Nil => () + case ps :: tail => listenTypeValues(tpe): + case callable: CallableTypeShape => + val declared = callable.paramLists.head + if !declared.hasRest then ps.params.zip(declared.params).foreach: (param, sign) => + if param.sign.isEmpty then sign.foreach: tpe => + signatureParameters(param.sym) = tpe + listenTypeValues(tpe): shape => + if param.sym.shapes.add(shape) then param.sym.shapeListeners.foreach(_(shape)) + callable.result.foreach(bind(tail, _)) case _ => () - follow(typeResolution(sign)) + bind(paramLists, declaredType(typeResolution(sign), Map.empty)) + + /** Synthesized constructor fields retain their source declaration on the symbol. */ + private def resultSignature(td: TermDefinition): Opt[Term] = td.tsym.decl match + case S(p: Param) => p.sign + case _ => td.resultSignature + + private def listenDeclaredMember(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], flow: FlowSymbol, + source: Term, selected: DefinitionSymbol[?] => Unit)(listener: TermShape => Unit): Unit = + member.onComplete: () => + member.asModOrObj.orElse(member.asTrm).orElse(member.asCls) match + case S(symbol: TermSymbol) if !symbol.isInstanceOf[ClassCtorSymbol] => + selected(symbol) + val td = symbol.defn.get + def publish(shape: TermShape): Unit = + // A synthesized field's signature is in the constructor parameter's + // scope. Written member signatures are in their own definition scope. + val exited = td.tsym.decl match + case S(_: Param) => shape + case _ => MarkedShape.exit(shape, ResolutionBoundary(td.tsym), S(flow)) + exited match + case value: TermShape => listener(value) + case NoShape => () + val result = resultSignature(td).map(sign => declaredType(typeResolution(sign), bindings)) + if td.sign.nonEmpty && (td.k is syntax.Fun) && !td.flags.hasResultAnnotation then + listenTypeValues(declaredType(typeResolution(td.sign.get), bindings))(publish) + else if td.params.nonEmpty then + publish(CallableTypeShape(source, + td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(sign => + declaredType(typeResolution(sign), bindings))), ps.restParam.nonEmpty)), result)) + else result match + case S(tpe) => listenTypeValues(tpe)(publish) + case N => publish(UnknownValueShape(source)) + case _ => + // A nested nominal declaration denotes its statically selected symbol; + // selecting it does not inspect an instance field or method body. + fromBMS(member, flow, Nil, listener, source, selected) def isOwnedSym(sym: Symbol): Bool = sym.getState is state @@ -136,79 +284,158 @@ class NewResolver: def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])]): Unit = raise: ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) + /** Explicit and inferred type arguments use the same entry/exit paths as term + * arguments. An explicit argument fixes that instantiation; its value argument + * must not add a more precise implementation shape to the declared interface. + */ + private val explicitTypeArguments = mutable.Set.empty[(VarSymbol, Ls[Marks])] + private def publishTypeArgument(symbol: VarSymbol, shape: TermShape | NoShape): Unit = shape match + case value: TermShape if isOwnedSym(symbol) => + if symbol.shapes.add(value) then symbol.shapeListeners.toList.foreach(_(value)) + case _ => () + + private def applyTypeArguments(defn: DefnShape, marks: Ls[Marks], args: Ls[Term], source: Term): Unit = + val params = defn.clsDef match + case S(cls) => cls.tparams.map(_.sym) + case N => defn.defn match + case td: TermDefinition => td.tparams.toList.flatten.map(_.sym) + case _ => Nil + if params.length != args.length then + resolError(source, msg"${defn.describe.capitalize} expected ${params.length} type ${ + "argument".pluralized(params.length)}, but got ${args.length}" -> defn.toLoc :: Nil) + params.zip(args).foreach: (param, arg) => + explicitTypeArguments += ((param, marks)) + listenTypeValues(arg): tpe => + publishTypeArgument(param, tpe.enter(marks)) + + private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks], + seen: Set[TypeResolution]): Unit = + def follow(tpe: DeclaredType, args: Ls[DeclaredType], captures: Ls[Marks], + seen: Set[TypeResolution]): Unit = if !seen(tpe.resolution) then + val next = seen + tpe.resolution + tpe.resolution.listen: + case TypeShape.Parameter(symbol) => tpe.bindings.get(symbol) match + case S(bound) => follow(bound, Nil, captures, next) + case N if !explicitTypeArguments((symbol, marks)) => publishTypeArgument(symbol, value.enter(captures)) + case _ => () + case TypeShape.Captured(base, thru) => + follow(declaredType(base, tpe.bindings), args, + EntryMark(ResolutionBoundary(thru), N, NoMarks) :: captures, next) + case TypeShape.Applied(base, params) => + follow(declaredType(base, tpe.bindings), params.map(declaredType(_, tpe.bindings)), captures, next) + case TypeShape.Alias(symbol, S(rhs)) => + val bindings = tpe.bindings ++ symbol.defn.get.tparams.map(_.sym).zip(args) + follow(declaredType(rhs, bindings), Nil, captures, next) + case TypeShape.Nominal(cls) if args.nonEmpty => + val (head, context) = value.applicationHead + def constrain(param: TyParam, pattern: DeclaredType): Unit = + def receive(shape: Shape): Unit = shape match + case shape: TermShape => shape.exit(context) match + case actual: TermShape => inferTypeArguments(pattern, actual, marks, next) + case NoShape => () + case _ => softAssert(false, "A type parameter received a symbolic overload set") + param.sym.shapeListeners += receive + param.sym.shapes.foreach(receive) + head match + case ds: DefnShape if ds.clsDef.contains(cls) => cls.tparams.zip(args).foreach(constrain) + case nominal: NominalTypeShape if nominal.defn is cls => + cls.tparams.zip(args).foreach: (param, pattern) => + nominal.bindings.get(param.sym).foreach: bound => + listenTypeValues(bound): shape => + shape.exit(context) match + case actual: TermShape => inferTypeArguments(pattern, actual, marks, next) + case NoShape => () + case _ => () + case _ => () // Concrete annotations are opaque, irrespective of the argument value. + follow(tpe, Nil, Nil, seen) + + private def listenSignatureResult(tpe: DeclaredType, count: Int)(listener: TermShape => Unit): Unit = + if count == 0 then listenTypeValues(tpe)(listener) + else listenTypeValues(tpe): + case Marked(callable: CallableTypeShape, _) => + callable.result.foreach(listenSignatureResult(_, count - 1)(listener)) + case _ => () + /** Subscribe once to complete argument-tuple candidates. A pending spread is not * an argument, nor evidence of an arity mismatch; each resolved combination is. */ def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Term, src: Term, funSh: TermShape): Unit = val expectedCount = ps.length - val reportedCounts = mutable.Set.empty[Int] def publish(p: Param, shape: TermShape | NoShape): Unit = shape match case NoShape => () case sh: TermShape => - if isOwnedSym(p.sym) && p.sym.shapes.add(sh) then p.sym.shapeListeners.foreach(_(sh)) + val sign = p.sign.map(sign => declaredType(typeResolution(sign), Map.empty)).orElse(signatureParameters.get(p.sym)) + sign match + case S(tpe) => inferTypeArguments(tpe, sh, mss, Set.empty) + case N => publishTypeArgument(p.sym, sh) def matchSegments(tuple: TupleShape, marks: Marks): Unit = val segments = tuple.segments val knownCount = segments.count(_.isInstanceOf[TupleShape.Field]) - val unknownLength = knownCount != segments.length - val arityMismatch = (!unknownLength && knownCount < expectedCount) || (r.isEmpty && knownCount > expectedCount) - if arityMismatch && reportedCounts.add(knownCount) then - val count = if unknownLength then msg"at least ${knownCount}" else msg"${knownCount}" - resolError(src, - msg"${funSh.describe.capitalize} expected ${expectedCount} ${ - "argument".pluralized(expectedCount)}, but got ${count}" -> funSh.toLoc :: Nil) - // Distribute only over successful argument counts. With no rest parameter, - // the total arity anchors known suffix fields even after an unknown spread. - // With a rest parameter, positions after an unknown spread have no upper - // bound; consider every remaining fixed position and retain every rest tail. - if !arityMismatch then - val extra = (expectedCount - knownCount).max(0) - def assign(segment: TupleShape.Segment, positions: Range): Unit = - positions.foreach: index => - val p = ps(index) - if p.sign.isEmpty then segment match - case TupleShape.Field(field, inner) => listenTerm(field.term): sh => - publish(p, sh.exit(inner).exit(marks).enter(mss)) - case TupleShape.Unknown(_, inner, value) => - publish(p, value.exit(inner).exit(marks).enter(mss)) - def loop(rest: Ls[TupleShape.Segment], before: Int, unknownBefore: Bool): Unit = rest match - case Nil => () - case (field: TupleShape.Field) :: tail => - val unknownAfter = tail.exists(_.isInstanceOf[TupleShape.Unknown]) - // With no later unknown segment, preceding spreads must supply all - // missing fixed arguments, even when the function has a rest parameter. - val first = if unknownBefore && !unknownAfter then before + extra else before - val last = if !unknownBefore then before - else if r.nonEmpty then expectedCount - 1 else before + extra - assign(field, first.max(0) until (last + 1).min(expectedCount)) - loop(tail, before + 1, unknownBefore) - case (unknown: TupleShape.Unknown) :: tail => - val end = if r.nonEmpty then expectedCount else before + extra - assign(unknown, before until end) - loop(tail, before, true) - loop(segments, 0, false) - r.foreach: p => - // If consumption reaches an unknown segment, some subsequent fields - // may have been consumed too. Approximate that optional prefix with an - // unknown segment, retaining the suffix that must remain. Publishing - // separate concrete tails here would turn uncertainty into false arity - // failures when the rest tuple is spread into another call. - def drop(xs: Ls[TupleShape.Segment], count: Int, approximate: Bool): Ls[TupleShape.Segment] = - if count == 0 then xs - else xs match - case Nil => Nil - case (_: TupleShape.Field) :: rest => drop(rest, count - 1, approximate) - case (unknown: TupleShape.Unknown) :: Nil => - // Consuming a prefix changes the length, but not the element - // shape, when there are no following fields to merge into it. - if approximate then unknown :: Nil else Nil - case (_: TupleShape.Unknown) :: rest => - val suffix = drop(rest, count, false) - if approximate then TupleShape.Unknown(tuple.source, Nil, UnknownValueShape(tuple.source)) :: suffix else suffix - val remaining = drop(segments, expectedCount, true) - val rest = if ps.isEmpty then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil) - publish(p, rest.exit(marks).enter(mss)) + // Distribute only successful argument counts. Unknown spreads retain all + // possible fixed positions and the residual tail for a rest parameter. + val extra = (expectedCount - knownCount).max(0) + def assign(segment: TupleShape.Segment, positions: Range): Unit = + positions.foreach: index => + val p = ps(index) + segment match + case TupleShape.Field(field, inner) => listenTerm(field.term): sh => + publish(p, sh.exit(inner).exit(marks).enter(mss)) + case TupleShape.Unknown(_, inner, value) => + publish(p, value.exit(inner).exit(marks).enter(mss)) + def loop(rest: Ls[TupleShape.Segment], before: Int, unknownBefore: Bool): Unit = rest match + case Nil => () + case (field: TupleShape.Field) :: tail => + val unknownAfter = tail.exists(_.isInstanceOf[TupleShape.Unknown]) + // With no later unknown segment, preceding spreads must supply all + // missing fixed arguments, even when the function has a rest parameter. + val first = if unknownBefore && !unknownAfter then before + extra else before + val last = if !unknownBefore then before + else if r.nonEmpty then expectedCount - 1 else before + extra + assign(field, first.max(0) until (last + 1).min(expectedCount)) + loop(tail, before + 1, unknownBefore) + case (unknown: TupleShape.Unknown) :: tail => + val end = if r.nonEmpty then expectedCount else before + extra + assign(unknown, before until end) + loop(tail, before, true) + loop(segments, 0, false) + r.foreach: p => + // If consumption reaches an unknown segment, some subsequent fields + // may have been consumed too. Approximate that optional prefix with an + // unknown segment, retaining the suffix that must remain. Publishing + // separate concrete tails here would turn uncertainty into false arity + // failures when the rest tuple is spread into another call. + def drop(xs: Ls[TupleShape.Segment], count: Int, approximate: Bool): Ls[TupleShape.Segment] = + if count == 0 then xs + else xs match + case Nil => Nil + case (_: TupleShape.Field) :: rest => drop(rest, count - 1, approximate) + case (unknown: TupleShape.Unknown) :: Nil => + // Consuming a prefix changes the length, but not the element + // shape, when there are no following fields to merge into it. + if approximate then unknown :: Nil else Nil + case (_: TupleShape.Unknown) :: rest => + val suffix = drop(rest, count, false) + if approximate then TupleShape.Unknown(tuple.source, Nil, UnknownValueShape(tuple.source)) :: suffix else suffix + val remaining = drop(segments, expectedCount, true) + val rest = if ps.isEmpty then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil) + publish(p, rest.exit(marks).enter(mss)) + checkArgumentArity(args, expectedCount, r.nonEmpty, src, funSh): + case (tuple, marks) => matchSegments(tuple, marks) + + private def checkArgumentArity(args: Term, expected: Int, rest: Bool, src: Term, + callable: TermShape)(matched: ((TupleShape, Marks)) => Unit): Unit = + val reportedCounts = mutable.Set.empty[Int] listenTerm(args): - case Marked(tuple: TupleShape, marks) => matchSegments(tuple, marks) + case Marked(tuple: TupleShape, marks) => + val known = tuple.segments.count(_.isInstanceOf[TupleShape.Field]) + val unknown = known != tuple.segments.length + val mismatch = (!unknown && known < expected) || (!rest && known > expected) + if mismatch then + if reportedCounts.add(known) then + val count = if unknown then msg"at least ${known}" else msg"${known}" + resolError(src, msg"${callable.describe.capitalize} expected ${expected} ${ + "argument".pluralized(expected)}, but got ${count}" -> callable.toLoc :: Nil) + else matched((tuple, marks)) case _ => resolError(src, msg"Expected an argument tuple." -> args.toLoc :: Nil) /** Resolve every constructor pattern through the same symbolic interpretation. @@ -301,7 +528,10 @@ class NewResolver: pattern match case al @ Pattern.Alias(pat, _) => matchShapePat(shape, pat): sh => - if al.symbol.shapes.add(sh) then al.symbol.shapeListeners.foreach(_(sh)) + // Rejected duplicate/negated bindings have no allocated symbol. Pattern + // validation already reports them; only valid bindings receive flow. + al.symbolOption.foreach: symbol => + if symbol.shapes.add(sh) then symbol.shapeListeners.toList.foreach(_(sh)) matched(sh) case Pattern.Wildcard() | Pattern.Literal(_) => matched(shape) case Pattern.Chain(left, right) => @@ -322,6 +552,11 @@ class NewResolver: case MemberLookup.Found(sym: BlockMemberSymbol, marks) => val field = symShapes.getOrElseUpdate((sym, resSym, marks), SymShape(sym, resSym, marks)) matchShapePat(field, pat)(_ => ()) + case MemberLookup.Declared(member, bindings, marks) => + listenDeclaredMember(member, bindings, resSym, ctor.target, _ => ()): field => + field.exit(marks) match + case value: TermShape => matchShapePat(value, pat)(_ => ()) + case NoShape => () case _ => // classPattern only publishes fields present in cls.body. // The nominal test above admits only that class's instances, @@ -335,7 +570,7 @@ class NewResolver: matched(sh) shape match case sh: TermShape => check(sh) - case sh: SymShape => fromBMS(sh.sym, sh.resSym, sh.markss, check, ctor.target, _ => ()) + case sh: SymShape => fromSymbol(sh, check, ctor.target, _ => ()) ctor.shapeListeners += listenConstructor ctor.shapes.foreach(listenConstructor) case Pattern.Tuple(_, _) => () // Tuple binding shapes are not inferred yet. @@ -352,6 +587,21 @@ class NewResolver: if res.shapes.add(lhs) then res.shapeListeners.foreach(_(lhs)) return case _ => () + lhs match + case Marked(callable: CallableTypeShape, context) => + val ps = callable.paramLists.head + checkArgumentArity(args, ps.params.length, ps.hasRest, res, lhs)(_ => ()) + def publish(shape: TermShape): Unit = shape.exit(context) match + case value: TermShape => + if res.shapes.add(value) then res.shapeListeners.foreach(_(value)) + case NoShape => () + callable.paramLists.tail match + case Nil => callable.result match + case S(tpe) => listenTypeValues(tpe)(publish) + case N => publish(UnknownValueShape(callable.source)) + case tail => publish(callable.copy(paramLists = tail)) + return + case _ => () // log(s"appShape? lhs = $lhs, args = $args, res = $res") val sh = appShapes.getOrElseUpdate((lhs, res.resSym), { log(s"appShape: lhs = ${lhs.shwDbg}, args = ${args.showDbg}, res = ${res.showDbg}") @@ -397,8 +647,15 @@ class NewResolver: register case _ => log(s"appShape: td.body = ${td.body}") - td.body.foreach: body => - go(body, mss) + td.sign match + case S(sign) => + val count = if td.flags.hasResultAnnotation then 0 else td.params.length + listenSignatureResult(declaredType(typeResolution(sign), Map.empty), count): result => + result.exit(mss) match + case value: TermShape => + if res.shapes.add(value) then res.shapeListeners.foreach(_(value)) + case NoShape => () + case N => td.body.foreach(go(_, mss)) case _ => softAssert(res.isErroneous) case (sh: IntroShape, mss) => @@ -428,6 +685,12 @@ class NewResolver: if !host.resolvedTargets.contains(field.tsym) then host.resolvedTargets ::= field.tsym publish(UnknownValueShape(field.rhs)) + private def publishDeclared(host: NewResolvable & ShapeHost, member: BlockMemberSymbol, flow: FlowSymbol, + bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks]): Unit = + val shape = declaredSymShapes.getOrElseUpdate((member, flow, marks, bindings), + DeclaredSymShape(member, flow, marks, bindings)) + if host.shapes.add(shape) then host.shapeListeners.toList.foreach(_(shape)) + private def publishDynamic(host: NewResolvable & ShapeHost, marks: Ls[Marks]): Unit = DynShape().exit(marks) match case shape: TermShape => @@ -451,6 +714,10 @@ class NewResolver: val candidate = prefix -> member.memberSymbol if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate publishMember(ref, member, ref.resSym, marks) + case MemberLookup.Declared(member, bindings, marks) => + val candidate = prefix -> member + if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate + publishDeclared(ref, member, ref.resSym, bindings, marks) case MemberLookup.Dynamic(marks) => if !ref.dynamicPrefixes.contains(prefix) then ref.dynamicPrefixes ::= prefix publishDynamic(ref, marks) @@ -493,7 +760,7 @@ class NewResolver: case sh: SymShape => completedClass(sh)(cls => select(cls, ExitMark(ResolutionBoundary(cls.sym), S(sh.resSym), NoMarks) :: sh.markss), - () => fromBMS(sh.sym, sh.resSym, sh.markss, value, trm, _ => ())) + () => fromSymbol(sh, value, trm, _ => ())) case sh: TermShape => value(sh) def newSel(sel: NewSel): Unit = @@ -503,6 +770,9 @@ class NewResolver: log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") if !sel.resolvedMembers.contains(bms.memberSymbol) then sel.resolvedMembers ::= bms.memberSymbol publishMember(sel, bms, sel.resSym, marks) + case MemberLookup.Declared(member, bindings, marks) => + if !sel.resolvedMembers.contains(member) then sel.resolvedMembers ::= member + publishDeclared(sel, member, sel.resSym, bindings, marks) case MemberLookup.Dynamic(marks) => sel.hasDynamicTarget = true publishDynamic(sel, marks) @@ -519,8 +789,29 @@ class NewResolver: val candidate = cd.sym -> marks if !sel.resolvedClasses.contains(candidate) then sel.resolvedClasses ::= candidate listenExt(cd.ext, ext => - member(DefnShape(cd, ext).getInstanceMember(sel.id.name).withMarks(marks), - msg"Class '${cd.sym.nme}'", cd.toLoc)) + val info = DefnShape(cd, ext).getInstanceMember(sel.id.name).withMarks(marks) + info match + case MemberLookup.Found(bms: BlockMemberSymbol, _) => + // Resolve the projection target even in an unused function. Wait + // for the receiver before deciding whether its result may expose + // implementation flow or only the declared member signature. + if !sel.resolvedMembers.contains(bms) then sel.resolvedMembers ::= bms + bms.onComplete: () => + bms.asModOrObj.orElse(bms.asTrm).orElse(bms.asCls).foreach: sym => + if !sel.resolvedTargets.contains(sym) then sel.resolvedTargets ::= sym + listenTerm(sel.prefix): receiver => + val selected = receiver match + case Marked(nominal: NominalTypeShape, context) => + nominal.ancestor(cd) match + case S(view) => view.getMember(sel.id.name).withMarks(context :: Nil) + case N => + // An explicit projection can name a narrower class, but + // it cannot recover that class's implementation arguments. + val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) + MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil) + case _ => info + member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc) + case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc)) , sh => sel.isErroneous = true resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc :: Nil) @@ -564,6 +855,11 @@ class NewResolver: listenExt(cd.ext, extsh => val dsh = constructorShape(cd, extsh) val sh = newShapes.getOrElseUpdate((cd.sym, marks, nw.resSym), { + def typeArgs(term: Term): Opt[Ls[Term]] = term match + case TyApp(_, args) => S(args) + case Capture(base, _) => typeArgs(base) + case _ => N + typeArgs(nw.cls).foreach(applyTypeArguments(dsh, marks, _, nw)) dsh.unappliedParams.lazyZip(nw.args).foreach: case ((ps, _), args) => zipArgs(marks, ps.params, ps.restParam, args, nw, dsh) NewShape(dsh, cd.sym, marks, nw.args, nw) @@ -617,6 +913,15 @@ class NewResolver: case N => listener(N) + private def fromSymbol(shape: SymShape, listener: TermShape => Unit, source: Term, + selected: DefinitionSymbol[?] => Unit): Unit = shape match + case declared: DeclaredSymShape => + listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, selected): value => + value.exit(shape.markss) match + case value: TermShape => listener(value) + case NoShape => () + case _ => fromBMS(shape.sym, shape.resSym, shape.markss, listener, source, selected) + def fromBMS(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: TermShape => Unit, trm: Term, selected: DefinitionSymbol[?] => Unit) = log(s"listenBMS: bms = ${bms.describe}") @@ -648,13 +953,12 @@ class NewResolver: sym.defn match case S(td: TermDefinition) if td.params.isEmpty => log(s"listenTerm: td.body = ${td.body.fold("N")(_.showDbg)}") - td.body match - case S(body) => - listenTerm(body)(wrappedListener) - case N => - // A declared value has a selected definition but no implementation - // from which to infer a value shape (e.g. an external mutable field). - () + resultSignature(td) match + case S(sign) => listenTypeValues(sign): shape => + td.tsym.decl match + case S(_: Param) => wrappedListener(MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N)) + case _ => wrappedListener(shape) + case N => td.body.foreach(listenTerm(_)(wrappedListener)) case S(d: TermDefinition) => d.tsym match case ccs: ClassCtorSymbol => @@ -731,7 +1035,7 @@ class NewResolver: case sh: TermShape => listener(sh) case ss: SymShape => - fromBMS(ss.sym, ss.resSym, ss.markss, listener, trm, sym => + fromSymbol(ss, listener, trm, sym => trm.withoutCaptures match case ref: NewResolvable => if !ref.resolvedTargets.contains(sym) then ref.resolvedTargets ::= sym @@ -763,7 +1067,15 @@ class NewResolver: lastWords("Synthetic selections must not enter new resolution") case Asc(_, sign) => listenTypeValues(sign)(listener) case _: DynSel | _: DynNew => listener(DynShape()) - case TyApp(underlying, _) => listen(underlying, discardMarks)(listener) + case TyApp(underlying, args) => + listen(underlying, discardMarks): shape => + def instantiate(value: TermShape): Unit = value.applicationHead match + case (ds: DefnShape, marks) => applyTypeArguments(ds, marks, args, trm) + case _ => () + shape match + case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => ()) + case value: TermShape => instantiate(value) + listener(shape) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => listenAggregate(tuple, listener): publish => def expand(elems: Ls[Elem], reversed: Ls[TupleShape.Element]): Unit = elems match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 211d1c9ab4..d89068112a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -24,6 +24,9 @@ sealed trait Shape extends ShapeLike: case us: UnknownValueShape => s"UnknownValueShape(${us.source.showDbg})" case ts: TupleShape => s"TupleShape(${ts.source.showDbg})" case rs: RecordShape => s"RecordShape(${rs.source.showDbg})" + case ts: NominalTypeShape => s"NominalTypeShape(${ts.defn.sym.showDbg})" + case ts: OpaqueTypeShape => s"OpaqueTypeShape(${ts.source.showDbg})" + case ts: CallableTypeShape => s"CallableTypeShape(${ts.source.showDbg})" case bs: BaseShape => s"BaseShape(${bs.defn.sym.showDbg})" case es: ErrShape => es.describe @@ -256,12 +259,16 @@ end TermShape * searching wildcard opens, since it can introduce an additional candidate. */ enum MemberLookup: case Found(member: BlockMemberSymbol | RecordMember, marks: Ls[Marks]) + // Declared members expose signatures only. Their marks transport dependent + // type arguments; they never authorize reading an implementation's value flow. + case Declared(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks]) case Dynamic(marks: Ls[Marks]) case Missing case Unknown(reason: MemberLookup.Uncertainty, loc: Opt[Loc]) def withMarks(marks: Ls[Marks]): MemberLookup = this match case Found(member, inner) => Found(member, inner ::: marks) + case Declared(member, bindings, inner) => Declared(member, bindings, inner ::: marks) case Dynamic(inner) => Dynamic(inner ::: marks) case _ => this @@ -326,6 +333,12 @@ class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: L def enter(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = ??? def enter(marks: Marks)(using TL): TermShape | NoShape = ??? +/** Keep a declared receiver's boundary while consumers choose between the term, + * type, and constructor interpretations of the same overload set. + */ +final class DeclaredSymShape(symbol: BlockMemberSymbol, site: FlowSymbol, marks: Ls[Marks], + val bindings: Map[VarSymbol, DeclaredType]) extends SymShape(symbol, site, marks) + /* class ThisShape(val defn: Definition) extends NonAppTermShape: def describe: Str = s"Self-reference to ${defn.bsym.describe} '${defn.bsym.nme}'" @@ -342,6 +355,45 @@ class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppT MemberLookup.inClass(defn, ext, name) def toLoc: Opt[Loc] = defn.toLoc +/** A nominal annotation exposes only declarations, including inherited declarations. + * In particular, selecting an unannotated field does not inspect its initializer. + */ +final case class NominalTypeShape(defn: ClassLikeDef, bindings: Map[VarSymbol, DeclaredType], + parent: Opt[TermShape]) extends NonAppTermShape: + def describe: Str = s"value of type '${defn.sym.nme}'" + def toLoc: Opt[Loc] = defn.toLoc + override def isInstanceOfClass(cls: ClassLikeDef): Bool = + (defn is cls) || parent.exists(_.isInstanceOfClass(cls)) + def ancestor(cls: ClassLikeDef): Opt[NominalTypeShape] = + if defn is cls then S(this) + else parent.flatMap: + case Marked(parent: NominalTypeShape, _) => parent.ancestor(cls) + case _ => N + protected def getMemberImpl(name: Str): MemberLookup = + defn.body.members.get(name) match + case S(member) => MemberLookup.Declared(member, bindings, Nil) + case N => parent.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)) + +/** Parameter types and arity exposed by one list in a declared calling interface. */ +final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool) + +/** Only the calling interface is visible through an annotation. No arguments are + * propagated into implementation parameters, and the result comes from its type. + */ +final case class CallableTypeShape(source: Term, paramLists: Ls[DeclaredParams], + result: Opt[DeclaredType]) extends NonAppTermShape: + require(paramLists.nonEmpty) + def describe: Str = "function with a declared signature" + def toLoc: Opt[Loc] = source.toLoc + protected def getMemberImpl(name: Str): MemberLookup = MemberLookup.Missing + +/** An abstract annotation authorizes no operations based on the implementation. */ +final case class OpaqueTypeShape(source: Term) extends NonAppTermShape: + def describe: Str = "value of abstract type" + def toLoc: Opt[Loc] = source.toLoc + protected def getMemberImpl(name: Str): MemberLookup = + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, toLoc) + class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTermShape: /** Instance lookup is shared by constructor calls and explicit `new`. * Inherited members retain their marks before the caller adds its captures. */ @@ -491,7 +543,7 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] case Missing => loop(rest) case Found(member: RecordMember, inner) => Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil) - case Found(_: BlockMemberSymbol, _) => + case Found(_: BlockMemberSymbol, _) | Declared(_, _, _) => // Record spreads recursively look up RecordShapes, which only create RecordMembers. lastWords("Record lookup returned a nominal member") loop(elements.reverse) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 33b48721b6..dfe3cafcb0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -1379,6 +1379,17 @@ final case class TermDefinition( require(k is tsym.k) def bsym: BlockMemberSymbol = sym val owner = tsym.owner + /** A result annotation denotes the whole result; a separate function signature + * includes the arrows corresponding to the definition's written parameter lists. + */ + def resultSignature: Opt[Term] = + def strip(sign: Term, count: Int): Term = (sign, count) match + case (Term.Forall(_, _, body), _) => strip(body, count) + case (Term.FunTy(_, rhs, _), n) if n > 0 => strip(rhs, n - 1) + case _ => sign + sign.map: sign => + if (k is syntax.Fun) && !flags.hasResultAnnotation then strip(sign, params.length) + else sign def visibility: Visibility = annotations .collectFirst: case Annot.Modifier(Keyword.`private`) => Visibility.Private diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 5b09f4fde7..688bf68b9b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -12,7 +12,10 @@ enum TypeShape: case Alias(symbol: TypeAliasSymbol, rhs: Opt[TypeResolution]) case Union(left: TypeResolution, right: TypeResolution) case Intersection(left: TypeResolution, right: TypeResolution) - case Function + case Function(params: Term, result: TypeResolution) + case Applied(base: TypeResolution, args: Ls[TypeResolution]) + case Parameter(symbol: VarSymbol) + case Captured(base: TypeResolution, thru: AnyDefinitionSymbol) case Unit case Dynamic case Abstract @@ -25,10 +28,10 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => def hasErrors: Bool = reported def showDbg(using DebugPrinter): Str = s"type of ${source.showDbg}" def publish(shape: TypeShape): Unit = - if shapes.add(shape) then shapeListeners.foreach(_(shape)) + if shapes.add(shape) then shapeListeners.toList.foreach(_(shape)) def listen(listener: TypeShape => Unit): Unit = shapeListeners += listener - shapes.foreach(listener) + shapes.toList.foreach(listener) def fail(messages: Ls[(Message, Opt[Loc])]): Unit = if !reported then reported = true report(messages) @@ -52,6 +55,14 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => fail(msg"This type is ambiguous, as it has multiple resolved targets" -> source.toLoc :: candidates) shapes.foreach: case Alias(_, rhs) => rhs.foreach(_.validate(next)) + case Captured(base, _) => base.validate(next) + case Applied(base, args) => base.validate(next); args.foreach(_.validate(next)) + case Function(_, result) => result.validate(next) case Union(left, right) => left.validate(next); right.validate(next) case Intersection(left, right) => left.validate(next); right.validate(next) case _ => () + +/** A type with its lexical type-parameter bindings. These bindings describe declared + * interfaces, not constructor arguments or value-flow capture paths. + */ +final case class DeclaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType]) diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls new file mode 100644 index 0000000000..d437924b87 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -0,0 +1,378 @@ +:. + +// No calls are needed to resolve an annotated receiver's members. +class Box(val value) +fun unused(box: Box) = box.value + +// Reading an unannotated field crosses no constructor argument-flow boundary. +:expect 42 +class Box(val value) +fun read(box: Box) = box.value +read(Box(42)) +//│ = 42 + +// An annotation hides subclass-only members, even at an observed call. +:ge +class Base +class Child extends Base with + val extra = 42 +fun hidden(base: Base) = base.extra +hidden(new Child) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.19: fun hidden(base: Base) = base.extra +//│ ║ ^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// The declared interface selects the method, while dispatch uses the actual receiver. +:expect 42 +class Base with + fun value(): Int = 0 +class Child extends Base with + fun value(): Int = 42 +fun read(base: Base) = base.value() +read(new Child) +//│ = 42 + +// A declared result is opaque even when its body constructs a known subclass. +:ge +class Base +class Child extends Base with + val extra = 42 +fun make(): Base = new Child +make().extra +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.43: make().extra +//│ ║ ^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Likewise for a getter/value annotation and for explicit ascription. +:ge +class Base +class Child extends Base with + val extra = 42 +val box: Base = new Child +box.extra +(new Child as Base).extra +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.57: (new Child as Base).extra +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.56: box.extra +//│ ║ ^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ box = Child { extra: 42 } + +// An unannotated field cannot reveal its initializer through an annotated receiver. +:ge +class Item with + val extra = 42 +class Box(val item) +fun read(box: Box) = box.item.extra +read(Box(new Item)) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.74: fun read(box: Box) = box.item.extra +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'extra' of a value with unknown shape. +//│ ║ l.74: fun read(box: Box) = box.item.extra +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// An unannotated method result cannot reveal the method body either. +:ge +class Item with + val extra = 42 +class Box with + fun item() = new Item +fun read(box: Box) = box.item().extra +read(new Box) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.90: fun read(box: Box) = box.item().extra +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'extra' of a value with unknown shape. +//│ ║ l.90: fun read(box: Box) = box.item().extra +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Inherited declared fields retain their type through curried constructors. +:expect 42 +class Item(val value: Int) +class Parent(val item: Item)(val ignored) +class Child(input)(ignored) extends Parent(input)(ignored) +fun read(child: Child) = child.item.value +read(Child(Item(42))(0)) +//│ = 42 + +// Nested captures do not bring implementation argument marks through the annotation. +:expect 42 +class Item(val value: Int) +class Box(val item: Item) +fun enclosing(box: Box) = + fun inner() = box.item.value + inner() +enclosing(Box(Item(42))) +//│ = 42 + +// Generic aliases and inherited interfaces substitute declared type arguments. +:expect 42 +class Item(val value: Int) +class Box[T](val item: T) +type Wrapped[T] = Box[T] +class Child[T](item: T) extends Box[T](item) +fun read(box: Wrapped[Item]) = box.item.value +fun inherited(box: Child[Item]) = box.item.value +read(Box[Item](Item(20))) + inherited(Child[Item](Item(22))) +//│ = 42 + +// Function type annotations supply the result interface without inspecting a lambda. +:expect 42 +class Item(val value: Int) +type Factory[T] = () -> T +fun read(factory: Factory[Item]) = factory().value +read(() => Item(42)) +//│ = 42 + +// Function type arity is checked even when the annotated function is unused. +:ge +fun unused(factory: () -> Int) = factory(1) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.140: fun unused(factory: () -> Int) = factory(1) +//│ ║ ^^^^^^^^^^ +//│ ╟── Function with a declared signature expected 0 arguments, but got 1 +//│ ║ l.140: fun unused(factory: () -> Int) = factory(1) +//│ ╙── ^^ + +// A callable annotation also hides extra members of the concrete result. +:ge +class Base +class Child extends Base with + val extra = 42 +fun read(factory: () -> Base) = factory().extra +read(() => new Child) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.153: fun read(factory: () -> Base) = factory().extra +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Separate signatures annotate implementation parameters before any observed call. +:ge +class Base +class Child extends Base with + val extra = 42 +fun hidden: (Base) -> Int +fun hidden(base) = base.extra +hidden(new Child) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.167: fun hidden(base) = base.extra +//│ ║ ^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Separate signatures supply an opaque result as well. +:ge +class Base +class Child extends Base with + val extra = 42 +fun make: () -> Base +fun make() = new Child +make().extra +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.182: make().extra +//│ ║ ^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Alias substitution keeps nested arguments in the scope where they were written. +:expect 42 +class Item(val value: Int) +type Identity[T] = T +type Twice[T] = Identity[Identity[T]] +fun read(item: Twice[Item]) = item.value +read(Item(42)) +//│ = 42 + +// Recursive nominal signatures unfold only when a member is selected. +class Node(val next: Link) +type Link = Node +fun unused(node: Link) = node.next.next + +// An unproductive alias must not loop, even when each step grows its arguments. +:ge +class Box[T] +type Loop[T] = Loop[Box[T]] +fun unused(value: Loop[Int]) = value.field +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.207: fun unused(value: Loop[Int]) = value.field +//│ ║ ^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'field' of a value with unknown shape. +//│ ║ l.207: fun unused(value: Loop[Int]) = value.field +//│ ╙── ^^^^^^^^^ + +// Type arguments provide callable shapes through the generic parameter. +:expect 42 +fun invoke[T](value: T) = value() +invoke[() -> Int](() => 42) +//│ = 42 + +// A class-qualified selection still respects an annotated receiver's field boundary. +:ge +class Item with + val extra = 42 +class Box(val item) +fun read(box: Box) = box.Box#item.extra +read(Box(new Item)) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.226: fun read(box: Box) = box.Box#item.extra +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'extra' of a value with unknown shape. +//│ ║ l.226: fun read(box: Box) = box.Box#item.extra +//│ ╙── ^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A signature alias supplies the interface to both the body and callers. +:expect 42 +class Item(val value: Int) +type Reader = (Item) -> Int +fun read: Reader +fun read(item) = item.value +read(Item(42)) +//│ = 42 + +// Explicit and inferred type arguments share parameter flow, with distinct call marks. +:expect [42, 7, 42, 7] +class Item(val value: Int) +class Other(val different: Int) +fun identity[T](x: T): T = x +[identity[Item](Item(42)).value, identity[Other](Other(7)).different, + identity(Item(42)).value, identity(Other(7)).different] +//│ = [42, 7, 42, 7] + +// Explicit type arguments prevent the value argument from refining the interface. +:ge +class Base +class Child extends Base with + val extra = 42 +fun identity[T](x: T): T = x +identity[Base](new Child).extra +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.260: identity[Base](new Child).extra +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Type-parameter uses in nested signatures retain their capture path. +:expect 42 +class Item(val value: Int) +fun identity[T](x: T): T = + fun nested(): T = x + nested() +identity(Item(42)).value +//│ = 42 + +// Infer through nested nominal type arguments, preserving each constructor's marks. +:expect 42 +class Box[X](val value: X) +class Foo[X](val f: Box[X]) +fun test[A](x: Foo[A]) = x.f +test(new Foo(new Box(Predef))).value.id(42) +//│ = 42 + +// Distinct nested instantiations must not leak their member sets into each other. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Box[X](val value: X) +class Foo[X](val f: Box[X]) +fun test[A](x: Foo[A]) = x.f +[test(new Foo(new Box(First(1)))).value.first, + test(new Foo(new Box(Second(2)))).value.second] +//│ = [1, 2] + +// An explicit constructor type argument is also an opaque boundary. +:ge +class Base +class Child extends Base with + val extra = 42 +class Box[T](val value: T) +(new Box[Base](new Child)).value.extra +Box[Base](new Child).value.extra +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.301: (new Box[Base](new Child)).value.extra +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.302: Box[Base](new Child).value.extra +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A generic method's result carries the receiver's type argument to the caller. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Box[X](val value: X) with + fun get(): X = value +fun identity[T](x: T): T = x +[identity[Box[First]](Box[First](First(1))).get().first, + identity[Box[Second]](Box[Second](Second(2))).get().second] +//│ = [1, 2] + +// TODO: infer function-parameter constraints and tuple projections through a type argument. +:fixme +:expect [2, 4] +class Foo[X](val f: X -> X) +fun test[A](x: Foo[[A, Int]]) = x.f +test(new Foo(arg => [arg.1 + 1, arg.2 + 2]))([1, 2]) +//│ ╔══[COMPILATION ERROR] This selection of member '1' has no resolved target +//│ ║ l.329: test(new Foo(arg => [arg.1 + 1, arg.2 + 2]))([1, 2]) +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[2, 4]', got: 'undefined' + +// Dependent method signatures also keep distinct inferred instantiations separate. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Box[X](val value: X) with + fun get(): X = value +class Foo[X](val f: Box[X]) +fun test[A](x: Foo[A]) = x.f +[test(new Foo(new Box(First(1)))).get().first, + test(new Foo(new Box(Second(2)))).get().second] +//│ = [1, 2] + +// Class qualification does not undo an explicit ascription. +:ge +class Item with + val extra = 42 +class Box with + val item = new Item +(new Box as Box).Box#item.extra +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.354: (new Box as Box).Box#item.extra +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'extra' of a value with unknown shape. +//│ ║ l.354: (new Box as Box).Box#item.extra +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// An overloaded type name must not expose its value initializer through an interface. +:ge +class Secret with + val extra = 42 +module Types with + class Item + val Item = new Secret +fun hidden(module types: Types) = types.Item.extra +hidden(Types) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.370: fun hidden(module types: Types) = types.Item.extra +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'extra' of a value with unknown shape. +//│ ║ l.370: fun hidden(module types: Types) = types.Item.extra +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/DeclaredTypes.mls new file mode 100644 index 0000000000..6bf6370cfe --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/DeclaredTypes.mls @@ -0,0 +1,18 @@ +:. +:global +:wasm + +// An annotated receiver cannot import its constructor parameter's capture context. +class Box(val value) +fun read(box: Box) = box.value +read(Box(42)) +//│ Wasm result: +//│ = 42 + +// A declared field type supplies the next receiver independently of observed calls. +class Item(val value) +class Box(val item: Item) +fun read(box: Box) = box.item.value +read(Box(Item(42))) +//│ Wasm result: +//│ = 42 From 55674b94819b811e8deb6545886cb8c2bf779f8d Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 12:42:46 +0800 Subject: [PATCH 064/228] Avoid collection snapshots during shape notification --- .../src/test/scala/hkmc2/PublisherTest.scala | 39 +++++++++++++++++++ .../scala/hkmc2/semantics/Elaborator.scala | 6 +-- .../scala/hkmc2/semantics/NewResolver.scala | 39 +++++++++---------- .../scala/hkmc2/semantics/TypeShape.scala | 5 +-- .../main/scala/hkmc2/utils/Publisher.scala | 19 ++++++++- 5 files changed, 79 insertions(+), 29 deletions(-) create mode 100644 hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala diff --git a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala new file mode 100644 index 0000000000..8f27020c49 --- /dev/null +++ b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala @@ -0,0 +1,39 @@ +package hkmc2 + +import org.scalatest.funsuite.AnyFunSuite +import scala.collection.mutable.ArrayBuffer + +import hkmc2.utils.* + + +class PublisherTest extends AnyFunSuite: + private class IntHost extends Host[Int]: + def showDbg(using DebugPrinter): String = "integer shapes" + def publish(shape: Int): Unit = + if shapes.add(shape) then notifyShapeListeners(shape) + + test("subscribing during notification replays each shape exactly once"): + val host = new IntHost + val original = ArrayBuffer.empty[Int] + val added = ArrayBuffer.empty[Int] + val last = ArrayBuffer.empty[Int] + host.subscribeToShapes: shape => + original += shape + if shape == 1 then host.subscribeToShapes(added += _) + host.subscribeToShapes(last += _) + host.publish(1) + host.publish(2) + assert(original.toList == List(1, 2)) + assert(added.toList == List(1, 2)) + assert(last.toList == List(1, 2)) + + test("publishing during replay does not replay the newly published shape again"): + val host = new IntHost + host.publish(1) + host.publish(2) + val received = ArrayBuffer.empty[Int] + host.subscribeToShapes: shape => + received += shape + if shape == 1 then host.publish(3) + host.publish(4) + assert(received.toList == List(1, 3, 2, 4)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index fa5572ca36..0b7c33a70f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -2576,10 +2576,10 @@ extends Importer: case S(ext) => listenTerm(ext): esh => val sh = BaseShape(d, S(esh)) // TODO actually use applied shape providing generics? - sym.shapeListeners.toList.foreach(_(sh)) + sym.notifyShapeListeners(sh) case N => val sh = BaseShape(d, N) // TODO actually use applied shape providing generics? - sym.shapeListeners.toList.foreach(_(sh)) + sym.notifyShapeListeners(sh) case sym: InnerSymbol => // println(">>> "+sym) // TODO: patterns @@ -2637,7 +2637,7 @@ extends Importer: sym.decl = S(p) if newResolution then sig.foreach: sign => listenTypeValues(sign): shape => - if sym.shapes.add(shape) then sym.shapeListeners.foreach(_(shape)) + if sym.shapes.add(shape) then sym.notifyShapeListeners(shape) (p, spd, aliases) def funParams(t: Tree): Ctxl[(ParamList, Ctx)] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index abc1f2f2c3..cccece5454 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -137,12 +137,9 @@ class NewResolver: private class TypeValues extends Host[TermShape]: def showDbg(using DebugPrinter): Str = "declared type values" def publish(shape: TermShape): Unit = - // A callback can subscribe to this same host; listen replays the new shape. - // Snapshot the old listeners so that reentrant subscription is delivered once. - if shapes.add(shape) then shapeListeners.toList.foreach(_(shape)) + if shapes.add(shape) then notifyShapeListeners(shape) def listen(listener: TermShape => Unit): Unit = - shapeListeners += listener - shapes.toList.foreach(listener) + subscribeToShapes(listener) private val typeValues = mutable.Map.empty[DeclaredType, TypeValues] private val abstractTypes = mutable.Map.empty[TypeResolution, DeclaredType] // An omitted argument in a nominal annotation must not subscribe to the @@ -237,7 +234,7 @@ class NewResolver: if param.sign.isEmpty then sign.foreach: tpe => signatureParameters(param.sym) = tpe listenTypeValues(tpe): shape => - if param.sym.shapes.add(shape) then param.sym.shapeListeners.foreach(_(shape)) + if param.sym.shapes.add(shape) then param.sym.notifyShapeListeners(shape) callable.result.foreach(bind(tail, _)) case _ => () bind(paramLists, declaredType(typeResolution(sign), Map.empty)) @@ -291,7 +288,7 @@ class NewResolver: private val explicitTypeArguments = mutable.Set.empty[(VarSymbol, Ls[Marks])] private def publishTypeArgument(symbol: VarSymbol, shape: TermShape | NoShape): Unit = shape match case value: TermShape if isOwnedSym(symbol) => - if symbol.shapes.add(value) then symbol.shapeListeners.toList.foreach(_(value)) + if symbol.shapes.add(value) then symbol.notifyShapeListeners(value) case _ => () private def applyTypeArguments(defn: DefnShape, marks: Ls[Marks], args: Ls[Term], source: Term): Unit = @@ -490,7 +487,7 @@ class NewResolver: Nil if !res.isErroneous then val psh = CtorPatternShape(cls, assoc, res, FlowSymbol.pat()) - if res.shapes.add(psh) then res.shapeListeners.foreach(_(psh)) + if res.shapes.add(psh) then res.notifyShapeListeners(psh) def valuePattern(sh: TermShape): Unit = sh.applicationHead match case (ds: DefnShape, _) => ds.defn match case cls: ClassDef => classPattern(cls) @@ -531,7 +528,7 @@ class NewResolver: // Rejected duplicate/negated bindings have no allocated symbol. Pattern // validation already reports them; only valid bindings receive flow. al.symbolOption.foreach: symbol => - if symbol.shapes.add(sh) then symbol.shapeListeners.toList.foreach(_(sh)) + if symbol.shapes.add(sh) then symbol.notifyShapeListeners(sh) matched(sh) case Pattern.Wildcard() | Pattern.Literal(_) => matched(shape) case Pattern.Chain(left, right) => @@ -584,7 +581,7 @@ class NewResolver: // unknown result rather than inferring callability from a different candidate. lhs match case Marked(_: (DynShape | UnknownValueShape), _) => - if res.shapes.add(lhs) then res.shapeListeners.foreach(_(lhs)) + if res.shapes.add(lhs) then res.notifyShapeListeners(lhs) return case _ => () lhs match @@ -593,7 +590,7 @@ class NewResolver: checkArgumentArity(args, ps.params.length, ps.hasRest, res, lhs)(_ => ()) def publish(shape: TermShape): Unit = shape.exit(context) match case value: TermShape => - if res.shapes.add(value) then res.shapeListeners.foreach(_(value)) + if res.shapes.add(value) then res.notifyShapeListeners(value) case NoShape => () callable.paramLists.tail match case Nil => callable.result match @@ -613,7 +610,7 @@ class NewResolver: zipArgs(mss, ps.params, ps.restParam, args, res, lhs) log(s"appShape isSaturated? ${sh.isSaturated}; head? ${sh.applicationHead}") def register = if res.shapes.add(sh) then - res.shapeListeners.foreach(listener => listener(sh)) + res.notifyShapeListeners(sh) log(s"lhs ${lhs.isSaturated} ${lhs.unappliedParams.map(_.mapFirst(_.showDbg).mapSecond(_.map(_.showDbg)))}") if lhs.isSaturated && !res.isErroneous then res.isErroneous = true @@ -629,7 +626,7 @@ class NewResolver: case NoShape => case sh: TermShape => if res.shapes.add(sh) then - res.shapeListeners.foreach(listener => listener(sh)) + res.notifyShapeListeners(sh) sh.applicationHead match case (ds: DefnShape, mss) => ds.defn match @@ -653,7 +650,7 @@ class NewResolver: listenSignatureResult(declaredType(typeResolution(sign), Map.empty), count): result => result.exit(mss) match case value: TermShape => - if res.shapes.add(value) then res.shapeListeners.foreach(_(value)) + if res.shapes.add(value) then res.notifyShapeListeners(value) case NoShape => () case N => td.body.foreach(go(_, mss)) case _ => @@ -672,7 +669,7 @@ class NewResolver: private def publishMember(host: NewResolvable & ShapeHost, member: BlockMemberSymbol | RecordMember, flow: FlowSymbol, marks: Ls[Marks]): Unit = def publish(shape: Shape): Unit = - if host.shapes.add(shape) then host.shapeListeners.foreach(_(shape)) + if host.shapes.add(shape) then host.notifyShapeListeners(shape) def definition(sym: BlockMemberSymbol): Unit = publish(symShapes.getOrElseUpdate((sym, flow, marks), SymShape(sym, flow, marks))) member match @@ -689,12 +686,12 @@ class NewResolver: bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks]): Unit = val shape = declaredSymShapes.getOrElseUpdate((member, flow, marks, bindings), DeclaredSymShape(member, flow, marks, bindings)) - if host.shapes.add(shape) then host.shapeListeners.toList.foreach(_(shape)) + if host.shapes.add(shape) then host.notifyShapeListeners(shape) private def publishDynamic(host: NewResolvable & ShapeHost, marks: Ls[Marks]): Unit = DynShape().exit(marks) match case shape: TermShape => - if host.shapes.add(shape) then host.shapeListeners.foreach(_(shape)) + if host.shapes.add(shape) then host.notifyShapeListeners(shape) case NoShape => () private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, loc: Opt[Loc]): Unit = @@ -864,7 +861,7 @@ class NewResolver: case ((ps, _), args) => zipArgs(marks, ps.params, ps.restParam, args, nw, dsh) NewShape(dsh, cd.sym, marks, nw.args, nw) }) - if nw.shapes.add(sh) then nw.shapeListeners.foreach(_(sh)) + if nw.shapes.add(sh) then nw.notifyShapeListeners(sh) ) , shape => if !nw.isErroneous then @@ -883,7 +880,7 @@ class NewResolver: listen(rhs): sh => assert(isOwnedSym(sym), s"defineVar: sym = ${sym.showDbg}, rhs = ${rhs.showDbg}") if sym.shapes.add(sh) then - sym.shapeListeners.foreach(listener => listener(sh)) + sym.notifyShapeListeners(sh) DefineVar(sym, rhs) def listenDefn(sym: TermSymbol, listener: TermShape => Unit): Unit = @@ -903,7 +900,7 @@ class NewResolver: log(s"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.shapes}") listenTerm(from): sh => if to.shapes.add(sh) then - to.shapeListeners.foreach(listener => listener(sh)) + to.notifyShapeListeners(sh) def listenExt(ext: Opt[Term], listener: Opt[TermShape] => Unit): Unit = ext match @@ -1057,7 +1054,7 @@ class NewResolver: aggregate.shapes.foreach(listener) if first then start: shape => - if aggregate.shapes.add(shape) then aggregate.shapeListeners.foreach(_(shape)) + if aggregate.shapes.add(shape) then aggregate.notifyShapeListeners(shape) def listen(trm: Term, discardMarks: Bool = false)(listener: Shape => Unit): Unit = log(s"listen: trm = ${trm.showDbg}") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 688bf68b9b..ac36a967d5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -28,10 +28,9 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => def hasErrors: Bool = reported def showDbg(using DebugPrinter): Str = s"type of ${source.showDbg}" def publish(shape: TypeShape): Unit = - if shapes.add(shape) then shapeListeners.toList.foreach(_(shape)) + if shapes.add(shape) then notifyShapeListeners(shape) def listen(listener: TypeShape => Unit): Unit = - shapeListeners += listener - shapes.toList.foreach(listener) + subscribeToShapes(listener) def fail(messages: Ls[(Message, Opt[Loc])]): Unit = if !reported then reported = true report(messages) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala index dd1b5051cc..a9364bbffa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala @@ -1,16 +1,31 @@ package hkmc2 package utils -import scala.collection.mutable.{Buffer, Set as MutSet, LinkedHashSet} +import scala.collection.mutable.{ArrayBuffer, LinkedHashSet} import hkmc2.utils.*, shorthands.* trait Publisher[A]: - private[hkmc2] val shapeListeners: Buffer[A => Unit] = Buffer.empty + // Listeners are append-only so callbacks may subscribe during notification. + private[hkmc2] val shapeListeners: ArrayBuffer[A => Unit] = ArrayBuffer.empty + + private[hkmc2] def notifyShapeListeners(shape: A): Unit = + // A new subscriber receives the current shape through replay. Only visit the + // original listeners here, avoiding duplicate delivery and iterator invalidation. + val count = shapeListeners.size + var i = 0 + while i < count do + shapeListeners(i)(shape) + i += 1 trait Host[A] extends Publisher[A]: private[hkmc2] val shapes: LinkedHashSet[A] = LinkedHashSet.empty + private[hkmc2] def subscribeToShapes(listener: A => Unit): Unit = + shapeListeners += listener + // Shapes are append-only and LinkedHashSet iteration preserves insertion order. + // Shapes added by a replay callback are already delivered by notification. + shapes.iterator.take(shapes.size).foreach(listener) def showDbg(using DebugPrinter): Str From 65524496f14bb691ece445e1b96885abf6147970 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 13:00:44 +0800 Subject: [PATCH 065/228] Propagate function constraints and resolve typed tuple projections --- doc/new-resolution-suite-migration.md | 15 +- .../main/scala/hkmc2/codegen/Lowering.scala | 21 ++- .../scala/hkmc2/semantics/NewResolver.scala | 174 +++++++++++++++--- .../main/scala/hkmc2/semantics/Shape.scala | 33 +++- .../src/main/scala/hkmc2/semantics/Term.scala | 3 + .../scala/hkmc2/semantics/TypeShape.scala | 2 + .../test/mlscript/newres/DeclaredTypes.mls | 118 ++++++++++-- .../shared/src/test/mlscript/newres/wasm/.mls | 1 + .../mlscript/newres/wasm/BackendFlags.mls | 1 - .../mlscript/newres/wasm/DeclaredTypes.mls | 36 +++- .../src/test/mlscript/newres/wasm/Erasure.mls | 2 - .../test/mlscript/newres/wasm/WasmBasics.mls | 2 - .../newres/wasm/WasmMethodResolution.mls | 2 - 13 files changed, 353 insertions(+), 57 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 254508edd5..7435efcad5 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -191,9 +191,18 @@ carry those type-argument contexts without reading implementation value flow. `newres/DeclaredTypes.mls` covers unused annotated functions, unannotated fields, subclass overrides, inherited fields, curried constructors, nested captures, generic aliases, separate signatures, and distinct generic instantiations. Its -function/tuple-constraint example remains an explicit `:fixme`: inferring through -`Foo[[A, Int]]` with a callback requires function-parameter constraints and tuple -projection shapes. REPL method cases remain assigned to the other branch. +function/tuple-constraint example now passes: callback parameter types flow into +implementation parameters, callback results constrain inferred type arguments, +and calls through declared signatures contribute argument constraints. Tuple +types retain their element interfaces, and zero-based projections preserve +capture marks through tuple values, declared types, and spreads. Mutable tuple +reads do not reuse initializer shapes. Distinct callback instantiations and +opaque annotated tuple elements have regression coverage. + +WASM tuple reads and writes pass, using indexed lowering. The complete callback +example and calls through tuple elements remain explicit WASM `:fixme` cases: +that backend still lacks the required first-class function representation and +anonymous-function lowering. REPL method cases remain assigned to the other branch. The original annotated-receiver cases from WASM `Basics` now pass and are covered by `newres/wasm/DeclaredTypes.mls`. A fresh trial reaches a later capture assertion diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index ec916a4c2c..43636eb5a3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -678,7 +678,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu private def newSelection(sel: NewSel)(k: (Term, Tree.Ident, Opt[DefinitionSymbol[?]]) => Block)(using LoweringCtx): Block = val NewSel(prefix, id, _) = sel if !checkProjection(sel) || sel.isErroneous then compError - else if sel.hasDynamicTarget then k(prefix, memberIdent(id, N), N) + else if sel.tupleIndex.nonEmpty && sel.resolvedTargets.nonEmpty then fail: + ErrorReport(msg"This selection has both tuple and nominal member targets" -> sel.toLoc :: Nil, + source = Diagnostic.Source.Compilation) + else if sel.hasDynamicTarget || sel.tupleIndex.nonEmpty then k(prefix, memberIdent(id, N), N) else sel.resolvedTargets.distinct match case Nil => fail: ErrorReport(msg"This selection of member '${id.name}' has no resolved target" -> sel.toLoc :: Nil, @@ -706,6 +709,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu source = Diagnostic.Source.Compilation) else k(prefix, memberIdent(id, N), N) + private def selectionPath(sel: NewSel, prefix: Path, name: Tree.Ident, + target: Opt[DefinitionSymbol[?]]): Path = sel.tupleIndex match + case S(index) => DynSelect(prefix, Value.Lit(Tree.IntLit(BigInt(index))), true).withLocOf(sel) + case N => Select(prefix, name)(target)(false).withLocOf(sel) + /** A projection must identify its class as well as its member: different * classes can inherit the same definition, and wildcard receivers can differ. */ private def checkProjection(sel: NewSel): Bool = sel.cls match @@ -1146,7 +1154,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu // * are preserved in the call and not moved to a temporary variable. case sel: NewSel => newSelection(sel): (prefix, name, target) => subTerm_nonTail(prefix): p => - conclude(Select(p, name)(target)(false).withLocOf(sel)) + conclude(selectionPath(sel, p, name, target)) case ref: UnresolvedRef => openSelection(ref): (prefix, member, target) => subTerm_nonTail(prefix): p => conclude(Select(p, memberIdent(ref.id, member))(target)(false).withLocOf(ref)) @@ -1220,6 +1228,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu source = Diagnostic.Source.Compilation) case sel: NewSel => newSelection(sel): (prefix, name, target) => target match + case N if sel.tupleIndex.nonEmpty => + subTerm_nonTail(prefix): p => + subTerm_nonTail(rhs): r => + AssignDynField(p, Value.Lit(Tree.IntLit(BigInt(sel.tupleIndex.get))), true, r, k(unit)) case S(sym: TermSymbol) => subTerm_nonTail(prefix): p => subTerm_nonTail(rhs): r => @@ -1290,8 +1302,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case whltrm: st.SynthWhile => ucs.Normalization(this)(whltrm)(k) - case sel: NewSel => newSelection(sel)((prefix, name, target) => - setupNamedSelection(prefix, name, target)(k)) + case sel: NewSel => newSelection(sel): (prefix, name, target) => + if sel.tupleIndex.nonEmpty then subTerm_nonTail(prefix)(p => k(selectionPath(sel, p, name, target))) + else setupNamedSelection(prefix, name, target)(k) case sel @ Sel(prefix, nme) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index cccece5454..a288bfc933 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -99,6 +99,8 @@ class NewResolver: case UnitVal() => result.publish(TypeShape.Unit) case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => result.publish(TypeShape.Parameter(sym)) + case Tup(fields) if fields.forall(_.isInstanceOf[Fld]) => + result.publish(TypeShape.Tuple(fields.collect { case Fld(_, sign, _) => typeResolution(sign) })) case _: WildcardTy | _: Neg | _: Rcd | _: Tup | _: Lit | Missing | Error() => result.publish(TypeShape.Abstract) case _ => listen(term, discardMarks = true): @@ -215,6 +217,9 @@ class NewResolver: case single => DeclaredParams(S(declaredType(typeResolution(single), current.bindings)) :: Nil, false) publish(CallableTypeShape(tpe.resolution.source, ps :: Nil, S(declaredType(ret, current.bindings)))) + case TypeShape.Tuple(fields) => + publish(TupleShape(current.resolution.source, + fields.map(field => TupleShape.TypedField(declaredType(field, current.bindings), Nil)))) case TypeShape.Union(left, right) => next(left); next(right) case TypeShape.Intersection(left, right) => next(left); next(right) case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) @@ -305,8 +310,11 @@ class NewResolver: listenTypeValues(arg): tpe => publishTypeArgument(param, tpe.enter(marks)) - private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks], - seen: Set[TypeResolution]): Unit = + // Install each constraint edge before subscribing: callback parameter/result + // flow can revisit it immediately. Distinct instantiations retain their marks. + private val typeConstraints = mutable.Set.empty[(DeclaredType, TermShape, Ls[Marks])] + private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks]): Unit = + if !typeConstraints.add((tpe, value, marks)) then return def follow(tpe: DeclaredType, args: Ls[DeclaredType], captures: Ls[Marks], seen: Set[TypeResolution]): Unit = if !seen(tpe.resolution) then val next = seen + tpe.resolution @@ -323,12 +331,27 @@ class NewResolver: case TypeShape.Alias(symbol, S(rhs)) => val bindings = tpe.bindings ++ symbol.defn.get.tparams.map(_.sym).zip(args) follow(declaredType(rhs, bindings), Nil, captures, next) + case TypeShape.Tuple(fields) => value.enter(captures) match + case Marked(tuple: TupleShape, context) => + fields.zipWithIndex.foreach: (field, index) => + tuple.getMember(index.toString) match + case MemberLookup.Indexed(actual, inner) => listenTupleField(actual): shape => + shape.exit(inner).exit(context) match + case value: TermShape => + inferTypeArguments(declaredType(field, tpe.bindings), value, marks) + case NoShape => () + case _ => () + case _ => () + case TypeShape.Function(_, _) => value.enter(captures) match + case actual: TermShape => + constrainFunction(declaredType(tpe.resolution, tpe.bindings), actual, marks) + case NoShape => () case TypeShape.Nominal(cls) if args.nonEmpty => val (head, context) = value.applicationHead def constrain(param: TyParam, pattern: DeclaredType): Unit = def receive(shape: Shape): Unit = shape match case shape: TermShape => shape.exit(context) match - case actual: TermShape => inferTypeArguments(pattern, actual, marks, next) + case actual: TermShape => inferTypeArguments(pattern, actual, marks) case NoShape => () case _ => softAssert(false, "A type parameter received a symbolic overload set") param.sym.shapeListeners += receive @@ -340,11 +363,77 @@ class NewResolver: nominal.bindings.get(param.sym).foreach: bound => listenTypeValues(bound): shape => shape.exit(context) match - case actual: TermShape => inferTypeArguments(pattern, actual, marks, next) + case actual: TermShape => inferTypeArguments(pattern, actual, marks) case NoShape => () case _ => () case _ => () // Concrete annotations are opaque, irrespective of the argument value. - follow(tpe, Nil, Nil, seen) + // Alias expansion guards only unproductive cycles. Descending into a tuple, + // nominal argument, or arrow installs another edge with its own cycle guard. + follow(tpe, Nil, Nil, Set.empty) + + /** Function constraints reverse the parameter flow and preserve result flow. + * Read an implementation only while checking it against its declared interface; + * calls through that interface continue to expose only the declared result. + */ + private def constrainFunction(expected: DeclaredType, actual: TermShape, marks: Ls[Marks]): Unit = + val context = actual.applicationHead._2 + def results(listener: TermShape => Unit): Unit = actual.applicationHead._1 match + case intro: IntroShape => intro.trm match + case Lam(_, body) => listenTerm(body)(listener) + case _ => () + case ds: DefnShape => ds.defn match + case td: TermDefinition => td.sign match + case S(sign) => listenSignatureResult(declaredType(typeResolution(sign), Map.empty), + if td.flags.hasResultAnnotation then 0 else td.params.length)(listener) + case N => td.body.foreach(listenTerm(_)(listener)) + case _ => () + case _ => () + def matchLists(expected: DeclaredType, remaining: Ls[(ParamList, Ls[Marks])]): Unit = + listenTypeValues(expected): + case Marked(callable: CallableTypeShape, expectedContext) => remaining match + case (params, _) :: tail => + val declared = callable.paramLists.head + if declared.params.length != params.params.length || declared.hasRest != params.restParam.nonEmpty then + resolError(expected.resolution.source, msg"Callback parameter list does not match its declared function type." -> actual.toLoc :: Nil) + else + params.params.zip(declared.params).foreach: (param, sign) => + sign.foreach: tpe => + listenTypeValues(tpe): shape => + constrainParameter(param, shape.exit(expectedContext).enter(context), context) + callable.result.foreach: ret => + tail match + case Nil => results: shape => + shape.exit(context).enter(expectedContext) match + case value: TermShape => inferTypeArguments(ret, value, marks) + case NoShape => () + case _ => matchLists(ret, tail) + case Nil => () + case _ => () + actual match + case Marked(givenType: CallableTypeShape, actualContext) => + listenTypeValues(expected): + case Marked(wanted: CallableTypeShape, expectedContext) => + val domain = givenType.paramLists.head + val expectedDomain = wanted.paramLists.head + if domain.params.length != expectedDomain.params.length || domain.hasRest != expectedDomain.hasRest then + resolError(expected.resolution.source, msg"Callback parameter list does not match its declared function type." -> actual.toLoc :: Nil) + else + domain.params.zip(expectedDomain.params).foreach: + case (S(givenParam), S(wantedParam)) => + listenTypeValues(wantedParam): shape => + shape.exit(expectedContext).enter(actualContext) match + case value: TermShape => inferTypeArguments(givenParam, value, context) + case NoShape => () + case _ => () + wanted.result.foreach: ret => + def receive(shape: TermShape): Unit = shape.exit(actualContext).enter(expectedContext) match + case value: TermShape => inferTypeArguments(ret, value, marks) + case NoShape => () + givenType.paramLists.tail match + case Nil => givenType.result.foreach(listenTypeValues(_)(receive)) + case tail => receive(givenType.copy(paramLists = tail)) + case _ => () + case _ => matchLists(expected, actual.unappliedParams) private def listenSignatureResult(tpe: DeclaredType, count: Int)(listener: TermShape => Unit): Unit = if count == 0 then listenTypeValues(tpe)(listener) @@ -353,49 +442,64 @@ class NewResolver: callable.result.foreach(listenSignatureResult(_, count - 1)(listener)) case _ => () + private def listenTupleField(field: TupleShape.Fixed)(listener: TermShape => Unit): Unit = + def receive(shape: TermShape): Unit = shape.exit(field.marks) match + case value: TermShape => listener(value) + case NoShape => () + field match + case TupleShape.Field(field, _) => listenTerm(field.term)(receive) + case TupleShape.TypedField(tpe, _) => listenTypeValues(tpe)(receive) + case TupleShape.UnknownField(source, _) => receive(UnknownValueShape(source)) + /** Subscribe once to complete argument-tuple candidates. A pending spread is not * an argument, nor evidence of an arity mismatch; each resolved combination is. */ def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Term, src: Term, funSh: TermShape): Unit = - val expectedCount = ps.length - def publish(p: Param, shape: TermShape | NoShape): Unit = shape match + val params = ps ::: r.toList + zipArgumentShapes(mss, ps.length, r.nonEmpty, args, src, funSh): (index, shape) => + constrainParameter(params(index), shape, mss) + + private def constrainParameter(p: Param, shape: TermShape | NoShape, marks: Ls[Marks]): Unit = + shape match case NoShape => () case sh: TermShape => val sign = p.sign.map(sign => declaredType(typeResolution(sign), Map.empty)).orElse(signatureParameters.get(p.sym)) sign match - case S(tpe) => inferTypeArguments(tpe, sh, mss, Set.empty) + case S(tpe) => inferTypeArguments(tpe, sh, marks) case N => publishTypeArgument(p.sym, sh) + + private def zipArgumentShapes(mss: Ls[Marks], expectedCount: Int, hasRest: Bool, + args: Term, src: Term, funSh: TermShape)(publish: (Int, TermShape | NoShape) => Unit): Unit = def matchSegments(tuple: TupleShape, marks: Marks): Unit = val segments = tuple.segments - val knownCount = segments.count(_.isInstanceOf[TupleShape.Field]) + val knownCount = segments.count(_.isInstanceOf[TupleShape.Fixed]) // Distribute only successful argument counts. Unknown spreads retain all // possible fixed positions and the residual tail for a rest parameter. val extra = (expectedCount - knownCount).max(0) def assign(segment: TupleShape.Segment, positions: Range): Unit = positions.foreach: index => - val p = ps(index) segment match - case TupleShape.Field(field, inner) => listenTerm(field.term): sh => - publish(p, sh.exit(inner).exit(marks).enter(mss)) + case field: TupleShape.Fixed => listenTupleField(field): sh => + publish(index, sh.exit(marks).enter(mss)) case TupleShape.Unknown(_, inner, value) => - publish(p, value.exit(inner).exit(marks).enter(mss)) + publish(index, value.exit(inner).exit(marks).enter(mss)) def loop(rest: Ls[TupleShape.Segment], before: Int, unknownBefore: Bool): Unit = rest match case Nil => () - case (field: TupleShape.Field) :: tail => + case (field: TupleShape.Fixed) :: tail => val unknownAfter = tail.exists(_.isInstanceOf[TupleShape.Unknown]) // With no later unknown segment, preceding spreads must supply all // missing fixed arguments, even when the function has a rest parameter. val first = if unknownBefore && !unknownAfter then before + extra else before val last = if !unknownBefore then before - else if r.nonEmpty then expectedCount - 1 else before + extra + else if hasRest then expectedCount - 1 else before + extra assign(field, first.max(0) until (last + 1).min(expectedCount)) loop(tail, before + 1, unknownBefore) case (unknown: TupleShape.Unknown) :: tail => - val end = if r.nonEmpty then expectedCount else before + extra + val end = if hasRest then expectedCount else before + extra assign(unknown, before until end) loop(tail, before, true) loop(segments, 0, false) - r.foreach: p => + if hasRest then // If consumption reaches an unknown segment, some subsequent fields // may have been consumed too. Approximate that optional prefix with an // unknown segment, retaining the suffix that must remain. Publishing @@ -405,7 +509,7 @@ class NewResolver: if count == 0 then xs else xs match case Nil => Nil - case (_: TupleShape.Field) :: rest => drop(rest, count - 1, approximate) + case (_: TupleShape.Fixed) :: rest => drop(rest, count - 1, approximate) case (unknown: TupleShape.Unknown) :: Nil => // Consuming a prefix changes the length, but not the element // shape, when there are no following fields to merge into it. @@ -414,9 +518,9 @@ class NewResolver: val suffix = drop(rest, count, false) if approximate then TupleShape.Unknown(tuple.source, Nil, UnknownValueShape(tuple.source)) :: suffix else suffix val remaining = drop(segments, expectedCount, true) - val rest = if ps.isEmpty then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil) - publish(p, rest.exit(marks).enter(mss)) - checkArgumentArity(args, expectedCount, r.nonEmpty, src, funSh): + val rest = if expectedCount == 0 then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil) + publish(expectedCount, rest.exit(marks).enter(mss)) + checkArgumentArity(args, expectedCount, hasRest, src, funSh): case (tuple, marks) => matchSegments(tuple, marks) private def checkArgumentArity(args: Term, expected: Int, rest: Bool, src: Term, @@ -424,7 +528,7 @@ class NewResolver: val reportedCounts = mutable.Set.empty[Int] listenTerm(args): case Marked(tuple: TupleShape, marks) => - val known = tuple.segments.count(_.isInstanceOf[TupleShape.Field]) + val known = tuple.segments.count(_.isInstanceOf[TupleShape.Fixed]) val unknown = known != tuple.segments.length val mismatch = (!unknown && known < expected) || (!rest && known > expected) if mismatch then @@ -587,7 +691,11 @@ class NewResolver: lhs match case Marked(callable: CallableTypeShape, context) => val ps = callable.paramLists.head - checkArgumentArity(args, ps.params.length, ps.hasRest, res, lhs)(_ => ()) + zipArgumentShapes(context :: Nil, ps.params.length, ps.hasRest, args, res, lhs): (index, value) => + if index < ps.params.length then ps.params(index).foreach: tpe => + value match + case value: TermShape => inferTypeArguments(tpe, value, context :: Nil) + case NoShape => () def publish(shape: TermShape): Unit = shape.exit(context) match case value: TermShape => if res.shapes.add(value) then res.notifyShapeListeners(value) @@ -715,6 +823,8 @@ class NewResolver: val candidate = prefix -> member if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate publishDeclared(ref, member, ref.resSym, bindings, marks) + case MemberLookup.Indexed(_, _) => + resolError(ref, msg"Tuple elements must be selected by index." -> ref.toLoc :: Nil) case MemberLookup.Dynamic(marks) => if !ref.dynamicPrefixes.contains(prefix) then ref.dynamicPrefixes ::= prefix publishDynamic(ref, marks) @@ -770,6 +880,15 @@ class NewResolver: case MemberLookup.Declared(member, bindings, marks) => if !sel.resolvedMembers.contains(member) then sel.resolvedMembers ::= member publishDeclared(sel, member, sel.resSym, bindings, marks) + case MemberLookup.Indexed(field, marks) => + val index = sel.id.name.toIntOption + softAssert(index.exists(_ >= 0), "Tuple lookup must identify a nonnegative index") + sel.tupleIndex = index + listenTupleField(field): shape => + shape.exit(marks) match + case value: TermShape => + if sel.shapes.add(value) then sel.notifyShapeListeners(value) + case NoShape => () case MemberLookup.Dynamic(marks) => sel.hasDynamicTarget = true publishDynamic(sel, marks) @@ -1073,6 +1192,15 @@ class NewResolver: case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => ()) case value: TermShape => instantiate(value) listener(shape) + case Mut(underlying: Tup) => listenTerm(underlying): + case Marked(tuple: TupleShape, marks) => + val fields = tuple.segments.map: + case _: TupleShape.Fixed => TupleShape.UnknownField(trm, Nil) + case unknown: TupleShape.Unknown => unknown + TupleShape(trm, TupleShape.Rest(tuple, fields) :: Nil).exit(marks) match + case shape: TermShape => listener(shape) + case NoShape => () + case shape => listener(shape) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => listenAggregate(tuple, listener): publish => def expand(elems: Ls[Elem], reversed: Ls[TupleShape.Element]): Unit = elems match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index d89068112a..576c7d6f90 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -262,6 +262,7 @@ enum MemberLookup: // Declared members expose signatures only. Their marks transport dependent // type arguments; they never authorize reading an implementation's value flow. case Declared(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks]) + case Indexed(field: TupleShape.Fixed, marks: Ls[Marks]) case Dynamic(marks: Ls[Marks]) case Missing case Unknown(reason: MemberLookup.Uncertainty, loc: Opt[Loc]) @@ -269,6 +270,7 @@ enum MemberLookup: def withMarks(marks: Ls[Marks]): MemberLookup = this match case Found(member, inner) => Found(member, inner ::: marks) case Declared(member, bindings, inner) => Declared(member, bindings, inner ::: marks) + case Indexed(field, inner) => Indexed(field, inner ::: marks) case Dynamic(inner) => Dynamic(inner ::: marks) case _ => this @@ -377,8 +379,8 @@ final case class NominalTypeShape(defn: ClassLikeDef, bindings: Map[VarSymbol, D /** Parameter types and arity exposed by one list in a declared calling interface. */ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool) -/** Only the calling interface is visible through an annotation. No arguments are - * propagated into implementation parameters, and the result comes from its type. +/** Calls through annotations expose only the declared result. Argument shapes + * constrain type parameters in the interface; they do not recover its implementation. */ final case class CallableTypeShape(source: Term, paramLists: Ls[DeclaredParams], result: Opt[DeclaredType]) extends NonAppTermShape: @@ -454,7 +456,7 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) exte case segment: TupleShape.Segment => segment :: Nil case TupleShape.Rest(_, segments) => segments case TupleShape.Spread(shape, marks) => shape.segments.map: - case TupleShape.Field(field, inner) => TupleShape.Field(field, inner ::: marks :: Nil) + case field: TupleShape.Fixed => field.withMarks(marks :: Nil) case TupleShape.Unknown(source, inner, value) => TupleShape.Unknown(source, inner ::: marks :: Nil, value) /** Does this candidate already depend on the given producer in the given context? * `source` identifies the producer by syntax-node identity; `marks` distinguish @@ -474,7 +476,16 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) exte ((shape.source is source) && inner == marks) || shape.containsSpread(source, marks) def describe: Str = "tuple literal" def toLoc: Opt[Loc] = source.toLoc - protected def getMemberImpl(name: Str): MemberLookup = ??? // Structural tuple members are not implemented yet. + protected def getMemberImpl(name: Str): MemberLookup = name.toIntOption match + case S(index) if index >= 0 => + def loop(rest: Ls[TupleShape.Segment], index: Int): MemberLookup = rest match + case (field: TupleShape.Fixed) :: tail => + if index == 0 then MemberLookup.Indexed(field, Nil) else loop(tail, index - 1) + case (_: TupleShape.Unknown) :: _ => + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, toLoc) + case Nil => MemberLookup.Missing + loop(segments, index) + case _ => MemberLookup.Missing /** Values whose members and call results are deliberately checked only at runtime. * Unlike UnknownValueShape, this authorizes dynamic operations; it is introduced @@ -498,7 +509,17 @@ final case class UnknownValueShape(source: Term) extends NonAppTermShape: object TupleShape: sealed trait Element sealed trait Segment extends Element - final case class Field(field: Fld, marks: Ls[Marks]) extends Segment + sealed trait Fixed extends Segment: + def marks: Ls[Marks] + def withMarks(outer: Ls[Marks]): Fixed = this match + case Field(field, inner) => Field(field, inner ::: outer) + case TypedField(tpe, inner) => TypedField(tpe, inner ::: outer) + case UnknownField(source, inner) => UnknownField(source, inner ::: outer) + final case class Field(field: Fld, marks: Ls[Marks]) extends Fixed + final case class TypedField(tpe: DeclaredType, marks: Ls[Marks]) extends Fixed + // Mutable tuple slots retain their positions, but their initializer cannot + // supply the shape of later reads (including reads through a spread copy). + final case class UnknownField(source: Term, marks: Ls[Marks]) extends Fixed /** An arbitrary number of arbitrary values. Use this for opaque layouts and * recursive widening, never for a spread whose shape has not arrived yet. * `value` distinguishes dynamically typed JS elements from values whose @@ -543,7 +564,7 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] case Missing => loop(rest) case Found(member: RecordMember, inner) => Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil) - case Found(_: BlockMemberSymbol, _) | Declared(_, _, _) => + case Found(_: BlockMemberSymbol, _) | Declared(_, _, _) | Indexed(_, _) => // Record spreads recursively look up RecordShapes, which only create RecordMembers. lastWords("Record lookup returned a nominal member") loop(elements.reverse) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index dfe3cafcb0..1f22cf9300 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -363,6 +363,9 @@ sealed trait NewSelImpl extends NewResolvableImpl: self: Term.NewSel => // At least one receiver permits runtime lookup without a static member symbol. var hasDynamicTarget: Bool = false + // Resolution records a numeric tuple projection; lowering emits an indexed + // access rather than looking for a nominal field symbol. + var tupleIndex: Opt[Int] = N var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution // Class identity and captures must survive even when candidates share an inherited member. var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index ac36a967d5..db4c733023 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -12,6 +12,7 @@ enum TypeShape: case Alias(symbol: TypeAliasSymbol, rhs: Opt[TypeResolution]) case Union(left: TypeResolution, right: TypeResolution) case Intersection(left: TypeResolution, right: TypeResolution) + case Tuple(fields: Ls[TypeResolution]) case Function(params: Term, result: TypeResolution) case Applied(base: TypeResolution, args: Ls[TypeResolution]) case Parameter(symbol: VarSymbol) @@ -57,6 +58,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case Captured(base, _) => base.validate(next) case Applied(base, args) => base.validate(next); args.foreach(_.validate(next)) case Function(_, result) => result.validate(next) + case Tuple(fields) => fields.foreach(_.validate(next)) case Union(left, right) => left.validate(next); right.validate(next) case Intersection(left, right) => left.validate(next); right.validate(next) case _ => () diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index d437924b87..0bd528fab5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -321,17 +321,12 @@ fun identity[T](x: T): T = x identity[Box[Second]](Box[Second](Second(2))).get().second] //│ = [1, 2] -// TODO: infer function-parameter constraints and tuple projections through a type argument. -:fixme +// Function constraints propagate tuple shapes through a type argument. :expect [2, 4] class Foo[X](val f: X -> X) fun test[A](x: Foo[[A, Int]]) = x.f -test(new Foo(arg => [arg.1 + 1, arg.2 + 2]))([1, 2]) -//│ ╔══[COMPILATION ERROR] This selection of member '1' has no resolved target -//│ ║ l.329: test(new Foo(arg => [arg.1 + 1, arg.2 + 2]))([1, 2]) -//│ ╙── ^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '[2, 4]', got: 'undefined' +test(new Foo(arg => [arg.0 + 1, arg.1 + 2]))([1, 2]) +//│ = [2, 4] // Dependent method signatures also keep distinct inferred instantiations separate. :expect [1, 2] @@ -353,10 +348,10 @@ class Box with val item = new Item (new Box as Box).Box#item.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.354: (new Box as Box).Box#item.extra +//│ ║ l.349: (new Box as Box).Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ║ l.354: (new Box as Box).Box#item.extra +//│ ║ l.349: (new Box as Box).Box#item.extra //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -370,9 +365,108 @@ module Types with fun hidden(module types: Types) = types.Item.extra hidden(Types) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.370: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.365: fun hidden(module types: Types) = types.Item.extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ║ l.370: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.365: fun hidden(module types: Types) = types.Item.extra //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Callback inputs and outputs share inferred type arguments at the application. +:expect 43 +class Item(val value: Int) +fun apply[A](f: A -> A, x: A): A = f(x) +apply(arg => Item(arg.value + 1), Item(42)).value +//│ = 43 + +// Expected callback parameters retain explicit abstraction boundaries. +:ge +class Base +class Child extends Base with + val extra = 1 +fun apply[A](f: A -> A, x: A): A = f(x) +apply[Base](arg => (arg.extra; arg), new Child) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.388: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ ^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Independent callback instantiations must not mix their tuple element shapes. +:expect [2, 4] +class First(val first: Int) +class Second(val second: Int) +class Foo[X](val f: X -> X) +fun test[A](x: Foo[[A, Int]]) = x.f +[test(new Foo(arg => [First(arg.0.first + 1), arg.1]))([First(1), 0]).0.first, + test(new Foo(arg => [Second(arg.0.second + 2), arg.1]))([Second(2), 0]).0.second] +//│ = [2, 4] + +// Declared tuple fields expose their types, not their initializer's subclasses. +:ge +class Base +class Child extends Base with + val extra = 1 +fun read(xs: [Base, Int]) = xs.0.extra +read([new Child, 0]) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.410: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ ^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Tuple projections retain contexts through spreads and function returns. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +fun wrap(x) = [...[x]] +[wrap(First(1)).0.first, wrap(Second(2)).0.second] +//│ = [1, 2] + +// Mutation invalidates initializer shapes, including through a tuple spread. +:ge +class First(val first: Int) +class Second(val second: Int) +let xs = mut [First(1)] +set xs.0 = Second(2) +[...xs].0.first +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.432: [...xs].0.first +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'first' of a value with unknown shape. +//│ ║ l.430: let xs = mut [First(1)] +//│ ╙── ^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [Second(2)] + +// Out-of-bounds static projections are rejected. +:ge +[1, 2].2 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.444: [1, 2].2 +//│ ║ ^^^^^^^^ +//│ ╟── Tuple literal does not contain member '2' +//│ ║ l.444: [1, 2].2 +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A callback supplied through an annotation exposes its declared result shape. +:expect 42 +class Item(val value: Int) +fun run[A](f: Int -> A): A = f(0) +fun callback: Int -> Item = ignored => Item(42) +run(callback).value +//│ = 42 + +// Tuple elements can be used directly as callees. +:expect 42 +[x => x + 1].0(41) +//│ = 42 + +// Indexed assignments must retain indexed lowering. +:expect 42 +let xs = mut [1, 2] +set xs.1 = 42 +xs.1 +//│ = 42 +//│ xs = [1, 42] diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/.mls index d83e1a1f31..9e21925992 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/.mls @@ -1,2 +1,3 @@ :.. :!js +:wasm diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/BackendFlags.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/BackendFlags.mls index 2a426531ae..a6715ad715 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/BackendFlags.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/BackendFlags.mls @@ -3,7 +3,6 @@ // Empty blocks do not warn; each compiled block warns when both are enabled. :global :js -:wasm :w 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/DeclaredTypes.mls index 6bf6370cfe..1c8b54042c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/DeclaredTypes.mls @@ -1,6 +1,4 @@ :. -:global -:wasm // An annotated receiver cannot import its constructor parameter's capture context. class Box(val value) @@ -16,3 +14,37 @@ fun read(box: Box) = box.item.value read(Box(Item(42))) //│ Wasm result: //│ = 42 + +// Function constraints resolve this example; first-class anonymous functions remain unsupported in WASM. +:fixme +class Foo[X](val f: X -> X) +fun test[A](x: Foo[[A, Int]]) = x.f +test(new Foo(arg => [arg.0 + 1, arg.1 + 2]))([1, 2]) +//│ ═══[COMPILATION ERROR] WatBuilder::returningTerm for FunDefn(...) where `!sym.nameIsMeaningful` not implemented yet +//│ ╔══[COMPILATION ERROR] Expected WAT of `fun` expression in Call(...) to have a `(ref )` type +//│ ║ l.22: test(new Foo(arg => [arg.0 + 1, arg.1 + 2]))([1, 2]) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// Static tuple projections lower to WASM array accesses. +:expect 42 +let xs = [1, 42] +xs.1 +//│ Wasm result: +//│ = 42 + +// Tuple writes use the same zero-based indexed representation. +:expect 42 +let xs = mut [1, 2] +set xs.0 = 42 +xs.0 +//│ Wasm result: +//│ = 42 + +// Calling a function loaded from a tuple still needs WASM callable representation support. +:fixme +:expect 42 +fun increment(x: Int): Int = x + 1 +[increment].0(41) +//│ ╔══[COMPILATION ERROR] Expected WAT of `fun` expression in Call(...) to have a `(ref )` type +//│ ║ l.47: [increment].0(41) +//│ ╙── ^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/Erasure.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/Erasure.mls index 0a01c63a1d..7fdea1dddf 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/Erasure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/Erasure.mls @@ -1,8 +1,6 @@ :. -:global -:wasm // Erasure must prepare an unboxed call target before lowering the caller's body. diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls index 721ca87d6e..8d6333fd71 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmBasics.mls @@ -1,7 +1,5 @@ :. -:global -:wasm // WASM lowering emits target-specific intrinsics; do not execute its IR as JS. diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls index 61173bd1d9..48a2d50722 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls @@ -1,8 +1,6 @@ :. -:global -:wasm :expect 3 From f187f26c4b125b0ba1bfb02d54545677c4a8f5fa Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 13:28:58 +0800 Subject: [PATCH 066/228] Expose Array interfaces on tuple shapes and migrate unblocked tests --- doc/new-resolution-suite-migration.md | 307 ++++++++++-------- .../scala/hkmc2/semantics/NewResolver.scala | 101 ++++-- .../main/scala/hkmc2/semantics/Shape.scala | 13 +- .../scala/hkmc2/semantics/TypeShape.scala | 4 + .../test/mlscript/basics/DynamicSelection.mls | 21 +- .../test/mlscript/basics/ExplicitLabels.mls | 34 +- .../test/mlscript/basics/LiteralSelection.mls | 27 +- .../src/test/mlscript/basics/MutArr.mls | 1 + .../src/test/mlscript/codegen/Arrays.mls | 30 +- .../test/mlscript/codegen/BlockPrinter.mls | 37 ++- .../codegen/CurriedClassInheritance.mls | 69 +++- .../src/test/mlscript/codegen/Inliner.mls | 1 + .../shared/src/test/mlscript/codegen/Misc.mls | 19 +- .../test/mlscript/codegen/NestedScoped.mls | 11 +- .../test/mlscript/codegen/PlainClasses.mls | 48 +-- .../src/test/mlscript/codegen/TraceLog.mls | 1 + .../src/test/mlscript/decls/Prelude.mls | 25 +- .../src/test/mlscript/newres/Arrays.mls | 109 +++++++ .../test/mlscript/ups/regex/EmailAddress.mls | 1 + .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 20 files changed, 606 insertions(+), 257 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/Arrays.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 7435efcad5..816c1eec36 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -5,19 +5,21 @@ The first pass tried all 372 active `.mls` files in `basics`, `codegen`, `ucs`, `ups`, and `apps` with new resolution and strict resolution, retaining 185 ports. After dynamic shapes and non-strict suite defaults became available, all 187 -deferred worksheets were retried. This pass migrates another 54, bringing the -count to 239 of 372 (64%). The 51 files under `ucs/staging` are excluded by the -test runner and remain unchanged. Counts below exclude shared `.mls` +deferred worksheets were retried, bringing the count to 239. After declared +interfaces, generic flow, tuple projections, and Array inheritance were implemented, +all 133 remaining worksheets (plus the two deferred WASM worksheets) were retried. +This pass migrates another 13, bringing the count to 252 of 372 (68%). The 51 files under +`ucs/staging` are excluded by the test runner and remain unchanged. Counts below exclude shared `.mls` configuration files and new-resolution regression tests. | Suite | Migrated | Retained on existing configuration | Active files | | --- | ---: | ---: | ---: | -| basics | 56 | 34 | 90 | -| codegen | 78 | 47 | 125 | +| basics | 60 | 30 | 90 | +| codegen | 86 | 39 | 125 | | ucs | 52 | 18 | 70 | -| ups | 48 | 23 | 71 | +| ups | 49 | 22 | 71 | | apps | 5 | 11 | 16 | -| Total | 239 | 133 | 372 | +| Total | 252 | 120 | 372 | Migrated files start with `:.`. Each suite's `.mls` loads the common language configuration and uses its non-strict default; nested directories inherit through @@ -29,19 +31,33 @@ targets and known invalid operations. The common language configuration disables JS execution, so each file retains its original execution flags. A parser-only test must not acquire runtime execution merely because its resolution configuration changes. -The additional ports comprise one basics test (`ObjectExtensions`), 16 codegen +The previous batch comprised one basics test (`ObjectExtensions`), 16 codegen tests, two UCS compiled-pattern tests, and 35 UPS tests. They include JavaScript imports, spread calls, qualified constructors, and recursive, parametric, and fixed-point patterns. Five new UPS directory configurations connect these tests to the common language configuration. -Forty-nine files needed only the configuration header and refreshed goldens. +In that batch, forty-nine files needed only the configuration header and refreshed goldens. `BadThis`, `BasicTerms`, `Spreads`, and `ImportMLs` additionally expect static errors for invalid operations already present in their negative cases; no existing error expectations were removed. `Throw` uses `new Error(...)`, preserving its throw/catch results. Tests of foreign callable classes remain deferred rather than being rewritten to avoid the capability under test. +The latest ports are: + +- basics: `DynamicSelection`, `ExplicitLabels`, `LiteralSelection`, `MutArr`. +- codegen: `Arrays`, `BlockPrinter`, `CurriedClassInheritance`, `Inliner`, `Misc`, + `NestedScoped`, `PlainClasses`, `TraceLog`. +- UPS: `regex/EmailAddress`. + +Nine need only the configuration header and refreshed goldens. `ExplicitLabels`, +`CurriedClassInheritance`, `PlainClasses`, and `Arrays` additionally expect compilation errors +for existing negative cases now rejected statically: a shadowed label's missing +`break`, invalid constructor overapplication/member selections, and an empty call +on a plain class, and known out-of-bounds tuple reads. Their existing runtime-error +expectations remain in place. + The two remaining WASM worksheets were also retried; both remain deferred. WASM therefore remains at 19 migrated worksheets out of 21, outside the table above. @@ -61,8 +77,8 @@ this worksheet migration. The annotated-receiver capture assertion in WASM `Basics` is fixed; a later unannotated `this.x` initializer still blocks that worksheet (see task 3). Method calls across REPL blocks remain assigned to the other branch. -Constructor-pattern field propagation also has a serious context-mixing blocker, -documented below; its `:fixme` regression is not evidence that migration is complete. +The constructor-pattern context-mixing regression now passes; the remaining +pattern-transfer gaps are listed below. ## Straightforward fixes applied @@ -78,8 +94,9 @@ documented below; its `:fixme` regression is not evidence that migration is comp 4. Type applications propagate the underlying shape, preserving the requested interpretation and capture handling. Class validation and lowering look through erased type arguments. `newres/TypeApplications.mls` checks generic - construction, inheritance, and function-result member lookup. This does not - implement generic substitution or missing type-argument validation. + construction, inheritance, and function-result member lookup. Generic substitution + and constraint flow are now implemented as described in task 3; type-argument + validation is still incomplete (`basics/GenericClasses.mls`). 5. Pattern compilation annotations forward binding-shape propagation to the annotated pattern. Remaining pattern forms and matcher synthesis are separate work, described below. @@ -100,7 +117,22 @@ explicitly bound `Some5` wins over a wildcard open, yielding `"555"` rather than Record fields now have their own `BlockMemberSymbol`s. Member lookup follows record overwrites and spreads in source order and retains the selected value's -shape and captures. Structural tuple member lookup remains unimplemented. +shape and captures. Tuples retain precise zero-based element projections and expose +the builtin Array class as their parent. This supports inherited members, Array +patterns, and inference of Array element type arguments. The parent uses the union +of element shapes, preserving both annotations and call-context marks. + +The prelude now declares Array's common instance methods and their result +interfaces, and identifies `Array.prototype` as an Array value. Callback parameter flow supports rest parameters receiving the +remaining arguments; Array callbacks receive the element, index, and array. +Imported legacy signatures preserve substituted parameters across the member's +lexical boundary. Mutable arrays discard their initial element shapes and length, +because methods such as `push` and `reverse` can change both. Unknown layouts still +permit indexed access with an unknown result, without authorizing arbitrary members. + +Array declarations remain deliberately incomplete: `map` and `concat` return +`Array[Any]`; polymorphic method results and richer foreign signatures need further +work. This does not infer result precision from an opaque annotation. **Accepted policy:** JavaScript imports (including package imports), `globalThis`, and explicit dynamic selection/instantiation introduce `DynShape`. Ordinary @@ -128,10 +160,10 @@ requirements for a known, unambiguous identity. record reads, and other losses of inference precision do not automatically license dynamic member lookup. A known missing member in another receiver candidate still reports an error even if a dynamic candidate is also present. -The constructor-pattern context-mixing blocker below therefore remains visible. +The same rule applies to values recovered from constructor patterns. Regression coverage is in `newres/Dynamic.mls`, `newres/Records.mls`, -`newres/SpreadCalls.mls`, and `newres/loose/Targets.mls`. +`newres/SpreadCalls.mls`, `newres/Arrays.mls`, and `newres/loose/Targets.mls`. ### 2. Pattern transfer and synthesized references @@ -170,8 +202,9 @@ its new-resolution trial, but retains unsupported member and pattern cases Evidence: the prior WASM `Basics` failure mixes a nominal annotated receiver with an untyped constructor field carrying a different capture context. -`codegen/CurriedClassInheritance.mls` and `ParamClasses.mls` also expose capture -or receiver-shape problems; they are not all necessarily the same bug. +`codegen/CurriedClassInheritance.mls` now migrates after updating its existing +negative expectations; `ParamClasses.mls` still exposes constructor-value member +lookup rather than a remaining annotation-opacity decision. **Implemented:** annotations are opaque shapes. `b: Base` exposes Base's declared members and preserves virtual dispatch; an implementation or observed argument @@ -207,10 +240,9 @@ anonymous-function lowering. REPL method cases remain assigned to the other bran The original annotated-receiver cases from WASM `Basics` now pass and are covered by `newres/wasm/DeclaredTypes.mls`. A fresh trial reaches a later capture assertion in the unannotated initializer `class Foo(val x) with val y = this.x`. -`CurriedClassInheritance` still expects runtime failures for operations now rejected -statically; `ParamClasses` still reaches unimplemented constructor-value member -lookup (`Foo.class`). All three worksheets retain their existing configurations -and goldens until these separate migration blockers are addressed. +`CurriedClassInheritance` now expects the earlier compilation errors as well as +its existing runtime errors. `ParamClasses` still reaches unimplemented +constructor-value member lookup (`Foo.class`); it and WASM `Basics` remain deferred. ### 4. Dynamic construction and foreign callable classes @@ -272,6 +304,22 @@ quadratic in its depth. There is no truncation or depth limit. These changes also fix the sibling-subclass field-extraction regression in `newres/ConstructorFieldRecovery.mls`, whose independent results are now checked. +### What the latest trials now reach + +- `basics/MiscArrayTests`: `reverse` and the correctly shaped `map` callbacks work. + Its unary callback now fails statically, as well as at runtime; the later `map` + function still needs parameter flow from a subsequent REPL block. +- `codegen/FirstClassFunctionTransform`: tuple-held calls work, making three old + error expectations obsolete, but later closure cases still report an unfinished + `foo` definition. The worksheet is not migrated by merely removing expectations. +- `codegen/SetStmt`: the first `concat` resolves; update-assignment argument flow + and reassigned aggregate shapes remain incomplete. +- `codegen/ObjectMethodDebinding`: `id(Test).foo` still has no resolved target. + Adding an expected error would not establish the intended debinding diagnostic. +- Imported application results still lack the required interfaces: CSV's nested + tuple selection, Iter's `reverse`, and parser collections' `sort`/`join` remain + unresolved. Adding Array members cannot manufacture missing receiver shapes. + ### Other implementation work - Audit assignments to member symbols and definition initializers. The trial @@ -280,9 +328,10 @@ These changes also fix the sibling-subclass field-extraction regression in - Add transfer rules for result-bearing control flow, handlers, and quotation where the resolver currently has no shape rule. Preserve the established capture model and the boundary between elaboration and lowering. -- Diagnose cyclic wildcard/module forwarding without recursive listener - registration overflowing the stack; `basics/CyclicModuleForwarders.mls` is - a small existing reproducer. +- Review module-forwarding compatibility in `basics/CyclicModuleForwarders.mls`. + The latest trial no longer overflows the stack; it rejects forwarding between + the distinct nominal module types `M1` and `M2`. Do not discard its old expected + outcomes without deciding the intended module compatibility rule. - Preserve source origins in synthesized references and diagnostics. Some migrated error cases now report a definition rather than a use site, or lose a location. In particular, `ups/UpsBugsBacklog.mls` and @@ -301,17 +350,16 @@ These changes also fix the sibling-subclass field-extraction regression in than propagating the generator body's return shape. The earlier generator timeout was not reproduced. 3. **Complete structural targets and external signatures.** Dynamic JS values and - record fields are supported; finish tuple members, mutation, primitive members, - and foreign declarations in small batches. Re-run affected negative tests as + record fields, tuple projections, and inherited Array members are supported; + finish mutation flow, primitive members, and foreign declarations in small batches. Re-run affected negative tests as well as successful programs. 4. **Complete pattern transfers and reference preservation.** Start with UCS conjunction/record/tuple cases, then the remaining recursive and transforming UPS cases. Keep fixed-point behavior tested independently from matcher code - generation. Resolve the constructor-pattern context-mixing - blocker above before treating nominal field extraction as complete. -5. **Complete type/interface and call validation.** Resolve the pending interface - question, finish declared result/field shapes, port module/generic checks, and - finish the WASM migration. Keep cross-block method work coordinated externally. + generation. Preserve the now-passing constructor-field context regressions. +5. **Complete type/interface and call validation.** Preserve opaque annotations + and marked generic flow; finish generic validation, polymorphic imported methods, + module checks, and the WASM migration. Keep cross-block method work coordinated externally. 6. **Migrate shared compilation fixtures from leaves upward.** Begin with the four UPS fixtures; then application parsing data types, lexer, parser helpers, and entry points. Rebuild `.mjs` dependencies before each worksheet batch. @@ -326,9 +374,9 @@ These changes also fix the sibling-subclass field-extraction regression in - `ctest`: all 46 selected compilation tests pass. - `catest`: all 20 application compilation fixtures pass in their existing mode. - `cwtest`: the WASM compilation fixture passes. -- `hkmc2AllTests/test`: all tests pass, including 700 main tests, 18 application - tests, and 22 WASM tests. The main count includes the five new UPS directory - configurations; it does not mean that 700 worksheets have been migrated. +- `hkmc2AllTests/test`: all 943 tests pass, including 707 main tests, 18 application + tests, and 22 WASM tests. The main count includes directory configurations and + new-resolution regressions; it is not a count of migrated worksheets. - All retained ports have reviewed goldens. Deferred worksheets retain their original source and output; no trial-generated application report changes remain. @@ -341,152 +389,139 @@ The files themselves retain the pre-trial configuration and output. ### basics - `basics/BadAssignments.mls`: Unexpected exception; scala.NotImplementedError: SelfRef(globalThis:globalThis) (of class SelfRef) -- `basics/BadModuleUses.mls`: Unexpected lack of compilation or type error after `fun foo(): module null = M` -- `basics/BadOverloading.mls`: Unexpected lack of compilation or type error after `fun Foo = 1` +- `basics/BadModuleUses.mls`: Unexpected lack of compilation or type error after `module Example with` +- `basics/BadOverloading.mls`: Unexpected lack of compilation or type error after `class Foo` - `basics/BadTypeClasses.mls`: Unexpected lack of compilation or type error after `M.f` -- `basics/Classes.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'id' has no resolved target -- `basics/CompanionModules_Classes.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'C' cannot be called like a function. -- `basics/CompanionModules_Functions.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'foo' cannot be called like a function. -- `basics/CyclicModuleForwarders.mls`: Unexpected exception; java.lang.StackOverflowError -- `basics/DynamicFields.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class constructor 'DynCtor' does not contain member 'dynField' +- `basics/Classes.mls`: Unexpected compilation error; This selection of member 'id' has no resolved target +- `basics/CompanionModules_Classes.mls`: Unexpected compilation error; Resolution error in application; Module 'C' cannot be called like a function. +- `basics/CompanionModules_Functions.mls`: Unexpected compilation error; Resolution error in application; Module 'foo' cannot be called like a function. +- `basics/CyclicModuleForwarders.mls`: Unexpected compilation error; Cannot use a value of type 'module M1' at an unrelated type 'module M2' +- `basics/DynamicFields.mls`: Unexpected compilation error; Resolution error in selection; Instance of class constructor 'DynCtor' does not contain member 'dynField' - `basics/DynamicInstantiation.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) -- `basics/DynamicSelection.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) -- `basics/ExplicitLabels.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Integer literal does not contain member 'break' -- `basics/FunDefs.mls`: Unexpected exception; java.lang.StackOverflowError +- `basics/FunDefs.mls`: Unexpected compilation error; Resolution error in application; Value of type 'Int' cannot receive more argument lists. - `basics/GenericClasses.mls`: Unexpected lack of compilation or type error after `class Foo[A](x: Foo[A, A])` - `basics/Inheritance.mls`: Unexpected lack of error to fix after `[Bar.x, Bar.foo]` -- `basics/LiteralSelection.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) -- `basics/MiscArrayTests.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `basics/MiscArrayTests.mls`: Unexpected compilation error; Resolution error in function type; Callback parameter list does not match its declared function type. - `basics/ModuleMethods.mls`: Unexpected lack of compilation or type error after `fun f(m: M)` -- `basics/MultiParamListClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Instance of class constructor 'Foo' cannot receive more argument lists. -- `basics/MutArr.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) -- `basics/MutRcd.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Record literal does not contain member 'foo' +- `basics/MultiParamListClasses.mls`: Unexpected compilation error; Resolution error in application; Instance of class constructor 'Foo' cannot receive more argument lists. +- `basics/MutRcd.mls`: Unexpected compilation error; Resolution error in selection; Record literal does not contain member 'foo' - `basics/MutVal.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `basics/New.mls`: Unexpected lack of warnings after `Foo` -- `basics/NewMut.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class 'Foo' does not contain member 'y' -- `basics/NewlineOps.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'length' has no resolved target -- `basics/NewlineSels.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'c' has no resolved target -- `basics/OpenIn.mls`: Unexpected compilation error; [COMPILATION ERROR] Builtin '~' is not a binary operator -- `basics/Overloading.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'Foo' cannot be called like a function. -- `basics/PrefixOps.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Bin' +- `basics/New.mls`: Unexpected lack of warnings after `new` +- `basics/NewMut.mls`: Unexpected compilation error; Resolution error in selection; Instance of class 'Foo' does not contain member 'y' +- `basics/NewlineOps.mls`: Unexpected compilation error; This selection of member 'length' has no resolved target +- `basics/NewlineSels.mls`: Unexpected compilation error; This selection of member 'c' has no resolved target +- `basics/OpenIn.mls`: Unexpected compilation error; Builtin '~' is not a binary operator +- `basics/Overloading.mls`: Unexpected compilation error; Resolution error in application; Module 'Foo' cannot be called like a function. +- `basics/PrefixOps.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Bin' - `basics/Puns.mls`: Unexpected exception; scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(x))))) (of class Record) -- `basics/Records.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'repeat' +- `basics/Records.mls`: Unexpected compilation error; Resolution error in selection; String literal does not contain member 'repeat' - `basics/StrTest.mls`: Unexpected lack of compilation or type error after `(~)("a")` -- `basics/Underscores.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'f' has no resolved target -- `basics/ValMemberSymbols.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Integer literal cannot receive more argument lists. +- `basics/Underscores.mls`: Unexpected compilation error; This selection of member 'f' has no resolved target +- `basics/ValMemberSymbols.mls`: Unexpected compilation error; This selection of member 'x' has no resolved target ### codegen -- `codegen/Arrays.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) - `codegen/AuxiliaryConstructors.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `codegen/BadOpen.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Module 'Foo' does not contain member 'y' -- `codegen/BlockPrinter.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `codegen/BadOpen.mls`: Unexpected compilation error; Resolution error in selection; Module 'Foo' does not contain member 'y' - `codegen/ClassMatching.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) -- `codegen/ConfigDirective.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'call' has no resolved target -- `codegen/ConsoleLog.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'log' has no resolved target -- `codegen/CurriedClassInheritance.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Instance of class constructor 'Bar' cannot receive more argument lists. +- `codegen/ConfigDirective.mls`: Unexpected compilation error; This selection of member 'call' has no resolved target +- `codegen/ConsoleLog.mls`: Unexpected compilation error; This selection of member 'log' has no resolved target - `codegen/CurriedClasses.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) -- `codegen/ErasedTypes.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class definition 'Function' cannot be called like a function. -- `codegen/FirstClassFunctionTransform.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) -- `codegen/Generators.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'next' has no resolved target -- `codegen/Getters.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'oops' has no resolved target -- `codegen/Hygiene.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'foo' +- `codegen/ErasedTypes.mls`: Unexpected compilation error; Resolution error in application; Value of type 'Function' cannot be called like a function. +- `codegen/FirstClassFunctionTransform.mls`: Unexpected lack of compilation error after `bar([foo].0)` +- `codegen/Generators.mls`: Unexpected compilation error; This selection of member 'next' has no resolved target +- `codegen/Getters.mls`: Unexpected compilation error; This selection of member 'oops' has no resolved target +- `codegen/Hygiene.mls`: Unexpected compilation error; Resolution error in selection; String literal does not contain member 'foo' - `codegen/ImportExample.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `codegen/ImportedOps.mls`: Unexpected compilation error; [COMPILATION ERROR] Builtin '~' is not a binary operator -- `codegen/Inliner.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) -- `codegen/Misc.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) +- `codegen/ImportedOps.mls`: Unexpected compilation error; Builtin '~' is not a binary operator - `codegen/ModuleMethods.mls`: Unexpected lack of compilation or type error after `Example |>. s(123)` -- `codegen/Modules.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'None' cannot be called like a function. -- `codegen/NestedClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'x' has no resolved target -- `codegen/NestedScoped.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) -- `codegen/NoFreeze.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'x' has no resolved target +- `codegen/Modules.mls`: Unexpected compilation error; Resolution error in application; Module 'None' cannot be called like a function. +- `codegen/NestedClasses.mls`: Unexpected compilation error; This selection of member 'x' has no resolved target +- `codegen/NoFreeze.mls`: Unexpected compilation error; This selection of member 'x' has no resolved target - `codegen/NoModuleCheck.mls`: Unexpected lack of compilation or type error after `M."foo"(1)` -- `codegen/ObjectMethodDebinding.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'foo' has no resolved target -- `codegen/Open.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; String literal does not contain member 'length' -- `codegen/OpenWildcard.mls`: Unexpected compilation error; [COMPILATION ERROR] Wildcard-open reference 'None' is ambiguous +- `codegen/ObjectMethodDebinding.mls`: Unexpected compilation error; This selection of member 'foo' has no resolved target +- `codegen/Open.mls`: Unexpected compilation error; Resolution error in selection; String literal does not contain member 'length' +- `codegen/OpenWildcard.mls`: Unexpected compilation error; Wildcard-open reference 'None' is ambiguous; candidate: object 'None' defined here - `codegen/ParamClasses.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) -- `codegen/PartialApps.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Tuple literal cannot receive more argument lists. -- `codegen/PlainClasses.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class value cannot receive more argument lists. -- `codegen/PredefUsage.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot be called like a function. -- `codegen/PrivateMembers.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'y' has no resolved target -- `codegen/Pwd.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'pop' has no resolved target -- `codegen/Quasiquotes.mls`: Unexpected compilation error; [COMPILATION ERROR] Unsupported quasiquote type member reference +- `codegen/PartialApps.mls`: Unexpected compilation error; Resolution error in application; Tuple literal cannot receive more argument lists. +- `codegen/PredefUsage.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot be called like a function. +- `codegen/PrivateMembers.mls`: Unexpected compilation error; This selection of member 'y' has no resolved target +- `codegen/Pwd.mls`: Unexpected compilation error; This selection of member 'pop' has no resolved target +- `codegen/Quasiquotes.mls`: Unexpected compilation error; Unsupported quasiquote type member reference - `codegen/RandomStuff.mls`: Unexpected exception; java.lang.StackOverflowError - `codegen/ReboundLet.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `codegen/RuntimeUsage.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Class value does not contain member 'leaveOut' -- `codegen/SanityChecks.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function literal expected 2 arguments, but got 1 -- `codegen/ScopedBlocks.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'Foo' cannot receive more argument lists. -- `codegen/ScopedBlocksAndHandlers.mls`: Unexpected exception; shape propagation for `Handle` is unimplemented. +- `codegen/RuntimeUsage.mls`: Unexpected compilation error; Resolution error in selection; Class value does not contain member 'leaveOut' +- `codegen/SanityChecks.mls`: Unexpected compilation error; Resolution error in application; Function literal expected 2 arguments, but got 1 +- `codegen/ScopedBlocks.mls`: Unexpected compilation error; Resolution error in application; Module 'Foo' cannot receive more argument lists. +- `codegen/ScopedBlocksAndHandlers.mls`: Unexpected exception; shape propagation for handler expressions is unimplemented. - `codegen/Scoping.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `codegen/SelfReferences.mls`: Unexpected lack of compilation or type error after `val self = this` -- `codegen/SetStmt.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) -- `codegen/ThisCallVariations.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'call' has no resolved target +- `codegen/SelfReferences.mls`: Unexpected lack of compilation or type error after `module Foo with` +- `codegen/SetStmt.mls`: Unexpected compilation error; This selection of member 'concat' has no resolved target +- `codegen/ThisCallVariations.mls`: Unexpected compilation error; This selection of member 'call' has no resolved target - `codegen/ThisCalls.mls`: Unexpected lack of compilation or type error after `Example |>. g(123)` -- `codegen/TraceLog.mls`: Unexpected exception; java.lang.StackOverflowError -- `codegen/While.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Integer literal cannot be called like a function. +- `codegen/While.mls`: Unexpected compilation error; Resolution error in application; Integer literal cannot be called like a function. ### ucs -- `ucs/examples/BinarySearchTree.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. +- `ucs/examples/BinarySearchTree.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot receive more argument lists. - `ucs/examples/EitherOrBoth.mls`: Unexpected exception; java.lang.Exception: Internal Error: Synthetic selections must not enter new resolution -- `ucs/examples/LeftistTree.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. -- `ucs/examples/ListFold.mls`: Unexpected compilation error; [COMPILATION ERROR] Builtin '~' is not a binary operator -- `ucs/examples/ULC.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. +- `ucs/examples/LeftistTree.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot receive more argument lists. +- `ucs/examples/ListFold.mls`: Unexpected compilation error; Builtin '~' is not a binary operator +- `ucs/examples/ULC.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot receive more argument lists. - `ucs/general/BooleanPatterns.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Negation(Literal(IntLit(2))),Negation(Literal(IntLit(3)))) (of class Composition) -- `ucs/hygiene/HygienicBindings.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class value cannot receive more argument lists. -- `ucs/normalization/Deduplication.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'length' has no resolved target +- `ucs/hygiene/HygienicBindings.mls`: Unexpected compilation error; Resolution error in application; Class value cannot receive more argument lists. +- `ucs/normalization/Deduplication.mls`: Unexpected compilation error; This selection of member 'length' has no resolved target - `ucs/normalization/InheritanceNormalization.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:B),None)) (of class Composition) -- `ucs/normalization/OverlapOfPrimitives.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Module 'String' cannot receive more argument lists. +- `ucs/normalization/OverlapOfPrimitives.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot receive more argument lists. - `ucs/normalization/RecordImpliedByClass.mls`: Unexpected exception; scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(av))), (Ident(b),Alias(Wildcard(),Ident(bv))))) (of class Record) - `ucs/patterns/BooleansOps.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Alias(Wildcard(),Ident(a)),Alias(Wildcard(),Ident(b))) (of class Composition) - `ucs/patterns/ConjunctionPattern.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:A),None)),Constructor(MemberRef(member:B),None)) (of class Composition) - `ucs/patterns/RecordPattern.mls`: Unexpected exception; scala.NotImplementedError: Record(List((Ident(x),Alias(Wildcard(),Ident(a))), (Ident(y),Alias(Wildcard(),Ident(b))))) (of class Record) - `ucs/patterns/String.mls`: Unexpected exception; scala.NotImplementedError: Concatenation(Literal(StrLit(0x)),Alias(Wildcard(),Ident(body))) (of class Concatenation) -- `ucs/patterns/where.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'get' has no resolved target -- `ucs/syntax/Else.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class 'Set' does not contain member 'has' -- `ucs/syntax/SimpleUCS.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'get' has no resolved target +- `ucs/patterns/where.mls`: Unexpected compilation error; This selection of member 'get' has no resolved target +- `ucs/syntax/Else.mls`: Unexpected compilation error; Resolution error in selection; Instance of class 'Set' does not contain member 'has' +- `ucs/syntax/SimpleUCS.mls`: Unexpected compilation error; This selection of member 'get' has no resolved target ### ups - `ups/EmptyJunctions.mls`: Unexpected exception; scala.NotImplementedError: Range(IntLit(5),IntLit(3),true) (of class Range) -- `ups/Future.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Test' -- `ups/LocalPatterns.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Expected a term; got pattern 'Zero' -- `ups/MatchResult.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Cross' +- `ups/Future.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Test' +- `ups/LocalPatterns.mls`: Unexpected compilation error; Resolution error in selection; Expected a term; got pattern 'Zero' +- `ups/MatchResult.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Cross' - `ups/RecursiveTransformations.mls`: Unexpected exception; scala.NotImplementedError: Negation(Constructor(Capture(MemberRef(member:Bin),term:f),None)) (of class Negation) -- `ups/SimpleTransform.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function 'area' expected 1 argument, but got 2 -- `ups/examples/BasicSeqStackParse.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'at' has no resolved target -- `ups/examples/Computation.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'padStart' has no resolved target -- `ups/examples/DoubleTripleList.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'head' has no resolved target +- `ups/SimpleTransform.mls`: Unexpected compilation error; Resolution error in application; Function 'area' expected 1 argument, but got 2 +- `ups/examples/BasicSeqStackParse.mls`: Unexpected compilation error; This selection of member 'at' has no resolved target +- `ups/examples/Computation.mls`: Unexpected compilation error; This selection of member 'padStart' has no resolved target +- `ups/examples/DoubleTripleList.mls`: Unexpected compilation error; This selection of member 'head' has no resolved target - `ups/examples/EvaluationContext.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) - `ups/examples/EvaluationContext2.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `ups/examples/HindleyMilner.mls`: The constructor-context timeout is fixed; the new-resolution trial finishes with unsupported member-variable shapes and pattern/member errors (see the recheck above). -- `ups/examples/ListPredicates.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'length' has no resolved target -- `ups/examples/Negation.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function 'flatten' expected 1 argument, but got 3 -- `ups/examples/PrecedenceClimbStackParse.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'ParseStep' -- `ups/examples/Record.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Function 'bmi' expected 1 argument, but got 2 -- `ups/fixpoint/ListFusion.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target -- `ups/regex/EmailAddress.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.TupleShape.getMemberImpl`) -- `ups/regex/Identifier.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'forEach' has no resolved target -- `ups/regex/Separation.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Integer' -- `ups/regex/TailRepetition.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'Zero' -- `ups/syntax/InterestingPatterns.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in member reference; Expected a term; got pattern 'TreeDepth' -- `ups/syntax/PatternBody.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target +- `ups/examples/HindleyMilner.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) +- `ups/examples/ListPredicates.mls`: Unexpected compilation error; This selection of member 'length' has no resolved target +- `ups/examples/Negation.mls`: Unexpected compilation error; Resolution error in application; Function 'flatten' expected 1 argument, but got 3 +- `ups/examples/PrecedenceClimbStackParse.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'ParseStep' +- `ups/examples/Record.mls`: Unexpected compilation error; Resolution error in application; Function 'bmi' expected 1 argument, but got 2 +- `ups/fixpoint/ListFusion.mls`: Unexpected compilation error; This selection of member 'toString' has no resolved target +- `ups/regex/Identifier.mls`: Unexpected compilation error; This selection of member 'forEach' has no resolved target +- `ups/regex/Separation.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Integer' +- `ups/regex/TailRepetition.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Zero' +- `ups/syntax/InterestingPatterns.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'TreeDepth' +- `ups/syntax/PatternBody.mls`: Unexpected compilation error; This selection of member 'toString' has no resolved target ### apps - `apps/AccountingTest.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `apps/CSVTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member '1' has no resolved target -- `apps/IterTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'reverse' has no resolved target -- `apps/parsing-web-demo/ExamplesTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'sort' has no resolved target -- `apps/parsing/DirectiveTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'display' has no resolved target -- `apps/parsing/LeftRecursion.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'display' has no resolved target -- `apps/parsing/LexerTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'join' has no resolved target -- `apps/parsing/ParseRuleVisualizerTest.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in application; Class value cannot receive more argument lists. -- `apps/parsing/PrattParsingTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target -- `apps/parsing/RecursiveDescentTest.mls`: Unexpected compilation error; [COMPILATION ERROR] This selection of member 'toString' has no resolved target +- `apps/CSVTest.mls`: Unexpected compilation error; This selection of member '1' has no resolved target +- `apps/IterTest.mls`: Unexpected compilation error; This selection of member 'reverse' has no resolved target +- `apps/parsing-web-demo/ExamplesTest.mls`: Unexpected compilation error; This selection of member 'sort' has no resolved target +- `apps/parsing/DirectiveTest.mls`: Unexpected compilation error; This selection of member 'display' has no resolved target +- `apps/parsing/LeftRecursion.mls`: Unexpected compilation error; This selection of member 'display' has no resolved target +- `apps/parsing/LexerTest.mls`: Unexpected compilation error; This selection of member 'join' has no resolved target +- `apps/parsing/ParseRuleVisualizerTest.mls`: Unexpected compilation error; Resolution error in application; Class value cannot receive more argument lists. +- `apps/parsing/PrattParsingTest.mls`: Unexpected compilation error; This selection of member 'toString' has no resolved target +- `apps/parsing/RecursiveDescentTest.mls`: Unexpected compilation error; This selection of member 'toString' has no resolved target - `apps/parsing/RulesTest.mls`: Unexpected exception; java.lang.Exception: Internal Error: Synthetic selections must not enter new resolution ### wasm -- `wasm/Basics.mls`: The annotated-receiver failure is fixed. A new trial fails in `val y = this.x` of `class Foo(val x)` with `Entry and exit cross different lexical resolution scopes`. -- `wasm/Binaryen.mls`: Unexpected compilation error; [COMPILATION ERROR] Resolution error in selection; Instance of class 'Instance' does not contain member 'exports' +- `wasm/Basics.mls`: Unexpected exception; java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes +- `wasm/Binaryen.mls`: Unexpected compilation error; Resolution error in selection; Instance of class 'Instance' does not contain member 'exports' diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index a288bfc933..7acd8707de 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -99,6 +99,8 @@ class NewResolver: case UnitVal() => result.publish(TypeShape.Unit) case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => result.publish(TypeShape.Parameter(sym)) + case Ref(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => + result.publish(TypeShape.Parameter(sym)) case Tup(fields) if fields.forall(_.isInstanceOf[Fld]) => result.publish(TypeShape.Tuple(fields.collect { case Fld(_, sign, _) => typeResolution(sign) })) case _: WildcardTy | _: Neg | _: Rcd | _: Tup | _: Lit | Missing | Error() => @@ -173,6 +175,7 @@ class NewResolver: def next(res: TypeResolution): Unit = follow(declaredType(res, current.bindings), Nil, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) + case TypeShape.Inferred(value) => publish(value) case TypeShape.Nominal(defn) => val bindings = bind(defn.tparams) defn.ext match @@ -219,7 +222,7 @@ class NewResolver: S(declaredType(ret, current.bindings)))) case TypeShape.Tuple(fields) => publish(TupleShape(current.resolution.source, - fields.map(field => TupleShape.TypedField(declaredType(field, current.bindings), Nil)))) + fields.map(field => TupleShape.TypedField(declaredType(field, current.bindings), Nil)))(this)) case TypeShape.Union(left, right) => next(left); next(right) case TypeShape.Intersection(left, right) => next(left); next(right) case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) @@ -249,6 +252,14 @@ class NewResolver: case S(p: Param) => p.sign case _ => td.resultSignature + private val capturedTypes = mutable.Map.empty[(DeclaredType, TermSymbol), DeclaredType] + private def captureType(bound: DeclaredType, scope: TermSymbol): DeclaredType = + capturedTypes.getOrElseUpdate((bound, scope), { + val captured = new TypeResolution(bound.resolution.source, bound.resolution.fail) + captured.publish(TypeShape.Captured(bound.resolution, scope)) + declaredType(captured, bound.bindings) + }) + private def listenDeclaredMember(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], flow: FlowSymbol, source: Term, selected: DefinitionSymbol[?] => Unit)(listener: TermShape => Unit): Unit = member.onComplete: () => @@ -265,13 +276,24 @@ class NewResolver: exited match case value: TermShape => listener(value) case NoShape => () - val result = resultSignature(td).map(sign => declaredType(typeResolution(sign), bindings)) + def signature(sign: Term): DeclaredType = + // Legacy annotations have plain references instead of Capture nodes. + // Rebase substituted class parameters into this member's scope, just + // as a new-resolution Capture does, before the member exits it. + val legacyParameters = mutable.Set.empty[VarSymbol] + def visit(term: Term): Unit = term match + case Ref(symbol: VarSymbol) if bindings.contains(symbol) => legacyParameters += symbol + case _ => term.subTerms.foreach(visit) + if !td.tsym.decl.exists(_.isInstanceOf[Param]) then visit(sign) + val scopedBindings = bindings.map: (symbol, bound) => + symbol -> (if legacyParameters(symbol) then captureType(bound, td.tsym) else bound) + declaredType(typeResolution(sign), scopedBindings) + val result = resultSignature(td).map(signature) if td.sign.nonEmpty && (td.k is syntax.Fun) && !td.flags.hasResultAnnotation then - listenTypeValues(declaredType(typeResolution(td.sign.get), bindings))(publish) + listenTypeValues(signature(td.sign.get))(publish) else if td.params.nonEmpty then publish(CallableTypeShape(source, - td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(sign => - declaredType(typeResolution(sign), bindings))), ps.restParam.nonEmpty)), result)) + td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), ps.restParam.nonEmpty)), result)) else result match case S(tpe) => listenTypeValues(tpe)(publish) case N => publish(UnknownValueShape(source)) @@ -356,15 +378,17 @@ class NewResolver: case _ => softAssert(false, "A type parameter received a symbolic overload set") param.sym.shapeListeners += receive param.sym.shapes.foreach(receive) + def constrainNominal(nominal: NominalTypeShape): Unit = + if nominal.defn is cls then cls.tparams.zip(args).foreach: (param, pattern) => + nominal.bindings.get(param.sym).foreach: bound => + listenTypeValues(bound): shape => + shape.exit(context) match + case actual: TermShape => inferTypeArguments(pattern, actual, marks) + case NoShape => () head match case ds: DefnShape if ds.clsDef.contains(cls) => cls.tparams.zip(args).foreach(constrain) - case nominal: NominalTypeShape if nominal.defn is cls => - cls.tparams.zip(args).foreach: (param, pattern) => - nominal.bindings.get(param.sym).foreach: bound => - listenTypeValues(bound): shape => - shape.exit(context) match - case actual: TermShape => inferTypeArguments(pattern, actual, marks) - case NoShape => () + case nominal: NominalTypeShape => constrainNominal(nominal) + case tuple: TupleShape => constrainNominal(tuple.arrayParent) case _ => () case _ => () // Concrete annotations are opaque, irrespective of the argument value. // Alias expansion guards only unproductive cycles. Descending into a tuple, @@ -393,13 +417,24 @@ class NewResolver: case Marked(callable: CallableTypeShape, expectedContext) => remaining match case (params, _) :: tail => val declared = callable.paramLists.head - if declared.params.length != params.params.length || declared.hasRest != params.restParam.nonEmpty then + if declared.params.length < params.params.length + || (declared.params.length > params.params.length && params.restParam.isEmpty) + || (declared.hasRest && params.restParam.isEmpty) then resolError(expected.resolution.source, msg"Callback parameter list does not match its declared function type." -> actual.toLoc :: Nil) else params.params.zip(declared.params).foreach: (param, sign) => sign.foreach: tpe => listenTypeValues(tpe): shape => constrainParameter(param, shape.exit(expectedContext).enter(context), context) + params.restParam.foreach: rest => + val fields = declared.params.drop(params.params.length).map: + case S(tpe) => TupleShape.TypedField(tpe, Nil) + case N => TupleShape.UnknownField(callable.source, Nil) + val suffix = if declared.hasRest + then TupleShape.Unknown(callable.source, Nil, UnknownValueShape(callable.source)) :: Nil + else Nil + val tuple = TupleShape(callable.source, fields ::: suffix)(this) + constrainParameter(rest, tuple.exit(expectedContext).enter(context), context) callable.result.foreach: ret => tail match case Nil => results: shape => @@ -442,6 +477,28 @@ class NewResolver: callable.result.foreach(listenSignatureResult(_, count - 1)(listener)) case _ => () + private val tupleArrayParents = mutable.Map.empty[TupleShape, NominalTypeShape] + /** A tuple is an Array whose element argument is the union of its field shapes. + * Cache the parent before following fields: a recursive tuple can require its + * own Array interface while one of those fields is still being resolved. + */ + private[semantics] def tupleArrayParent(tuple: TupleShape): NominalTypeShape = + tupleArrayParents.get(tuple) match + case S(parent) => parent + case N => + val cls = prelude.builtins.Array.defn.get + softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") + val elements = new TypeResolution(tuple.source, messages => resolError(tuple.source, messages)) + val parent = NominalTypeShape(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), N) + tupleArrayParents(tuple) = parent + tuple.segments.foreach: + case field: TupleShape.Fixed => listenTupleField(field): value => + elements.publish(TypeShape.Inferred(value)) + case TupleShape.Unknown(_, marks, value) => value.exit(marks) match + case value: TermShape => elements.publish(TypeShape.Inferred(value)) + case NoShape => () + parent + private def listenTupleField(field: TupleShape.Fixed)(listener: TermShape => Unit): Unit = def receive(shape: TermShape): Unit = shape.exit(field.marks) match case value: TermShape => listener(value) @@ -518,7 +575,7 @@ class NewResolver: val suffix = drop(rest, count, false) if approximate then TupleShape.Unknown(tuple.source, Nil, UnknownValueShape(tuple.source)) :: suffix else suffix val remaining = drop(segments, expectedCount, true) - val rest = if expectedCount == 0 then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil) + val rest = if expectedCount == 0 then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil)(this) publish(expectedCount, rest.exit(marks).enter(mss)) checkArgumentArity(args, expectedCount, hasRest, src, funSh): case (tuple, marks) => matchSegments(tuple, marks) @@ -1194,10 +1251,10 @@ class NewResolver: listener(shape) case Mut(underlying: Tup) => listenTerm(underlying): case Marked(tuple: TupleShape, marks) => - val fields = tuple.segments.map: - case _: TupleShape.Fixed => TupleShape.UnknownField(trm, Nil) - case unknown: TupleShape.Unknown => unknown - TupleShape(trm, TupleShape.Rest(tuple, fields) :: Nil).exit(marks) match + // Array methods can change both the elements and the length. Retain the + // producer dependency, but none of the initializer's fixed layout. + val fields = TupleShape.Unknown(trm, Nil, UnknownValueShape(trm)) :: Nil + TupleShape(trm, TupleShape.Rest(tuple, fields) :: Nil)(this).exit(marks) match case shape: TermShape => listener(shape) case NoShape => () case shape => listener(shape) @@ -1211,7 +1268,7 @@ class NewResolver: val shape = reversed match case TupleShape.Spread(shape, NoMarks) :: Nil => shape case TupleShape.Spread(shape, marks: SomeMarks) :: Nil => MarkedShape(shape, marks) - case _ => TupleShape(tuple, reversed.reverse) + case _ => TupleShape(tuple, reversed.reverse)(this) publish(shape) case (field: Fld) :: rest => expand(rest, TupleShape.Field(field, Nil) :: reversed) case Spd(_, term) :: rest => @@ -1228,16 +1285,16 @@ class NewResolver: // Earlier candidates remain valid alternatives. A pending spread // never reaches this branch: it waits for a shape notification. val spread = if shape.containsSpread(shape.source, marks) - then TupleShape.unknown(shape.source) + then TupleShape.unknown(shape.source)(this) else shape expand(rest, TupleShape.Spread(spread, marks) :: reversed) case Marked(_: DynShape, marks) => - val spread = TupleShape(term, TupleShape.Unknown(term, Nil, DynShape()) :: Nil) + val spread = TupleShape(term, TupleShape.Unknown(term, Nil, DynShape()) :: Nil)(this) expand(rest, TupleShape.Spread(spread, marks) :: reversed) case Marked(_, marks) => // Opaque iterables (e.g. external Arrays) have no resolved // element layout. Their runtime spread is still permitted. - expand(rest, TupleShape.Spread(TupleShape.unknown(term), marks) :: reversed) + expand(rest, TupleShape.Spread(TupleShape.unknown(term)(this), marks) :: reversed) expand(tuple.fields, Nil) case record: Rcd => listenAggregate(record, listener): publish => def expand(stats: Ls[Statement], reversed: Ls[RecordShape.Element]): Unit = stats match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 576c7d6f90..b01c4f7999 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -451,7 +451,9 @@ class RefinedShape(val base: TermShape, val refinements: Ls[Str -> Term]) extend * lazy. Recursive spreads can have unknown length; retain the known fields around * them instead of discarding either those fields or the unresolved possibilities. */ -final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) extends NonAppTermShape: +final case class TupleShape(source: Term, elements: Ls[TupleShape.Element])(resolver: NewResolver) extends NonAppTermShape: + def arrayParent: NominalTypeShape = resolver.tupleArrayParent(this) + override def isInstanceOfClass(cls: ClassLikeDef): Bool = arrayParent.isInstanceOfClass(cls) lazy val segments: Ls[TupleShape.Segment] = elements.flatMap: case segment: TupleShape.Segment => segment :: Nil case TupleShape.Rest(_, segments) => segments @@ -482,10 +484,12 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element]) exte case (field: TupleShape.Fixed) :: tail => if index == 0 then MemberLookup.Indexed(field, Nil) else loop(tail, index - 1) case (_: TupleShape.Unknown) :: _ => - MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, toLoc) + // The position is a valid array operation even when a spread or + // mutation has erased the element layout. Its value remains unknown. + MemberLookup.Indexed(TupleShape.UnknownField(source, Nil), Nil) case Nil => MemberLookup.Missing loop(segments, index) - case _ => MemberLookup.Missing + case _ => arrayParent.getMember(name) /** Values whose members and call results are deliberately checked only at runtime. * Unlike UnknownValueShape, this authorizes dynamic operations; it is introduced @@ -526,7 +530,8 @@ object TupleShape: * shape was lost through widening; `source` supplies diagnostic locations. */ final case class Unknown(source: Term, marks: Ls[Marks], value: NonMarkedShape) extends Segment final case class Spread(shape: TupleShape, marks: Marks) extends Element - def unknown(source: Term): TupleShape = TupleShape(source, Unknown(source, Nil, UnknownValueShape(source)) :: Nil) + def unknown(source: Term)(resolver: NewResolver): TupleShape = + TupleShape(source, Unknown(source, Nil, UnknownValueShape(source)) :: Nil)(resolver) /** Retain the original candidate as well as the selected residual segments: * flattening away the parent would hide recursive producer dependencies from * containsSpread, allowing recursion through rest slicing to evade widening. */ diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index db4c733023..117c3f8239 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -16,6 +16,10 @@ enum TypeShape: case Function(params: Term, result: TypeResolution) case Applied(base: TypeResolution, args: Ls[TypeResolution]) case Parameter(symbol: VarSymbol) + // Synthesized generic arguments can retain inferred value shapes, for example + // the element union of the Array supertype of a tuple. Written annotations + // never introduce this case by inspecting their implementation. + case Inferred(value: TermShape) case Captured(base: TypeResolution, thru: AnyDefinitionSymbol) case Unit case Dynamic diff --git a/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls b/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls index bbe04bc183..60c6d03d5d 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls @@ -1,3 +1,4 @@ +:. :js @@ -25,22 +26,22 @@ r !a :fixme r! a //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.26: r! a +//│ ║ l.27: r! a //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Builtin '!' is not a binary operator -//│ ║ l.26: r! a -//│ ╙── ^^^^ +//│ ║ l.27: r! a +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :fixme r ! a //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.37: r ! a +//│ ║ l.38: r ! a //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Builtin '!' is not a binary operator -//│ ║ l.37: r ! a -//│ ╙── ^^^^^ +//│ ║ l.38: r ! a +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // * Note: artifact from BbML/InvalML @@ -78,13 +79,13 @@ t.01 :fixme // * Strange that this is treated as a statement !(1,2) //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.79: !(1,2) +//│ ║ l.80: !(1,2) //│ ╙── ^ :ge (!)(1,2) //│ ╔══[COMPILATION ERROR] Builtin '!' is not a binary operator -//│ ║ l.85: (!)(1,2) +//│ ║ l.86: (!)(1,2) //│ ╙── ^^^^^^^^ //│ ═══[COMPILATION ERROR] Cannot call non-binary builtin symbol '!' //│ ═══[RUNTIME ERROR] Error: Cannot call non-binary builtin symbol '!' @@ -99,10 +100,10 @@ t.01 :fixme !1 //│ ╔══[PARSE ERROR] Expected an expression; found dynamic selector instead -//│ ║ l.100: !1 +//│ ║ l.101: !1 //│ ╙── ^^ //│ ╔══[PARSE ERROR] Expected end of input; found dynamic selector instead -//│ ║ l.100: !1 +//│ ║ l.101: !1 //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/ExplicitLabels.mls b/hkmc2/shared/src/test/mlscript/basics/ExplicitLabels.mls index 46b5e8985d..6bbc148b51 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ExplicitLabels.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ExplicitLabels.mls @@ -1,3 +1,4 @@ +:. :js @@ -31,16 +32,22 @@ let i = 0 in do outLoop: :e missing.break(1) //│ ╔══[COMPILATION ERROR] Name not found: missing -//│ ║ l.32: missing.break(1) +//│ ║ l.33: missing.break(1) //│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'break' has no resolved target +//│ ║ l.33: missing.break(1) +//│ ╙── ^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e missing.continue //│ ╔══[COMPILATION ERROR] Name not found: missing -//│ ║ l.40: missing.continue +//│ ║ l.44: missing.continue //│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'continue' has no resolved target +//│ ║ l.44: missing.continue +//│ ╙── ^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -48,7 +55,7 @@ missing.continue () => do lbl: lbl.continue(1) //│ ╔══[COMPILATION ERROR] 'continue' does not take arguments. -//│ ║ l.49: lbl.continue(1) +//│ ║ l.56: lbl.continue(1) //│ ╙── ^^^^^^^^^^^^^^^ //│ = fun @@ -57,7 +64,7 @@ missing.continue do lbl: lbl.break(1, 2) //│ ╔══[COMPILATION ERROR] 'break' expects at most one argument. -//│ ║ l.58: lbl.break(1, 2) +//│ ║ l.65: lbl.break(1, 2) //│ ╙── ^^^^^^^^^^^^^^^ @@ -65,8 +72,11 @@ do lbl: lbl: lbl.break(1) //│ ╔══[COMPILATION ERROR] Name not found: lbl -//│ ║ l.66: lbl.break(1) +//│ ║ l.73: lbl.break(1) //│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'break' has no resolved target +//│ ║ l.73: lbl.break(1) +//│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ lbl = undefined @@ -110,11 +120,18 @@ do lbl: // * If a variable shadows a label, label control-flow selection should no longer resolve as a label op. +:ge :re do lbl: let lbl = 5 lbl.break -//│ ═══[RUNTIME ERROR] Error: Access to required field 'break' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.127: lbl.break +//│ ║ ^^^^^^^^^ +//│ ╟── Integer literal does not contain member 'break' +//│ ║ l.126: let lbl = 5 +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // * Nested labels in a plain `do` block should be handled; later labels are nested in earlier ones. @@ -144,6 +161,9 @@ do only: only.break(x) //│ ╔══[COMPILATION ERROR] Name not found: only -//│ ║ l.145: only.break(x) +//│ ║ l.162: only.break(x) //│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'break' has no resolved target +//│ ║ l.162: only.break(x) +//│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/LiteralSelection.mls b/hkmc2/shared/src/test/mlscript/basics/LiteralSelection.mls index 3e374f2a7e..3692a0bb99 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LiteralSelection.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LiteralSelection.mls @@ -1,3 +1,4 @@ +:. :js @@ -41,7 +42,7 @@ obj :pe obj. a //│ ╔══[PARSE ERROR] Expected end of input; found period instead -//│ ║ l.42: obj. a +//│ ║ l.43: obj. a //│ ╙── ^ //│ = {a: 1} @@ -55,20 +56,20 @@ obj .("a") obj .("a") //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.56: .("a") +//│ ║ l.57: .("a") //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.56: .("a") +//│ ║ l.57: .("a") //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (‹erroneous syntax›). -//│ ║ l.56: .("a") +//│ ║ l.57: .("a") //│ ╙── ^ //│ = {a: 1} :pe obj. ("a") //│ ╔══[PARSE ERROR] Expected end of input; found period instead -//│ ║ l.69: obj. ("a") +//│ ║ l.70: obj. ("a") //│ ╙── ^ //│ = {a: 1} @@ -82,20 +83,20 @@ obj ."a" obj ."a" //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.83: ."a" +//│ ║ l.84: ."a" //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.83: ."a" +//│ ║ l.84: ."a" //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (‹erroneous syntax›). -//│ ║ l.83: ."a" +//│ ║ l.84: ."a" //│ ╙── ^ //│ = {a: 1} :pe obj. "a" //│ ╔══[PARSE ERROR] Expected end of input; found period instead -//│ ║ l.96: obj. "a" +//│ ║ l.97: obj. "a" //│ ╙── ^ //│ = {a: 1} @@ -111,7 +112,7 @@ obj." " :w obj."¯\_(ツ)_/¯" //│ ╔══[WARNING] Found invalid escape character -//│ ║ l.112: obj."¯\_(ツ)_/¯" +//│ ║ l.113: obj."¯\_(ツ)_/¯" //│ ╙── ^ //│ = 2 @@ -123,21 +124,21 @@ obj."¯\\_(ツ)_/¯" :w "¯\_(ツ)_/¯" //│ ╔══[WARNING] Found invalid escape character -//│ ║ l.124: "¯\_(ツ)_/¯" +//│ ║ l.125: "¯\_(ツ)_/¯" //│ ╙── ^ //│ = "¯_(ツ)_/¯" :w let obj = "¯\_(ツ)_/¯": 2 //│ ╔══[WARNING] Found invalid escape character -//│ ║ l.131: let obj = "¯\_(ツ)_/¯": 2 +//│ ║ l.132: let obj = "¯\_(ツ)_/¯": 2 //│ ╙── ^ //│ obj = {"¯_(ツ)_/¯": 2} :w obj."¯\_(ツ)_/¯" //│ ╔══[WARNING] Found invalid escape character -//│ ║ l.138: obj."¯\_(ツ)_/¯" +//│ ║ l.139: obj."¯\_(ツ)_/¯" //│ ╙── ^ //│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/basics/MutArr.mls b/hkmc2/shared/src/test/mlscript/basics/MutArr.mls index ba4cec87cb..6e861bdeb7 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MutArr.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MutArr.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/Arrays.mls b/hkmc2/shared/src/test/mlscript/codegen/Arrays.mls index 2a90026bb7..7b5ad0d051 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Arrays.mls @@ -1,12 +1,20 @@ +:. :js let empty = [] //│ empty = [] +:ge :re empty.0 -//│ ═══[RUNTIME ERROR] Error: Access to required field '0' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.10: empty.0 +//│ ║ ^^^^^^^ +//│ ╟── Tuple literal does not contain member '0' +//│ ║ l.5: let empty = [] +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :sjs let single = [1] @@ -44,13 +52,27 @@ val triple = [1, 2, 3] triple.2 //│ = 3 +:ge :re triple.3 -//│ ═══[RUNTIME ERROR] Error: Access to required field '3' yielded 'undefined' - +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.57: triple.3 +//│ ║ ^^^^^^^^ +//│ ╟── Tuple literal does not contain member '3' +//│ ║ l.49: val triple = [1, 2, 3] +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:ge :re triple.4 -//│ ═══[RUNTIME ERROR] Error: Access to required field '4' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.68: triple.4 +//│ ║ ^^^^^^^^ +//│ ╟── Tuple literal does not contain member '4' +//│ ║ l.49: val triple = [1, 2, 3] +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // * Array hacking (taken from Anto's original port of nofib, which was slightly deranged) diff --git a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls index 4b2aa99b4f..39b592aca6 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls @@ -1,3 +1,4 @@ +:. :js :sir @@ -147,7 +148,7 @@ new C(123) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define C⁰ as class C² { //│ private val x⁵; -//│ constructor C¹(x) { set C².this.x⁵ = x; end } +//│ constructor C¹(x) { @private define x⁶ as val x⁵ = x; end } //│ }; //│ do C¹(123); //│ return new C²(123) @@ -162,17 +163,18 @@ module A with fun g(x) = 3 A.x //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define A⁰ as class A¹ -//│ module A² { +//│ define A¹ as class A² +//│ module A⁰ { //│ private val y⁰; -//│ val x⁶; +//│ val x⁷; //│ constructor { -//│ define x⁶ as val x⁷ = 2; -//│ set A².this.y⁰ = 3; +//│ define x⁷ as val x⁸ = 2; +//│ set A⁰.this.y⁰ = 3; //│ define C³ as class C⁵ { -//│ private val x⁸; +//│ private val x⁹; //│ constructor C⁴(x) { -//│ set C⁵.this.x⁸ = x; +//│ @private +//│ define x¹⁰ as val x⁹ = x; //│ end //│ } //│ method f⁴ = fun f⁵(x1) { @@ -183,7 +185,7 @@ A.x //│ } //│ method g⁰ = fun g¹(x) { return 3 } //│ }; -//│ return A².x⁷ +//│ return A⁰.x⁸ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 2 @@ -192,8 +194,8 @@ module A with A.x //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define A³ as class A⁴ -//│ module A⁵ { val x⁹; constructor { define x⁹ as val x¹⁰ = 3; end } }; -//│ return A⁵.x¹⁰ +//│ module A⁵ { val x¹¹; constructor { define x¹¹ as val x¹² = 3; end } }; +//│ return A⁵.x¹² //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 3 @@ -203,9 +205,16 @@ class B(x) extends A with //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define A⁶ as class A⁷; //│ define B⁰ as class B² extends A⁷ { -//│ private val x¹¹; +//│ private val x¹³; //│ val y¹; -//│ constructor B¹(x) { do super⁰(); end; set B².this.x¹¹ = x; define y¹ as val y² = 2; end } +//│ constructor B¹(x) { +//│ do super⁰(); +//│ end; +//│ @private +//│ define x¹⁴ as val x¹³ = x; +//│ define y¹ as val y² = 2; +//│ end +//│ } //│ }; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -237,7 +246,7 @@ object A with let x = [1, 2, 3] x.0 //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ set x¹² = [1, 2, 3]; return x¹².0﹖ +//│ set x¹⁵ = [1, 2, 3]; return x¹⁵.0 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 //│ x = [1, 2, 3] diff --git a/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls b/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls index 5a1f2f1ff0..9fe81094f1 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls @@ -1,3 +1,4 @@ +:. :js @@ -7,7 +8,7 @@ class Foo(val x)(val y) :sjs class Bar(val a)(val d) extends Foo(a) //│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported -//│ ║ l.8: class Bar(val a)(val d) extends Foo(a) +//│ ║ l.9: class Bar(val a)(val d) extends Foo(a) //│ ╙── ^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Bar1; @@ -36,8 +37,15 @@ let b = Bar(123)(1234) b.a //│ = 123 +:ge :re let c = b(100) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.42: let c = b(100) +//│ ║ ^^^^^^ +//│ ╟── Instance of class constructor 'Bar' cannot receive more argument lists. +//│ ║ l.34: let b = Bar(123)(1234) +//│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: b is not a function //│ c = undefined @@ -47,11 +55,30 @@ c.a :fixme Bar(123)(1234)(100).a +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.57: Bar(123)(1234)(100).a +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class constructor 'Bar' cannot receive more argument lists. +//│ ║ l.57: Bar(123)(1234)(100).a +//│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: baseCall is not a function +:ge :re [c.x, c.y] -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'x') +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.68: [c.x, c.y] +//│ ║ ^^^ +//│ ╟── Instance of class constructor 'Bar' does not contain member 'x' +//│ ║ l.42: let c = b(100) +//│ ╙── ^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.68: [c.x, c.y] +//│ ║ ^^^ +//│ ╟── Instance of class constructor 'Bar' does not contain member 'y' +//│ ║ l.42: let c = b(100) +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. let b0 = Bar //│ b0 = fun Bar { class: class Bar } @@ -59,9 +86,16 @@ let b0 = Bar let b1 = b0(123) //│ b1 = fun +:ge :re b1.a -//│ ═══[RUNTIME ERROR] Error: Access to required field 'a' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.91: b1.a +//│ ║ ^^^^ +//│ ╟── Instance of class constructor 'Bar' does not contain member 'a' +//│ ║ l.86: let b1 = b0(123) +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. let b = b1(1234) //│ b = Bar(123) @@ -69,8 +103,15 @@ let b = b1(1234) b.a //│ = 123 +:ge :re let c = b(100) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.108: let c = b(100) +//│ ║ ^^^^^^ +//│ ╟── Instance of class constructor 'Bar' cannot receive more argument lists. +//│ ║ l.100: let b = b1(1234) +//│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: b2 is not a function //│ c = undefined @@ -78,9 +119,22 @@ let c = b(100) c.a //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'a') +:ge :re [c.x, c.y] -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'x') +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.124: [c.x, c.y] +//│ ║ ^^^ +//│ ╟── Instance of class constructor 'Bar' does not contain member 'x' +//│ ║ l.108: let c = b(100) +//│ ╙── ^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.124: [c.x, c.y] +//│ ║ ^^^ +//│ ╟── Instance of class constructor 'Bar' does not contain member 'y' +//│ ║ l.108: let c = b(100) +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. class Baz(a)(val d) extends Foo(a)(a + 1) @@ -88,8 +142,15 @@ class Baz(a)(val d) extends Foo(a)(a + 1) let b = Baz(123)(1234) //│ b = Baz(_) +:ge :re let c = b(100) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.147: let c = b(100) +//│ ║ ^^^^^^ +//│ ╟── Instance of class constructor 'Baz' cannot receive more argument lists. +//│ ║ l.142: let b = Baz(123)(1234) +//│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: b3 is not a function //│ c = undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls b/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls index d6342946a5..0425d81741 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls @@ -1,3 +1,4 @@ +:. :js #config(deadBranchRemoval: true) diff --git a/hkmc2/shared/src/test/mlscript/codegen/Misc.mls b/hkmc2/shared/src/test/mlscript/codegen/Misc.mls index 21c9c4ebb3..ce076fa800 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Misc.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Misc.mls @@ -1,3 +1,4 @@ +:. :js @@ -12,9 +13,7 @@ :ge // Shouldn't allow using `Int` in term position 1 : Int -//│ ╔══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term -//│ ║ l.14: 1 : Int -//│ ╙── ^^^ +//│ ═══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -67,29 +66,23 @@ bar(1, 2) // the target is JS rather than Wasm. :e wasm -//│ ╔══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly -//│ ║ l.69: wasm -//│ ╙── ^^^^ +//│ ═══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e wasm.i32 -//│ ╔══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly -//│ ║ l.76: wasm.i32 -//│ ╙── ^^^^ +//│ ═══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e wasm.i32.const -//│ ╔══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly -//│ ║ l.83: wasm.i32.const -//│ ╙── ^^^^ +//│ ═══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e wasm.i32.const(42) //│ ╔══[COMPILATION ERROR] WebAssembly intrinsics are not available when targeting JS -//│ ║ l.90: wasm.i32.const(42) +//│ ║ l.83: wasm.i32.const(42) //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/NestedScoped.mls b/hkmc2/shared/src/test/mlscript/codegen/NestedScoped.mls index 4237a4d21f..19fa8184f7 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/NestedScoped.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/NestedScoped.mls @@ -1,3 +1,4 @@ +:. :js @@ -57,7 +58,7 @@ scope.locally of scope.locally of ( :e x //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.58: x +//│ ║ l.59: x //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -153,7 +154,7 @@ fun foo(x, y) = :ge scope.locally() //│ ╔══[COMPILATION ERROR] Unsupported form for scope.locally. -//│ ║ l.154: scope.locally() +//│ ║ l.155: scope.locally() //│ ╙── ^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -161,7 +162,7 @@ scope.locally() :ge scope.locally of (let x = 1 in x), (let y = 2 in y) //│ ╔══[COMPILATION ERROR] Unsupported form for scope.locally. -//│ ║ l.162: scope.locally of (let x = 1 in x), (let y = 2 in y) +//│ ║ l.163: scope.locally of (let x = 1 in x), (let y = 2 in y) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -170,10 +171,10 @@ scope.locally of (let x = 1 in x), (let y = 2 in y) :ge scope.locally of (let x = 1 in x), (let y = 2 in x) //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.171: scope.locally of (let x = 1 in x), (let y = 2 in x) +//│ ║ l.172: scope.locally of (let x = 1 in x), (let y = 2 in x) //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Unsupported form for scope.locally. -//│ ║ l.171: scope.locally of (let x = 1 in x), (let y = 2 in x) +//│ ║ l.172: scope.locally of (let x = 1 in x), (let y = 2 in x) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index 9cc5bf21ba..48a6208d3e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -1,3 +1,4 @@ +:. :js :global @@ -49,8 +50,15 @@ Foo //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = class Foo +:ge :re Foo() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.55: Foo() +//│ ║ ^^^^^ +//│ ╟── Class value cannot receive more argument lists. +//│ ║ l.8: class Foo +//│ ╙── ^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ return runtime.safeCall(Foo1()) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -119,15 +127,14 @@ let t = test() :e new t -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found reference. -//│ ║ l.121: new t -//│ ║ ^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.129: new t +//│ ║ ^^^^^ +//│ ╙── Invalid class expression: reference //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return globalThis.Object.freeze(new t()) +//│ throw "This code cannot be run as its compilation yielded an error." //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ > hi -//│ = Foo +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. new! t //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— @@ -138,15 +145,14 @@ new! t :e new t() -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found reference. -//│ ║ l.140: new t() -//│ ║ ^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.147: new t() +//│ ║ ^^^^^^^ +//│ ╙── Invalid class expression: reference //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return globalThis.Object.freeze(new t()) +//│ throw "This code cannot be run as its compilation yielded an error." //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ > hi -//│ = Foo +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. new! t() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— @@ -245,9 +251,9 @@ print(a.bar(1)) //│ let a, tmp, tmp1; //│ a = globalThis.Object.freeze(new Foo12()); //│ Predef.print(a.x); -//│ tmp = runtime.safeCall(a.foo(1)); +//│ tmp = a.foo(1); //│ Predef.print(tmp); -//│ tmp1 = runtime.safeCall(a.bar(1)); +//│ tmp1 = a.bar(1); //│ return Predef.print(tmp1) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > 1 @@ -263,10 +269,10 @@ class Foo with val x = 2 //│ ╔══[COMPILATION ERROR] Multiple definitions of symbol 'x' //│ ╟── defined here -//│ ║ l.262: val x = 1 +//│ ║ l.268: val x = 1 //│ ║ ^^^^^^^^^ //│ ╟── defined here -//│ ║ l.263: val x = 2 +//│ ║ l.269: val x = 2 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo14; @@ -288,10 +294,10 @@ class Foo with val x = 1 let x = 2 //│ ╔══[COMPILATION ERROR] Name 'x' is already used -//│ ║ l.289: let x = 2 +//│ ║ l.295: let x = 2 //│ ║ ^^^^^ //│ ╟── by a member declared in the same block -//│ ║ l.288: val x = 1 +//│ ║ l.294: val x = 1 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo16; @@ -317,7 +323,7 @@ class Foo with :e class Foo with val x = 1 //│ ╔══[COMPILATION ERROR] Illegal body of class definition (should be a block; found term definition). -//│ ║ l.318: class Foo with val x = 1 +//│ ║ l.324: class Foo with val x = 1 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo18; diff --git a/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls b/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls index bc14133686..80735a4ae3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TraceLog.mls @@ -1,3 +1,4 @@ +:. :js :traceJS :noSanityCheck diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index e8d602cc3f..688edda8c8 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -106,13 +106,34 @@ declare class Class // declare class Array[T](val length: Int): Array[T] // but this made instance checks wrongly use `Array.class`; // TODO: handle Array and other JS classes that can be instantiated without `new` specially in codegen. -declare class Array[T] +declare class Array[T] with + val length: Int + fun join(...separators): Str + fun reverse(): Array[T] + fun sort(...compare): Array[T] + fun slice(...bounds): Array[T] + fun push(...items): Int + fun unshift(...items): Int + fun pop(): T | undefined + fun shift(): T | undefined + fun concat(...arrays): Array[Any] + // These declarations retain the input element interface. Until method-level + // generic instantiation is supported, mapped results expose only Any. + fun map(f: (T, Int, Array[T]) -> Any): Array[Any] + fun forEach(f: (T, Int, Array[T]) -> Any): undefined + fun filter(f: (T, Int, Array[T]) -> Bool): Array[T] + fun every(f: (T, Int, Array[T]) -> Bool): Bool + fun some(f: (T, Int, Array[T]) -> Bool): Bool + fun indexOf(value: T, ...from): Int + fun lastIndexOf(value: T, ...from): Int + fun includes(value: T, ...from): Bool + fun toString(): Str declare module Array with fun from concat isArray - prototype + val prototype: Array[Any] declare class Symbol declare module Symbol with diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls new file mode 100644 index 0000000000..3566c689f7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -0,0 +1,109 @@ +:. + +// Tuples retain indexed fields while inheriting the declared Array interface. +:expect [3, "1-2-3", 2] +let xs = [1, 2, 3] +[xs.length, xs.join("-"), xs.1] +//│ = [3, "1-2-3", 2] +//│ xs = [1, 2, 3] + +// A tuple is also an instance of the Array class for pattern transfer. +:expect 3 +let xs = [1, 2, 3] +if xs is Array then xs.length else 0 +//│ = 3 +//│ xs = [1, 2, 3] + +// Inherited callbacks receive the union of the tuple's element shapes. +:expect [2, 3] +class Item(val value: Int) +[Item(1), Item(2)].map((item, ...) => item.value + 1) +//│ = [2, 3] + +// Declared tuple element types remain opaque through the Array interface. +:ge +class Base +class Child extends Base with + val extra = 1 +fun read(xs: [Base]) = xs.map((item, ...) => item.extra) +read([new Child]) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.28: fun read(xs: [Base]) = xs.map((item, ...) => item.extra) +//│ ║ ^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Mutable arrays expose methods without claiming their initial elements persist. +:expect [3, "2,1,3", 3] +let xs = mut [1, 2] +xs.reverse() +let size = xs.push(3) +[size, xs.join(), xs.2] +//│ = [3, "2,1,3", 3] +//│ size = 3 +//│ xs = [2, 1, 3] + +// Array results retain their declared instance interface. +:expect "2,3" +[1, 2].map((x, ...) => x + 1).join() +//│ = "2,3" + +// Tuple element inference must remain separate across function activations. +:expect [[1], [2]] +class First(val first: Int) +class Second(val second: Int) +fun singleton(x) = [x] +[singleton(First(1)).map((x, ...) => x.first), + singleton(Second(2)).map((x, ...) => x.second)] +//│ = [[1], [2]] + +// Inheriting Array does not authorize arbitrary properties. +:ge +[1].nonexistent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.62: [1].nonexistent +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Tuple literal does not contain member 'nonexistent' +//│ ║ l.62: [1].nonexistent +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Nominal argument inference sees the tuple's Array supertype. +:expect [4] +class Item(val value: Int) +fun values[A](xs: Array[A]) = xs.map((item, ...) => item.value) +values([Item(4)]) +//│ = [4] + +// Rest callback parameters retain the remaining argument tuple. +:expect [3, 4] +[10, 20].map((item, ...rest) => rest.0 + rest.1.length + 1) +//│ = [3, 4] + +// Fixed callbacks must accept all three arguments supplied by Array.map. +:ge +[1].map(x => x) +//│ ╔══[COMPILATION ERROR] Resolution error in function type +//│ ║ l.121: fun map(f: (T, Int, Array[T]) -> Any): Array[Any] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Callback parameter list does not match its declared function type. +//│ ║ in Arrays.mls:85 +//│ ║ l.85: [1].map(x => x) +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 3 + +// Mutable arrays do not propagate stale initializer element shapes to callbacks. +:ge +class Original(val original: Int) +class Replacement(val replacement: Int) +let xs = mut [Original(1)] +set xs.0 = Replacement(2) +xs.map((x, ...) => x.original) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.101: xs.map((x, ...) => x.original) +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'original' of a value with unknown shape. +//│ ║ l.99: let xs = mut [Original(1)] +//│ ╙── ^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [Replacement(2)] diff --git a/hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls b/hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls index 08a8e6af0c..4bf03c2c19 100644 --- a/hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls +++ b/hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Char.mls" diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 4f3c2f8683..3d9524d5fb 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.253: fun +//│ ║ l.274: fun //│ ║ ^^^ -//│ ║ l.254: const(c: Int): Int32 +//│ ║ l.275: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 82f399db36c9f5ec3023a8e06c8b296266d2361f Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 24 Sep 2026 13:33:22 +0800 Subject: [PATCH 067/228] Minor --- .../scala/hkmc2/semantics/NewResolver.scala | 4 ++-- .../src/main/scala/hkmc2/semantics/Shape.scala | 2 +- .../mlscript/basics/CompanionFunctions.mls | 2 +- .../test/mlscript/basics/MemberProjections.mls | 4 ++-- .../codegen/CurriedClassInheritance.mls | 18 +++++++++--------- .../src/test/mlscript/codegen/Spreads.mls | 4 ++-- .../src/test/mlscript/newres/ParamClasses.mls | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 7acd8707de..5798c28905 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -781,9 +781,9 @@ class NewResolver: res.isErroneous = true if lhs.applicationHead._1 is lhs then resolError(res, - msg"${lhs.describe.capitalize} cannot be called like a function." -> lhs.toLoc :: Nil) + msg"${lhs.describe.capitalize} cannot be called like a function." -> lhs.toLoc :: Nil) else resolError(res, - msg"${lhs.describe.capitalize} cannot receive more argument lists." -> lhs.toLoc :: Nil) + msg"${lhs.describe.capitalize} cannot receive more argument lists." -> lhs.toLoc :: Nil) if sh.isSaturated then def go(body: Term, mss: Ls[Marks]) = listenTerm(body): sh => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index b01c4f7999..bf92d74779 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -426,7 +426,7 @@ class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTer case defn: TypeLikeDef => defn.kind.desc + " " case defn: TermDefinition => defn.tsym match - case _: ClassCtorSymbol => "class constructor " + case _: ClassCtorSymbol => "class " case s => s.k.desc + " " }'${defn.bsym.nme}'" // override def toString: String = s"DefnShape(${defn.describe} ${defn.bsym.nme})" diff --git a/hkmc2/shared/src/test/mlscript/basics/CompanionFunctions.mls b/hkmc2/shared/src/test/mlscript/basics/CompanionFunctions.mls index 685b0711d7..d3b877db8d 100644 --- a/hkmc2/shared/src/test/mlscript/basics/CompanionFunctions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/CompanionFunctions.mls @@ -18,7 +18,7 @@ Foo(123) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.17: Foo(123) //│ ║ ^^^^^^^^ -//│ ╟── Class constructor 'Foo' expected 2 arguments, but got 1 +//│ ╟── Class 'Foo' expected 2 arguments, but got 1 //│ ║ l.7: fun Foo(x) = new Foo(x, 0) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.8: data class Foo(x, y) diff --git a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls index 337934bcdb..efeea33267 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls @@ -92,7 +92,7 @@ foo.M.Foo::m() //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.91: foo.M.Foo::m() //│ ║ ^^^^^ -//│ ╟── Instance of class constructor 'Foo' does not contain member 'M' +//│ ╟── Instance of class 'Foo' does not contain member 'M' //│ ║ l.11: let foo = M.Foo(123) //│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in application @@ -115,7 +115,7 @@ foo.M.Foo#m() //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.111: foo.M.Foo#m() //│ ║ ^^^^^ -//│ ╟── Instance of class constructor 'Foo' does not contain member 'M' +//│ ╟── Instance of class 'Foo' does not contain member 'M' //│ ║ l.11: let foo = M.Foo(123) //│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot resolve the projection class diff --git a/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls b/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls index 9fe81094f1..f6c27012bd 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls @@ -43,7 +43,7 @@ let c = b(100) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.42: let c = b(100) //│ ║ ^^^^^^ -//│ ╟── Instance of class constructor 'Bar' cannot receive more argument lists. +//│ ╟── Instance of class 'Bar' cannot receive more argument lists. //│ ║ l.34: let b = Bar(123)(1234) //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: b is not a function @@ -58,7 +58,7 @@ Bar(123)(1234)(100).a //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.57: Bar(123)(1234)(100).a //│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ╟── Instance of class constructor 'Bar' cannot receive more argument lists. +//│ ╟── Instance of class 'Bar' cannot receive more argument lists. //│ ║ l.57: Bar(123)(1234)(100).a //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: baseCall is not a function @@ -69,13 +69,13 @@ Bar(123)(1234)(100).a //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.68: [c.x, c.y] //│ ║ ^^^ -//│ ╟── Instance of class constructor 'Bar' does not contain member 'x' +//│ ╟── Instance of class 'Bar' does not contain member 'x' //│ ║ l.42: let c = b(100) //│ ╙── ^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.68: [c.x, c.y] //│ ║ ^^^ -//│ ╟── Instance of class constructor 'Bar' does not contain member 'y' +//│ ╟── Instance of class 'Bar' does not contain member 'y' //│ ║ l.42: let c = b(100) //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -92,7 +92,7 @@ b1.a //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.91: b1.a //│ ║ ^^^^ -//│ ╟── Instance of class constructor 'Bar' does not contain member 'a' +//│ ╟── Instance of class 'Bar' does not contain member 'a' //│ ║ l.86: let b1 = b0(123) //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -109,7 +109,7 @@ let c = b(100) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.108: let c = b(100) //│ ║ ^^^^^^ -//│ ╟── Instance of class constructor 'Bar' cannot receive more argument lists. +//│ ╟── Instance of class 'Bar' cannot receive more argument lists. //│ ║ l.100: let b = b1(1234) //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: b2 is not a function @@ -125,13 +125,13 @@ c.a //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.124: [c.x, c.y] //│ ║ ^^^ -//│ ╟── Instance of class constructor 'Bar' does not contain member 'x' +//│ ╟── Instance of class 'Bar' does not contain member 'x' //│ ║ l.108: let c = b(100) //│ ╙── ^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.124: [c.x, c.y] //│ ║ ^^^ -//│ ╟── Instance of class constructor 'Bar' does not contain member 'y' +//│ ╟── Instance of class 'Bar' does not contain member 'y' //│ ║ l.108: let c = b(100) //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -148,7 +148,7 @@ let c = b(100) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.147: let c = b(100) //│ ║ ^^^^^^ -//│ ╟── Instance of class constructor 'Baz' cannot receive more argument lists. +//│ ╟── Instance of class 'Baz' cannot receive more argument lists. //│ ║ l.142: let b = Baz(123)(1234) //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: b3 is not a function diff --git a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls index e68da28e4b..a82e4658bc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls @@ -72,7 +72,7 @@ x.r //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.67: let x = new A(1, 2, 3) //│ ║ ^^^^^^^^^^^^^^ -//│ ╟── Class constructor 'A' expected 0 arguments, but got 3 +//│ ╟── Class 'A' expected 0 arguments, but got 3 //│ ║ l.66: data class A(...r) //│ ╙── ^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection @@ -96,7 +96,7 @@ new A(1, 2, 3).getR //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.92: new A(1, 2, 3).getR //│ ║ ^^^^^^^^^^^^^^ -//│ ╟── Class constructor 'A' expected 0 arguments, but got 3 +//│ ╟── Class 'A' expected 0 arguments, but got 3 //│ ║ l.90: data class A(...r) with //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ║ l.91: fun getR = r diff --git a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls index b620a4dc3b..c99c25c511 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls @@ -238,7 +238,7 @@ g(2) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.237: g(2) //│ ║ ^^^^ -//│ ╟── Instance of class constructor 'Foo' cannot receive more argument lists. +//│ ╟── Instance of class 'Foo' cannot receive more argument lists. //│ ║ l.233: let g = f(1) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] TypeError: g is not a function @@ -248,7 +248,7 @@ let f = new Foo() //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.247: let f = new Foo() //│ ║ ^^^^^^^^^ -//│ ╟── Class constructor 'Foo' expected 1 argument, but got 0 +//│ ╟── Class 'Foo' expected 1 argument, but got 0 //│ ║ l.84: class Foo(val a) { //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ║ l.85: val b = a + 1 From 2bba991dc45244f0354735078dbf7c5b404a8be1 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 13:37:52 +0800 Subject: [PATCH 068/228] Distinguish marked callees from overapplied constructors in diagnostics --- .../scala/hkmc2/semantics/NewResolver.scala | 16 +++-- .../test/mlscript/codegen/PlainClasses.mls | 2 +- .../src/test/mlscript/newres/BadApps.mls | 64 +++++++++++++++++++ .../src/test/mlscript/newres/BareClasses.mls | 2 +- 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 5798c28905..b0df1aa36d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -779,11 +779,17 @@ class NewResolver: log(s"lhs ${lhs.isSaturated} ${lhs.unappliedParams.map(_.mapFirst(_.showDbg).mapSecond(_.map(_.showDbg)))}") if lhs.isSaturated && !res.isErroneous then res.isErroneous = true - if lhs.applicationHead._1 is lhs - then resolError(res, - msg"${lhs.describe.capitalize} cannot be called like a function." -> lhs.toLoc :: Nil) - else resolError(res, - msg"${lhs.describe.capitalize} cannot receive more argument lists." -> lhs.toLoc :: Nil) + // Marks transport a value across scopes; they do not apply arguments. + // Inspect the value beneath them, retaining actual calls/instantiations + // so an already constructed instance never gets the suggestion to use new. + val message = lhs match + case Marked(ds: DefnShape, _) if ds.defn.isInstanceOf[ClassDef] => + msg"Class '${ds.defn.bsym.nme}' must be instantiated with 'new'." + case Marked(_: (AppShape | NewShape), _) => + msg"${lhs.describe.capitalize} cannot receive more argument lists." + case _ => + msg"${lhs.describe.capitalize} cannot be called like a function." + resolError(res, message -> lhs.toLoc :: Nil) if sh.isSaturated then def go(body: Term, mss: Ls[Marks]) = listenTerm(body): sh => diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index 48a6208d3e..98dc7aee5b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -56,7 +56,7 @@ Foo() //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.55: Foo() //│ ║ ^^^^^ -//│ ╟── Class value cannot receive more argument lists. +//│ ╟── Class 'Foo' must be instantiated with 'new'. //│ ║ l.8: class Foo //│ ╙── ^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/BadApps.mls b/hkmc2/shared/src/test/mlscript/newres/BadApps.mls index 134b84910b..f1ed2cac7f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BadApps.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BadApps.mls @@ -28,3 +28,67 @@ x(456) //│ ═══[RUNTIME ERROR] TypeError: x is not a function +// Returning a non-callable value adds capture marks, not an argument list. +:e +fun result() = 1 +result()() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.34: result()() +//│ ║ ^^^^^^^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.33: fun result() = 1 +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] TypeError: 1 is not a function + +// A captured module is still a non-callable value. +:e +module M +fun call(x) = x() +call(M) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.46: fun call(x) = x() +//│ ║ ^^^ +//│ ╟── Module 'M' cannot be called like a function. +//│ ║ l.45: module M +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] TypeError: Class constructor M cannot be invoked without 'new' + +// Plain class values require new, including when passed through a parameter. +:e +class Plain +fun instantiate(cls) = cls() +instantiate(Plain) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.59: fun instantiate(cls) = cls() +//│ ║ ^^^^^ +//│ ╟── Class 'Plain' must be instantiated with 'new'. +//│ ║ l.58: class Plain +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] TypeError: Class constructor Plain cannot be invoked without 'new' + +// A fully applied constructor really has received all its argument lists. +:e +class Curried(x)(y) +fun overapply(instance) = instance() +overapply(Curried(1)(2)) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.72: fun overapply(instance) = instance() +//│ ║ ^^^^^^^^^^ +//│ ╟── Instance of class 'Curried' cannot receive more argument lists. +//│ ║ l.73: overapply(Curried(1)(2)) +//│ ╙── ^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] TypeError: tmp is not a function + +// Explicitly constructed instances must not be told to add another new. +:e +class Plain +fun overapply(instance) = instance() +overapply(new Plain) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.85: fun overapply(instance) = instance() +//│ ║ ^^^^^^^^^^ +//│ ╟── Instance of class 'Plain' cannot receive more argument lists. +//│ ║ l.86: overapply(new Plain) +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] TypeError: tmp1 is not a function + diff --git a/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls index f269a2ebf9..73ed053208 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls @@ -125,7 +125,7 @@ let f = Foo(1) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.124: let f = Foo(1) //│ ║ ^^^^^^ -//│ ╟── Class value cannot receive more argument lists. +//│ ╟── Class 'Foo' must be instantiated with 'new'. //│ ║ l.114: class Foo { //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo6 cannot be invoked without 'new' From c73967c49eb3b8ccc29e0a57e13924ec8591c320 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 13:49:50 +0800 Subject: [PATCH 069/228] Instantiate declared generic methods and preserve Array.map result types --- doc/new-resolution-suite-migration.md | 30 +++- .../src/test/scala/hkmc2/CompilerTest.scala | 37 +++++ .../scala/hkmc2/semantics/NewResolver.scala | 75 ++++++--- .../main/scala/hkmc2/semantics/Shape.scala | 4 +- .../test/mlscript-compile/GenericLibrary.mls | 10 ++ .../src/test/mlscript/decls/Prelude.mls | 4 +- .../src/test/mlscript/newres/Arrays.mls | 4 +- .../test/mlscript/newres/GenericMethods.mls | 142 ++++++++++++++++++ .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 9 files changed, 271 insertions(+), 39 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 816c1eec36..dad940573c 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -130,9 +130,27 @@ lexical boundary. Mutable arrays discard their initial element shapes and length because methods such as `push` and `reverse` can change both. Unknown layouts still permit indexed access with an unknown result, without authorizing arbitrary members. -Array declarations remain deliberately incomplete: `map` and `concat` return -`Array[Any]`; polymorphic method results and richer foreign signatures need further -work. This does not infer result precision from an opaque annotation. +Generic methods use the same marked parameter flow as generic free functions, +including explicit arguments, callback-result inference, and curried signatures. +`Array.map[U]` returns `Array[U]`. Declared callable shapes retain their type +parameters; consumer inference for imported parameters uses resolver-local hosts +instead of mutating shared declarations. Existing candidates on imported parameter +symbols remain readable. `newres/GenericMethods.mls` covers both local and imported +methods, and a compiler-cache regression checks that consumers leave the prelude +parameter's candidates and listeners unchanged. + +This is not yet general import of an inferred flow graph. The same worksheet keeps +explicit `:fixme` regressions for an exported mapped array whose argument lives in +the defining resolver's local host, and an imported generic function with an +inferred result dependent on its parameter. Both currently lose the result shape. +Transporting those dependencies requires the pending listener/flow-graph design; +copying only candidates is insufficient, and copied closures would still reference +the defining resolver. A third regression records a capture assertion when a map +callback reads a constructor field in a module initializer. + +Array declarations remain incomplete: `concat` returns `Array[Any]` because its +rest arguments currently have no declared element constraint. This does not infer +result precision from an opaque annotation. **Accepted policy:** JavaScript imports (including package imports), `globalThis`, and explicit dynamic selection/instantiation introduce `DynShape`. Ordinary @@ -358,7 +376,7 @@ These changes also fix the sibling-subclass field-extraction regression in UPS cases. Keep fixed-point behavior tested independently from matcher code generation. Preserve the now-passing constructor-field context regressions. 5. **Complete type/interface and call validation.** Preserve opaque annotations - and marked generic flow; finish generic validation, polymorphic imported methods, + and marked generic flow; finish generic validation, rest-parameter signatures, module checks, and the WASM migration. Keep cross-block method work coordinated externally. 6. **Migrate shared compilation fixtures from leaves upward.** Begin with the four UPS fixtures; then application parsing data types, lexer, parser helpers, @@ -371,10 +389,10 @@ These changes also fix the sibling-subclass field-extraction regression in ## Validation -- `ctest`: all 46 selected compilation tests pass. +- `ctest`: all 47 selected compilation tests pass. - `catest`: all 20 application compilation fixtures pass in their existing mode. - `cwtest`: the WASM compilation fixture passes. -- `hkmc2AllTests/test`: all 943 tests pass, including 707 main tests, 18 application +- `hkmc2AllTests/test`: all 946 tests pass, including 708 main tests, 18 application tests, and 22 WASM tests. The main count includes directory configurations and new-resolution regressions; it is not a count of migrated worksheets. - All retained ports have reviewed goldens. Deferred worksheets retain their diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index d97c9cec72..9f13a03ee5 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -271,6 +271,43 @@ class CompilerTest extends AnyFunSuite: assert(valueDefn.flatMap(_.inlinerBodySummary |> Option.apply).exists(_ is summary), "The second importer should reuse the published summary instead of replacing it") + test("generic method inference does not mutate shared prelude parameters"): + val fs = new InMemoryFileSystem(loadStandardLibrary()) + given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace = false + given Raise = diagnostic => fail(diagnostic.toString) + val prelude = cctx.getPrelude(paths.preludeFile).ctx + val map = prelude.builtins.Array.defn.get.body.members("map").asTrm.get.defn.get + val parameter = map.tparams.get.head.sym + val originalShapes = parameter.shapes.toVector + val originalListeners = parameter.shapeListeners.length + + fs.write("/Generic.mls", """module Generic with + | fun identity[A](x: A): A = x + |""".stripMargin) + fs.write("/First.mls", """#lang(0.3.x, strictResolution: true) + |import "./Generic.mls" + |class Item(val first: Int) + |[0].map((x, ...) => Generic.identity(Item(1))) + | .map((x, ...) => x.first) + |""".stripMargin) + fs.write("/Second.mls", """#lang(0.3.x, strictResolution: true) + |import "./Generic.mls" + |class Item(val second: Int) + |[0].map((x, ...) => Generic.identity[Item](Item(2))) + | .map((x, ...) => x.second) + |""".stripMargin) + val compiler = new MLsCompiler(_ => summon[Raise]) + compiler.compileModule(Path("/First.mls")) + compiler.compileModule(Path("/Second.mls")) + + assert(parameter.shapes.toVector == originalShapes, + "Consumer inference must not publish into the shared prelude parameter") + assert(parameter.shapeListeners.length == originalListeners, + "Consumer inference must not attach listeners to the shared prelude parameter") + test("compiler can report errors"): val (fs, compiler) = createCompiler() diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index b0df1aa36d..7b3da4398e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -200,11 +200,7 @@ class NewResolver: case TypeShape.Parameter(symbol) => current.bindings.get(symbol) match case S(bound) => follow(bound, Nil, aliases, publish) case N => - def receive(shape: Shape): Unit = shape match - case value: TermShape => publish(value) - case _ => softAssert(false, "A type parameter received a symbolic overload set") - symbol.shapeListeners += receive - symbol.shapes.foreach(receive) + listenTypeArgument(symbol)(publish) case TypeShape.Captured(base, thru) => follow(declaredType(base, current.bindings), args, aliases, shape => publish(shape match @@ -219,7 +215,7 @@ class NewResolver: , fields.exists(!_.isInstanceOf[Fld])) case single => DeclaredParams(S(declaredType(typeResolution(single), current.bindings)) :: Nil, false) publish(CallableTypeShape(tpe.resolution.source, ps :: Nil, - S(declaredType(ret, current.bindings)))) + S(declaredType(ret, current.bindings)), Nil)) case TypeShape.Tuple(fields) => publish(TupleShape(current.resolution.source, fields.map(field => TupleShape.TypedField(declaredType(field, current.bindings), Nil)))(this)) @@ -268,11 +264,14 @@ class NewResolver: selected(symbol) val td = symbol.defn.get def publish(shape: TermShape): Unit = + val generic = shape match + case callable: CallableTypeShape => callable.copy(tparams = td.tparams.toList.flatten.map(_.sym)) + case _ => shape // A synthesized field's signature is in the constructor parameter's // scope. Written member signatures are in their own definition scope. val exited = td.tsym.decl match - case S(_: Param) => shape - case _ => MarkedShape.exit(shape, ResolutionBoundary(td.tsym), S(flow)) + case S(_: Param) => generic + case _ => MarkedShape.exit(generic, ResolutionBoundary(td.tsym), S(flow)) exited match case value: TermShape => listener(value) case NoShape => () @@ -293,7 +292,7 @@ class NewResolver: listenTypeValues(signature(td.sign.get))(publish) else if td.params.nonEmpty then publish(CallableTypeShape(source, - td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), ps.restParam.nonEmpty)), result)) + td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), ps.restParam.nonEmpty)), result, Nil)) else result match case S(tpe) => listenTypeValues(tpe)(publish) case N => publish(UnknownValueShape(source)) @@ -313,20 +312,49 @@ class NewResolver: * must not add a more precise implementation shape to the declared interface. */ private val explicitTypeArguments = mutable.Set.empty[(VarSymbol, Ls[Marks])] - private def publishTypeArgument(symbol: VarSymbol, shape: TermShape | NoShape): Unit = shape match + // Imported declarations are shared between compilation units. Their inferred + // arguments belong to this resolver, not to the imported symbols' mutable hosts. + // Both hosts carry the same entry/exit marks used for locally defined functions. + private val importedTypeArguments = mutable.Map.empty[VarSymbol, TypeValues] + private def importedTypeArgument(symbol: VarSymbol): TypeValues = + importedTypeArguments.getOrElseUpdate(symbol, { + val host = new TypeValues + // Completed imports can already expose inferred arguments through exported + // values. Read those candidates without registering listeners on their host. + symbol.shapes.foreach: + case value: TermShape => host.publish(value) + case _ => softAssert(false, "A type parameter received a symbolic overload set") + host + }) + private def listenTypeArgument(symbol: VarSymbol)(listener: TermShape => Unit): Unit = + if isOwnedSym(symbol) then + def receive(shape: Shape): Unit = shape match + case value: TermShape => listener(value) + case _ => softAssert(false, "A type parameter received a symbolic overload set") + symbol.shapeListeners += receive + symbol.shapes.foreach(receive) + else importedTypeArgument(symbol).listen(listener) + + private def publishOwnedParameter(symbol: VarSymbol, shape: TermShape | NoShape): Unit = shape match case value: TermShape if isOwnedSym(symbol) => if symbol.shapes.add(value) then symbol.notifyShapeListeners(value) case _ => () - private def applyTypeArguments(defn: DefnShape, marks: Ls[Marks], args: Ls[Term], source: Term): Unit = - val params = defn.clsDef match - case S(cls) => cls.tparams.map(_.sym) - case N => defn.defn match - case td: TermDefinition => td.tparams.toList.flatten.map(_.sym) - case _ => Nil + private def publishTypeArgument(symbol: VarSymbol, shape: TermShape | NoShape): Unit = shape match + case value: TermShape if !isOwnedSym(symbol) => importedTypeArgument(symbol).publish(value) + case _ => publishOwnedParameter(symbol, shape) + + private def applyTypeArguments(callee: DefnShape | CallableTypeShape, marks: Ls[Marks], args: Ls[Term], source: Term): Unit = + val params = callee match + case callable: CallableTypeShape => callable.tparams + case defn: DefnShape => defn.clsDef match + case S(cls) => cls.tparams.map(_.sym) + case N => defn.defn match + case td: TermDefinition => td.tparams.toList.flatten.map(_.sym) + case _ => Nil if params.length != args.length then - resolError(source, msg"${defn.describe.capitalize} expected ${params.length} type ${ - "argument".pluralized(params.length)}, but got ${args.length}" -> defn.toLoc :: Nil) + resolError(source, msg"${callee.describe.capitalize} expected ${params.length} type ${ + "argument".pluralized(params.length)}, but got ${args.length}" -> callee.toLoc :: Nil) params.zip(args).foreach: (param, arg) => explicitTypeArguments += ((param, marks)) listenTypeValues(arg): tpe => @@ -371,13 +399,10 @@ class NewResolver: case TypeShape.Nominal(cls) if args.nonEmpty => val (head, context) = value.applicationHead def constrain(param: TyParam, pattern: DeclaredType): Unit = - def receive(shape: Shape): Unit = shape match - case shape: TermShape => shape.exit(context) match + listenTypeArgument(param.sym): shape => + shape.exit(context) match case actual: TermShape => inferTypeArguments(pattern, actual, marks) case NoShape => () - case _ => softAssert(false, "A type parameter received a symbolic overload set") - param.sym.shapeListeners += receive - param.sym.shapes.foreach(receive) def constrainNominal(nominal: NominalTypeShape): Unit = if nominal.defn is cls then cls.tparams.zip(args).foreach: (param, pattern) => nominal.bindings.get(param.sym).foreach: bound => @@ -523,7 +548,7 @@ class NewResolver: val sign = p.sign.map(sign => declaredType(typeResolution(sign), Map.empty)).orElse(signatureParameters.get(p.sym)) sign match case S(tpe) => inferTypeArguments(tpe, sh, marks) - case N => publishTypeArgument(p.sym, sh) + case N => publishOwnedParameter(p.sym, sh) private def zipArgumentShapes(mss: Ls[Marks], expectedCount: Int, hasRest: Bool, args: Term, src: Term, funSh: TermShape)(publish: (Int, TermShape | NoShape) => Unit): Unit = @@ -1249,7 +1274,7 @@ class NewResolver: case TyApp(underlying, args) => listen(underlying, discardMarks): shape => def instantiate(value: TermShape): Unit = value.applicationHead match - case (ds: DefnShape, marks) => applyTypeArguments(ds, marks, args, trm) + case (callee: (DefnShape | CallableTypeShape), marks) => applyTypeArguments(callee, marks, args, trm) case _ => () shape match case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => ()) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index bf92d74779..71f9d1aee8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -381,9 +381,11 @@ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool) /** Calls through annotations expose only the declared result. Argument shapes * constrain type parameters in the interface; they do not recover its implementation. + * Named generic declarations retain their parameter symbols so explicit type + * applications constrain the same parameters as inferred arguments. */ final case class CallableTypeShape(source: Term, paramLists: Ls[DeclaredParams], - result: Opt[DeclaredType]) extends NonAppTermShape: + result: Opt[DeclaredType], tparams: Ls[VarSymbol]) extends NonAppTermShape: require(paramLists.nonEmpty) def describe: Str = "function with a declared signature" def toLoc: Opt[Loc] = source.toLoc diff --git a/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls b/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls new file mode 100644 index 0000000000..c2cb90ac73 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls @@ -0,0 +1,10 @@ +#lang(0.3.x, strictResolution: true) + +module GenericLibrary with + class Known(val known: Int) + fun identity[A](x: A): A = x + class Methods with + fun identity[A](x: A): A = x + val known = identity(Known(3)) + val mapped = [Known(4)].map((item, ...) => Known(5)) + fun inferred[A](x: A) = x diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 688edda8c8..a665aec534 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -117,9 +117,7 @@ declare class Array[T] with fun pop(): T | undefined fun shift(): T | undefined fun concat(...arrays): Array[Any] - // These declarations retain the input element interface. Until method-level - // generic instantiation is supported, mapped results expose only Any. - fun map(f: (T, Int, Array[T]) -> Any): Array[Any] + fun map[U](f: (T, Int, Array[T]) -> U): Array[U] fun forEach(f: (T, Int, Array[T]) -> Any): undefined fun filter(f: (T, Int, Array[T]) -> Bool): Array[T] fun every(f: (T, Int, Array[T]) -> Bool): Bool diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 3566c689f7..1b64f20d85 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -84,8 +84,8 @@ values([Item(4)]) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.121: fun map(f: (T, Int, Array[T]) -> Any): Array[Any] -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.119: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:85 //│ ║ l.85: [1].map(x => x) diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls new file mode 100644 index 0000000000..d4bdf4870e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -0,0 +1,142 @@ +:. + +// Generic methods use the same inferred and explicit arguments as free functions. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Methods with + fun identity[A](x: A): A = x +fun test(m: Methods) = + [m.identity(First(1)).first, m.identity[Second](Second(2)).second] +test(new Methods) +//│ = [1, 2] + +// Class and method type arguments retain their separate scopes. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val value: T) with + fun map[U](f: T -> U): Box[U] = Box(f(value)) +fun test[T](box: Box[T], f: T -> Second) = box.map(f).value.second +[ (Box(First(1)) as Box[First]).map(x => x).value.first, + test(Box(First(1)), x => Second(x.first + 1)) ] +//│ = [1, 2] + +// Imported generic methods infer result arguments from callback results. +:expect [2] +class Item(val value: Int) +class Box[T](val value: T) +[Item(1)].map((item, ...) => Box(Item(item.value + 1))) + .map((box, ...) => box.value.value) +//│ = [2] + +// Instantiations of the same imported method must not contaminate each other. +:expect [[1], [2]] +class First(val first: Int) +class Second(val second: Int) +fun first() = [0].map((x, ...) => First(1)) +fun second() = [0].map((x, ...) => Second(2)) +[first().map((x, ...) => x.first), second().map((x, ...) => x.second)] +//│ = [[1], [2]] + +// Explicit type arguments preserve the requested opaque result interface. +:ge +class Base +class Child extends Base with + val extra = 1 +[new Child].map[Base]((x, ...) => x).map((x, ...) => x.extra) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.47: [new Child].map[Base]((x, ...) => x).map((x, ...) => x.extra) +//│ ║ ^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Imported methods validate explicit type-argument counts too. +:ge +[1].map[Int, Int]((x, ...) => x) +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.56: [1].map[Int, Int]((x, ...) => x) +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Function with a declared signature expected 1 type argument, but got 2 +//│ ║ l.56: [1].map[Int, Int]((x, ...) => x) +//│ ╙── ^^^^^^^ +//│ = [1] + +// Imported free functions and methods use the same inference as prelude methods. +import "../../mlscript-compile/GenericLibrary.mls" + +:expect [1, 2, 3] +class First(val first: Int) +class Second(val second: Int) +fun test(m: GenericLibrary.Methods) = m.identity[Second](Second(2)).second +[GenericLibrary.identity(First(1)).first, + test(new GenericLibrary.Methods), GenericLibrary.known.known] +//│ = [1, 2, 3] + +// Explicit method arguments also remain opaque for local declared interfaces. +:ge +class Base +class Child extends Base with + val extra = 1 +class Methods with + fun identity[A](x: A): A = x +fun test(m: Methods) = m.identity[Base](new Child).extra +test(new Methods) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.83: fun test(m: Methods) = m.identity[Base](new Child).extra +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Curried declared methods preserve their type arguments across parameter lists. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Methods with + fun pick[A](x: A)(ignored: Int): A = x +fun test(m: Methods) = + [m.pick(First(1))(0).first, m.pick[Second](Second(2))(0).second] +test(new Methods) +//│ = [1, 2] + +// Separate signatures preserve the method's generic parameter list. +:expect 4 +class Item(val value: Int) +class Methods with + fun identity[A]: A -> A = x => x +fun test(m: Methods) = m.identity[Item](Item(4)).value +test(new Methods) +//│ = 4 + +// FIXME: The defining unit's inferred Array argument lives in its local host. +// Importing the value must also transport that dependency, not only its shape. +:fixme +:expect [5] +GenericLibrary.mapped.map((item, ...) => item.known) +//│ ╔══[COMPILATION ERROR] This selection of member 'known' has no resolved target +//│ ║ l.115: GenericLibrary.mapped.map((item, ...) => item.known) +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[5]', got: 'undefined' + +// FIXME: The imported parameter-to-result listener still belongs to the defining +// resolver. Rebuilding a declared result works; importing this inferred flow does not. +:fixme +:expect 6 +class Item(val value: Int) +GenericLibrary.inferred(Item(6)).value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.127: GenericLibrary.inferred(Item(6)).value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '6', got: 'undefined' + +// FIXME: A callback reading a constructor field in a module initializer still +// crosses incompatible lexical capture paths, before any consumer imports it. +:fixme +:expect [5] +module Local with + class Known(val known: Int) + val mapped = [Known(4)].map((item, ...) => Known(item.known + 1)) +Local.mapped.map((item, ...) => item.known) +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 3d9524d5fb..0fe8b85502 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.274: fun +//│ ║ l.272: fun //│ ║ ^^^ -//│ ║ l.275: const(c: Int): Int32 +//│ ║ l.273: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 39b6e48fa95cf0aeaaacb7748ebb1ecb32f95717 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 14:52:01 +0800 Subject: [PATCH 070/228] Isolate resolver inference with contextual listeners and lazy imported hosts --- doc/new-resolution-suite-migration.md | 37 +- .../src/test/scala/hkmc2/CompilerTest.scala | 37 ++ .../src/test/scala/hkmc2/PublisherTest.scala | 114 +++- .../scala/hkmc2/semantics/Elaborator.scala | 18 +- .../main/scala/hkmc2/semantics/Importer.scala | 6 +- .../scala/hkmc2/semantics/NewResolver.scala | 598 +++++++++--------- .../hkmc2/semantics/NewResolverState.scala | 228 +++++++ .../main/scala/hkmc2/semantics/Shape.scala | 63 +- .../scala/hkmc2/semantics/TypeShape.scala | 10 +- .../main/scala/hkmc2/utils/Publisher.scala | 88 ++- .../test/mlscript-compile/GenericLibrary.mls | 1 + .../src/test/mlscript/basics/Indentation.mls | 8 + .../mlscript/newres/ConstructorResolution.mls | 4 +- .../src/test/mlscript/newres/Functions.mls | 14 + .../test/mlscript/newres/GenericMethods.mls | 28 +- .../test/mlscript/newres/NestedModules.mls | 9 +- 16 files changed, 854 insertions(+), 409 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index dad940573c..494eb85995 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -133,20 +133,27 @@ permit indexed access with an unknown result, without authorizing arbitrary memb Generic methods use the same marked parameter flow as generic free functions, including explicit arguments, callback-result inference, and curried signatures. `Array.map[U]` returns `Array[U]`. Declared callable shapes retain their type -parameters; consumer inference for imported parameters uses resolver-local hosts -instead of mutating shared declarations. Existing candidates on imported parameter -symbols remain readable. `newres/GenericMethods.mls` covers both local and imported -methods, and a compiler-cache regression checks that consumers leave the prelude -parameter's candidates and listeners unchanged. - -This is not yet general import of an inferred flow graph. The same worksheet keeps -explicit `:fixme` regressions for an exported mapped array whose argument lives in -the defining resolver's local host, and an imported generic function with an -inferred result dependent on its parameter. Both currently lose the result shape. -Transporting those dependencies requires the pending listener/flow-graph design; -copying only candidates is insufficient, and copied closures would still reference -the defining resolver. A third regression records a capture assertion when a map -callback reads a constructor field in a module initializer. +parameters. `NewResolverState` owns the inference graph and caches. Listeners have +type `TermShape => NewResolverState ?=> Unit`, so imported callbacks resolve hosts +through the consuming state. Importing touches only the accessed hosts: their +candidates and listeners are copied lazily, and caches consult individual source +entries on demand. Type-parameter references retain their originating host, so +exporter-private inference on third-party parameters survives re-exporting without +copying or enumerating whole resolver maps. Legacy annotation interpretations +also stay local instead of attaching consumer callbacks to shared prelude syntax. +Completion seals each file or diff-test block's recorded decisions and the original +host data read by erasure/lowering. Symbol flow and elimination listeners remain +active against private host data; they cannot change completed reference targets, +even between blocks sharing the same elaborator state. + +`newres/GenericMethods.mls` now covers exported mapped arrays and imported generic +functions and methods with inferred results. Compiler-cache tests check independent +consumers and unchanged source candidates, listeners, and legacy annotations. +Publisher tests cover cycles, reentrant replay, transitive private inference, and +bounded copying in the presence of unrelated exporter hosts. +The external-identity case in `newres/NestedModules.mls` also passes. A remaining +`:fixme` records a capture assertion when a map callback reads a constructor field +in a module initializer, before importing comes into play. Array declarations remain incomplete: `concat` returns `Array[Any]` because its rest arguments currently have no declared element constraint. This does not infer @@ -392,7 +399,7 @@ These changes also fix the sibling-subclass field-extraction regression in - `ctest`: all 47 selected compilation tests pass. - `catest`: all 20 application compilation fixtures pass in their existing mode. - `cwtest`: the WASM compilation fixture passes. -- `hkmc2AllTests/test`: all 946 tests pass, including 708 main tests, 18 application +- `hkmc2AllTests/test`: all 951 tests pass, including 708 main tests, 18 application tests, and 22 WASM tests. The main count includes directory configurations and new-resolution regressions; it is not a count of migrated worksheets. - All retained ports have reviewed goldens. Deferred worksheets retain their diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index 9f13a03ee5..3f6907b02d 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -283,6 +283,8 @@ class CompilerTest extends AnyFunSuite: val parameter = map.tparams.get.head.sym val originalShapes = parameter.shapes.toVector val originalListeners = parameter.shapeListeners.length + val callbackSignature = map.params.head.params.head.sign.get + val originalInterpretation = callbackSignature.typeInterpretation fs.write("/Generic.mls", """module Generic with | fun identity[A](x: A): A = x @@ -307,6 +309,41 @@ class CompilerTest extends AnyFunSuite: "Consumer inference must not publish into the shared prelude parameter") assert(parameter.shapeListeners.length == originalListeners, "Consumer inference must not attach listeners to the shared prelude parameter") + assert(callbackSignature.typeInterpretation == originalInterpretation, + "Interpreting a legacy signature must not cache consumer listeners on shared syntax") + + test("imported inferred results leave the cached definition's listeners and candidates unchanged"): + val fs = new InMemoryFileSystem(loadStandardLibrary()) + given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace = false + given Raise = diagnostic => fail(diagnostic.toString) + fs.write("/Generic.mls", """#lang(0.3.x, strictResolution: true) + |module Generic with + | class Known(val known: Int) + | val mapped = [Known(4)].map((item, ...) => Known(5)) + | fun identity[A](x: A) = x + |""".stripMargin) + val compiler = new MLsCompiler(_ => summon[Raise]) + compiler.compileModule(Path("/Generic.mls")) + val prelude = cctx.getPrelude(paths.preludeFile).ctx + val library = cctx.getElaboratedBlock(io.Path("/Generic.mls"), prelude) + val module = library.compilationUnit.defaultExport.get.asModOrObj.get.defn.get + val identity = module.body.members("identity").asTrm.get.defn.get + val parameters = identity.params.flatMap(_.params.map(_.sym)) ::: identity.tparams.get.map(_.sym) + val before = parameters.map(p => (p.shapes.toVector, p.shapeListeners.toVector)) + List("first", "second").foreach: field => + val path = s"/$field.mls" + fs.write(path, s"""#lang(0.3.x, strictResolution: true) + |import "./Generic.mls" + |class Item(val $field: Int) + |Generic.identity(Item(1)).$field + |Generic.mapped.map((item, ...) => item.known) + |""".stripMargin) + compiler.compileModule(Path(path)) + assert(parameters.map(p => (p.shapes.toVector, p.shapeListeners.toVector)) == before, + "A consumer must not change the cached definition's inference graph") test("compiler can report errors"): val (fs, compiler) = createCompiler() diff --git a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala index 8f27020c49..cfe4b3bf70 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala @@ -4,15 +4,17 @@ import org.scalatest.funsuite.AnyFunSuite import scala.collection.mutable.ArrayBuffer import hkmc2.utils.* +import hkmc2.semantics.{Elaborator, NewResolverState, Term, VarSymbol, DynShape, OpaqueTypeShape, TermShape} class PublisherTest extends AnyFunSuite: private class IntHost extends Host[Int]: def showDbg(using DebugPrinter): String = "integer shapes" - def publish(shape: Int): Unit = - if shapes.add(shape) then notifyShapeListeners(shape) + def publish(shape: Int)(using NewResolverState): Unit = + if currentShapes.add(shape) then notifyShapeListeners(shape) test("subscribing during notification replays each shape exactly once"): + given NewResolverState = new Elaborator.State().newResolverState val host = new IntHost val original = ArrayBuffer.empty[Int] val added = ArrayBuffer.empty[Int] @@ -28,6 +30,7 @@ class PublisherTest extends AnyFunSuite: assert(last.toList == List(1, 2)) test("publishing during replay does not replay the newly published shape again"): + given NewResolverState = new Elaborator.State().newResolverState val host = new IntHost host.publish(1) host.publish(2) @@ -37,3 +40,110 @@ class PublisherTest extends AnyFunSuite: if shape == 1 then host.publish(3) host.publish(4) assert(received.toList == List(1, 3, 2, 4)) + + test("imported listeners route captured destinations through each consumer state"): + val original = new Elaborator.State().newResolverState + val first = new Elaborator.State().newResolverState + val second = new Elaborator.State().newResolverState + val source = new IntHost + val target = new IntHost + locally: + given NewResolverState = original + // Include a cycle: each consumer must install a host copy before invoking + // listeners that can immediately return to that same host. + source.subscribeToShapes(target.publish) + target.subscribeToShapes(source.publish) + source.publish(1) + val sourceListeners = source.shapeListeners.size + val targetListeners = target.shapeListeners.size + source.publish(2)(using first) + source.publish(3)(using second) + assert(source.currentShapes(using original).toList == List(1)) + assert(target.currentShapes(using original).toList == List(1)) + assert(target.currentShapes(using first).toList == List(1, 2)) + assert(target.currentShapes(using second).toList == List(1, 3)) + assert(source.shapeListeners.size == sourceListeners) + assert(target.shapeListeners.size == targetListeners) + + test("imports lazily retain exporter-private inference on third-party hosts"): + val declaration = new Elaborator.State().newResolverState + val exporter = new Elaborator.State().newResolverState + val consumer = new Elaborator.State().newResolverState + val source = new IntHost + val target = new IntHost + source.publish(1)(using declaration) + source.subscribeToShapes(target.publish)(using exporter) + source.publish(2)(using exporter) + source.publish(3)(using consumer) + // Unrelated exporter state must not be copied merely to import this host. + (1 to 1000).foreach: n => + val unrelated = new IntHost + unrelated.publish(n)(using exporter) + val exported = source.inferenceHost(using exporter) + assert(consumer.copiedHostCount == 1) + exported.publish(4)(using consumer) + assert(source.currentShapes(using declaration).toList == List(1)) + assert(source.currentShapes(using exporter).toList == List(1, 2)) + assert(source.currentShapes(using consumer).toSet == Set(1, 3)) + assert(consumer.local(exported).shapes.toSet == Set(1, 2, 4)) + assert(target.currentShapes(using consumer).toSet == Set(1, 2, 4)) + assert(consumer.copiedHostCount == 3) + assert(target.currentShapes(using exporter).toList == List(1, 2)) + + test("re-exported listeners retain intermediate exporters' private destinations"): + val declaration = new Elaborator.State().newResolverState + val exporter = new Elaborator.State().newResolverState + val middle = new Elaborator.State().newResolverState + val consumer = new Elaborator.State().newResolverState + val source = new IntHost + val target = new IntHost + val referencedTarget = new IntHost + val reference = referencedTarget.inferenceHost(using declaration) + locally: + given NewResolverState = declaration + source.subscribeToShapes: shape => + target.publish(shape) + reference.publish(shape) + source.publish(1)(using declaration) + source.publish(2)(using exporter) + val exportedSource = source.inferenceHost(using exporter) + val exportedTarget = target.inferenceHost(using exporter) + val exportedReference = referencedTarget.inferenceHost(using exporter) + exportedSource.publish(3)(using middle) + val forwarded = middle.local(exportedSource) + forwarded.publish(4)(using consumer) + assert(source.currentShapes(using declaration).toList == List(1)) + assert(target.currentShapes(using exporter).toList == List(1, 2)) + assert(middle.local(exportedTarget).shapes.toList == List(1, 2, 3)) + assert(consumer.local(middle.local(exportedTarget)).shapes.toList == List(1, 2, 3, 4)) + assert(consumer.local(middle.local(exportedReference)).shapes.toList == List(1, 2, 3, 4)) + + test("completing a block freezes its nodes while retaining symbol and elimination flow"): + given owner: Elaborator.State = new Elaborator.State + given state: NewResolverState = owner.newResolverState + val variable = new VarSymbol(new syntax.Tree.Ident("x")) + val use = Term.NewSel(Term.SimpleRef(variable)(new syntax.Tree.Ident("x")), + new syntax.Tree.Ident("value"), None)(semantics.FlowSymbol.sel("value")) + use.hasDynamicTarget = true + variable.subscribeToShapes: shape => + if use.currentShapes.add(shape) then use.notifyShapeListeners(shape) + def publish(shape: TermShape)(using NewResolverState): Unit = + if variable.currentShapes.add(shape) then variable.notifyShapeListeners(shape) + val before = DynShape() + val after = OpaqueTypeShape(Term.UnitVal()) + publish(before) + val originalListeners = use.shapeListeners.toVector + state.completeBlock(use) + publish(after) + assert(variable.currentShapes.toSet == Set(before, after)) + assert(use.currentShapes.toSet == Set(before, after)) + assert(use.shapes.toSet == Set(before)) + assert(use.shapeListeners.toVector == originalListeners) + intercept[AssertionError]: + state.recordResolution(use, false)(use.tupleIndex = Some(0)) + assert(use.tupleIndex.isEmpty && use.hasDynamicTarget) + // A new node in the same worksheet state is still open for resolution. + val next = Term.UnitVal() + next.inferenceHost + assert(state.canResolve(next)) + state.recordResolution(use, true)(fail("An unchanged decision must not be written again")) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 0b7c33a70f..c445155c5e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -419,12 +419,12 @@ object Elaborator: abstract class Elem: def nme: Str - def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver, NewResolverState): Term def symbol: Opt[Symbol] def isImport: Bool final case class RefElem(sym: Symbol) extends Elem: val nme = sym.nme - def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term = + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver, NewResolverState): Term = if config.language.useNewResolution then sym match case sym: codegen.SimpleSymbol => @@ -442,7 +442,7 @@ object Elaborator: def symbol = S(sym) def isImport: Bool = false final case class SelElem(base: Elem, nme: Str, symOpt: Opt[MemberSymbol], isImport: Bool) extends Elem: - def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term = + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver, NewResolverState): Term = // * Same remark as in RefElem#ref val prefix = base.ref(Ident(base.nme)) val name = new Ident(nme).withLocOf(id) @@ -454,7 +454,7 @@ object Elaborator: Term.SynthSel(prefix, name)(symOpt, FlowSymbol.synthSel(nme), N, S(summon)) def symbol = symOpt final case class WildcardElem(nme: Str, sources: Ls[OpenSource]) extends Elem: - def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term = + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver, NewResolverState): Term = // Each ref is fresh. Locate its outer capture at the opening site too; // a chained wildcard reference may already have attached this location. val prefixes = sources.map(source => source.elem.ref(source.id).withoutLoc.withLocOf(source.id)) @@ -464,7 +464,7 @@ object Elaborator: def symbol: Opt[Symbol] = N def isImport: Bool = true final case class CaptElem(base: Elem, thru: DefinitionSymbol[?]) extends Elem: - def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver): Term = + def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver, NewResolverState): Term = Term.Capture(base.ref(Ident(base.nme)), thru) def symbol = base.symbol def isImport: Bool = false @@ -589,6 +589,7 @@ object Elaborator: case Private(sym: BlockMemberSymbol, modulePath: Str) class State: + lazy val newResolverState: NewResolverState = new NewResolverState(this) val suid = new Uid.Symbol.State given State = this private var _compilationUnit: Opt[CompilationUnit] = N @@ -722,6 +723,7 @@ extends Importer: import tl.* given TraceLogger = tl private given NewResolver = this + private given NewResolverState = state.newResolverState.withReporter(raise) val newResolution: Bool = config.language.useNewResolution @@ -1204,7 +1206,7 @@ extends Importer: private def interpretRef(ref: Term, interp: Interpretation): Term = if newResolution then interp match case Trm => requireTerm(ref) - case Tpe => typeResolution(ref) + case Tpe => ref.typeInterpretation = S(typeResolution(ref)) case _ => () ref @@ -2637,7 +2639,7 @@ extends Importer: sym.decl = S(p) if newResolution then sig.foreach: sign => listenTypeValues(sign): shape => - if sym.shapes.add(shape) then sym.notifyShapeListeners(shape) + if sym.currentShapes.add(shape) then sym.notifyShapeListeners(shape) (p, spd, aliases) def funParams(t: Tree): Ctxl[(ParamList, Ctx)] = @@ -2932,12 +2934,14 @@ extends Importer: given UnderCtx = new UnderCtx(N) val (res, newCtx) = block(sts, hasResult = false, resultInterp = Trm) // TODO handle name clashes + if newResolution then rstate.completeBlock(res) (res, newCtx) def topLevel(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) val (res, ctx) = block(sts, hasResult = false, resultInterp = Trm) computeVariances(res) + if newResolution then rstate.completeBlock(res) (res, ctx) def computeVariances(s: Statement): Unit = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala index dfedc50db1..5d00318b10 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala @@ -19,7 +19,7 @@ class Importer extends NewResolver: import tl.* - def importPath(path: Str, alias: Opt[syntax.Tree.Ident])(using cfg: Config): Import = + def importPath(path: Str, alias: Opt[syntax.Tree.Ident])(using cfg: Config, rs: NewResolverState): Import = // log(s"pwd: ${os.pwd}") // log(s"wd: ${wd}") @@ -42,7 +42,7 @@ class Importer extends NewResolver: file.ext match case "mjs" | "js" => - if newResolution then sym.shapes.add(DynShape()) + if newResolution then sym.currentShapes.add(DynShape()) Import(sym, file.toString, file) case "mls" => @@ -81,6 +81,6 @@ class Importer extends NewResolver: Import(sym, path, file) else - if newResolution then sym.shapes.add(DynShape()) + if newResolution then sym.currentShapes.add(DynShape()) Import(sym, path, file) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 7b3da4398e..90299ba6ce 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -20,6 +20,7 @@ import hkmc2.syntax.LetBind class NewResolver: self: Elaborator => import tl.* + import NewResolverState.Listener /* class Constraint(val lhs: Term, val rhs: Term): @@ -42,82 +43,85 @@ class NewResolver: */ // * The `FlowSymbol`s are currently used to uniquely identify terms - val appShapes: mutable.Map[(TermShape, FlowSymbol), AppShape] = mutable.Map.empty - val newShapes: mutable.Map[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = mutable.Map.empty - val introShapes: mutable.Map[IntroTerm, IntroShape] = mutable.Map.empty // TODO use symbols for faster lookup? - val symShapes: mutable.Map[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = mutable.Map.empty - private val declaredSymShapes = mutable.Map.empty[ - (BlockMemberSymbol, FlowSymbol, Ls[Marks], Map[VarSymbol, DeclaredType]), DeclaredSymShape] - private val selfShapes: mutable.Map[InnerSymbol, BaseShape] = mutable.Map.empty + private def appShapes(using rs: NewResolverState) = rs.appShapes + private def newShapes(using rs: NewResolverState) = rs.newShapes + private def introShapes(using rs: NewResolverState) = rs.introShapes + private def symShapes(using rs: NewResolverState) = rs.symShapes + private def declaredSymShapes(using rs: NewResolverState) = rs.declaredSymShapes + private def selfShapes(using rs: NewResolverState) = rs.selfShapes // Tuple and record AST nodes for which we have subscribed to spread operands. // Each node owns its shapes and listeners. Comparing nodes by structural equality // would skip subscriptions for a second equal expression, leaving its listeners // without results; compare their identities instead. - private val aggregateProducers = mutable.Set.empty[Identity[Tup | Rcd]] - val defnShapes: mutable.Map[DefinitionSymbol[?], DefnShape] = mutable.Map.empty + private def aggregateProducers(using rs: NewResolverState) = rs.aggregateProducers + private def defnShapes(using rs: NewResolverState) = rs.defnShapes /** Interpret types through completed symbolic candidates, independently of term overloads. */ - def typeResolution(term: Term): TypeResolution = term.typeInterpretation match - case S(result) => result - case N => - val result = new TypeResolution(term, messages => resolError(term, messages)) - term.typeInterpretation = S(result) // Register before following a potentially recursive alias. - def select(symbol: TypeSymbol): Unit = - term.withoutCaptures match - case ref: NewResolvable => - if !ref.resolvedTargets.contains(symbol) then ref.resolvedTargets ::= symbol - case _ => () - def definition(defn: Definition): Unit = defn match - case cls: ClassLikeDef => result.publish(TypeShape.Nominal(cls)) - case alias: TypeDef => result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) - case _ => result.publish(TypeShape.Abstract) - symbol.defn match - case S(defn) => definition(defn) - case N => symbol.defnListeners += definition - def reject(shape: Shape): Unit = - result.fail(msg"${shape.describe.capitalize} cannot be used as a type" -> shape.toLoc :: Nil) - result.publish(TypeShape.Abstract) - def selectMember(member: BlockMemberSymbol): Unit = member.onComplete: () => - member.asTpe.orElse(member.asModOrObj) match - case S(symbol) => select(symbol) - case N => - result.fail(msg"${member.describe.capitalize} cannot be used as a type" -> member.toLoc :: Nil) - result.publish(TypeShape.Abstract) - term match - // Imported prelude declarations can still contain legacy references. - case Ref(symbol: TypeSymbol) => select(symbol) - case Ref(member: BlockMemberSymbol) => selectMember(member) - case Capture(base, thru) => result.publish(TypeShape.Captured(typeResolution(base), thru)) - case TyApp(base, args) => result.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) - case Forall(_, _, body) => typeResolution(body).listen(result.publish) - case CompType(left, right, union) => - val l = typeResolution(left) - val r = typeResolution(right) - result.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) - case DynTy() => result.publish(TypeShape.Dynamic) - case FunTy(params, ret, _) => result.publish(TypeShape.Function(params, typeResolution(ret))) - case UnitVal() => result.publish(TypeShape.Unit) - case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => - result.publish(TypeShape.Parameter(sym)) - case Ref(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => - result.publish(TypeShape.Parameter(sym)) - case Tup(fields) if fields.forall(_.isInstanceOf[Fld]) => - result.publish(TypeShape.Tuple(fields.collect { case Fld(_, sign, _) => typeResolution(sign) })) - case _: WildcardTy | _: Neg | _: Rcd | _: Tup | _: Lit | Missing | Error() => + def typeResolution(term: Term)(using rs: NewResolverState): TypeResolution = + rs.typeInterpretations.get(new Identity(term)).orElse(term.typeInterpretation) match + case S(result) => result + case N => + val result = new TypeResolution(term, messages => resolError(term, messages)) + // Cache before following a recursive alias. Imported legacy annotations + // have no stored interpretation: their new inference graph must remain + // private to this consumer, including listeners on synthesized hosts. + rs.typeInterpretations(new Identity(term)) = result + def select(symbol: TypeSymbol)(using NewResolverState): Unit = + term.withoutCaptures match + case ref: NewResolvable => + rstate.recordResolution(ref, ref.resolvedTargets.contains(symbol))(ref.resolvedTargets ::= symbol) + case _ => () + def definition(defn: Definition)(using NewResolverState): Unit = defn match + case cls: ClassLikeDef => result.publish(TypeShape.Nominal(cls)) + case alias: TypeDef => result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) + case _ => result.publish(TypeShape.Abstract) + symbol.defn match + case S(defn) => definition(defn) + case N => symbol.defnListeners += definition + def reject(shape: Shape)(using NewResolverState): Unit = + result.fail(msg"${shape.describe.capitalize} cannot be used as a type" -> shape.toLoc :: Nil) result.publish(TypeShape.Abstract) - case _ => listen(term, discardMarks = true): - case shape: SymShape => shape.sym.onComplete: () => - shape.sym.asTpe.orElse(shape.sym.asModOrObj) match - case S(symbol) => select(symbol) - case N => reject(shape) - case shape: TermShape => reject(shape) - result + def selectMember(member: BlockMemberSymbol)(using NewResolverState): Unit = member.onComplete: () => + member.asTpe.orElse(member.asModOrObj) match + case S(symbol) => select(symbol) + case N => + result.fail(msg"${member.describe.capitalize} cannot be used as a type" -> member.toLoc :: Nil) + result.publish(TypeShape.Abstract) + term match + // Imported prelude declarations can still contain legacy references. + case Ref(symbol: TypeSymbol) => select(symbol) + case Ref(member: BlockMemberSymbol) => selectMember(member) + case Capture(base, thru) => result.publish(TypeShape.Captured(typeResolution(base), thru)) + case TyApp(base, args) => result.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) + case Forall(_, _, body) => typeResolution(body).listen(result.publish) + case CompType(left, right, union) => + val l = typeResolution(left) + val r = typeResolution(right) + result.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) + case DynTy() => result.publish(TypeShape.Dynamic) + case FunTy(params, ret, _) => result.publish(TypeShape.Function(params, typeResolution(ret))) + case UnitVal() => result.publish(TypeShape.Unit) + case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => + result.publish(TypeShape.Parameter(sym, sym.inferenceHost)) + case Ref(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => + result.publish(TypeShape.Parameter(sym, sym.inferenceHost)) + case Tup(fields) if fields.forall(_.isInstanceOf[Fld]) => + result.publish(TypeShape.Tuple(fields.collect { case Fld(_, sign, _) => typeResolution(sign) })) + case _: WildcardTy | _: Neg | _: Rcd | _: Tup | _: Lit | Missing | Error() => + result.publish(TypeShape.Abstract) + case _ => listen(term, discardMarks = true): + case shape: SymShape => shape.sym.onComplete: () => + shape.sym.asTpe.orElse(shape.sym.asModOrObj) match + case S(symbol) => select(symbol) + case N => reject(shape) + case shape: TermShape => reject(shape) + result /** Register complete signatures while resolution can still publish candidates. Arrow operands * need their own interpretations: erasure may consume the arrows as physical parameter lists. */ - def registerSignature(sign: Term): Unit = - typeResolution(sign) + def registerSignature(sign: Term)(using NewResolverState): Unit = + sign.typeInterpretation = S(typeResolution(sign)) sign match case Forall(_, _, body) => registerSignature(body) case FunTy(lhs, rhs, _) => @@ -133,22 +137,16 @@ class NewResolver: * interfaces such as Tree[A] = Node[A] | Empty would accumulate equivalent * A-to-A environments every time a recursive field is selected. */ - private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType]): DeclaredType = - resolution.shapes.toList match - case TypeShape.Parameter(symbol) :: Nil if bindings.contains(symbol) => bindings(symbol) + private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType])(using NewResolverState): DeclaredType = + resolution.currentShapes.toList match + case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => bindings(symbol) case _ => DeclaredType(resolution, bindings) - private class TypeValues extends Host[TermShape]: - def showDbg(using DebugPrinter): Str = "declared type values" - def publish(shape: TermShape): Unit = - if shapes.add(shape) then notifyShapeListeners(shape) - def listen(listener: TermShape => Unit): Unit = - subscribeToShapes(listener) - private val typeValues = mutable.Map.empty[DeclaredType, TypeValues] - private val abstractTypes = mutable.Map.empty[TypeResolution, DeclaredType] + private def typeValues(using rs: NewResolverState) = rs.typeValues + private def abstractTypes(using rs: NewResolverState) = rs.abstractTypes // An omitted argument in a nominal annotation must not subscribe to the // class parameter's constructor inference and silently refine that annotation. - private def abstractType(source: TypeResolution): DeclaredType = abstractTypes.getOrElseUpdate(source, { + private def abstractType(source: TypeResolution)(using NewResolverState): DeclaredType = abstractTypes.getOrElseUpdate(source, { val resolution = new TypeResolution(source.source, source.fail) resolution.publish(TypeShape.Abstract) declaredType(resolution, Map.empty) @@ -158,21 +156,21 @@ class NewResolver: * available. Subscribe to the type graph, never to values flowing into it. * Shared hosts retain subscriptions for forward and recursive type references. */ - def listenTypeValues(sign: Term)(listener: TermShape => Unit): Unit = + def listenTypeValues(sign: Term)(listener: Listener)(using NewResolverState): Unit = listenTypeValues(declaredType(typeResolution(sign), Map.empty))(listener) - private def listenTypeValues(tpe: DeclaredType)(listener: TermShape => Unit): Unit = + private def listenTypeValues(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = typeValues.get(tpe) match case S(host) => host.listen(listener) case N => val host = new TypeValues typeValues(tpe) = host host.listen(listener) - def follow(current: DeclaredType, args: Ls[DeclaredType], aliases: Set[TypeResolution], publish: TermShape => Unit): Unit = + def follow(current: DeclaredType, args: Ls[DeclaredType], aliases: Set[TypeResolution], publish: Listener)(using NewResolverState): Unit = current.resolution.listen: shape => - def bind(params: Ls[TyParam]): Map[VarSymbol, DeclaredType] = + def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = current.bindings ++ params.map(_.sym).zip(args.padTo(params.length, abstractType(current.resolution))) - def next(res: TypeResolution): Unit = follow(declaredType(res, current.bindings), Nil, aliases, publish) + def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current.bindings), Nil, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) case TypeShape.Inferred(value) => publish(value) @@ -197,10 +195,10 @@ class NewResolver: case N => publish(OpaqueTypeShape(tpe.resolution.source)) case TypeShape.Applied(base, params) => follow(declaredType(base, current.bindings), params.map(declaredType(_, current.bindings)), aliases, publish) - case TypeShape.Parameter(symbol) => current.bindings.get(symbol) match + case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match case S(bound) => follow(bound, Nil, aliases, publish) case N => - listenTypeArgument(symbol)(publish) + listenTypeArgument(host)(publish) case TypeShape.Captured(base, thru) => follow(declaredType(base, current.bindings), args, aliases, shape => publish(shape match @@ -227,9 +225,9 @@ class NewResolver: // A separate full signature also annotates the implementation's parameters. // Register these before its body is elaborated, so observed calls can never // publish inferred alternatives for parameters behind this boundary. - private val signatureParameters = mutable.Map.empty[VarSymbol, DeclaredType] - def registerParameterSignature(paramLists: Ls[ParamList], sign: Term): Unit = - def bind(paramLists: Ls[ParamList], tpe: DeclaredType): Unit = paramLists match + private def signatureParameters(using rs: NewResolverState) = rs.signatureParameters + def registerParameterSignature(paramLists: Ls[ParamList], sign: Term)(using NewResolverState): Unit = + def bind(paramLists: Ls[ParamList], tpe: DeclaredType)(using NewResolverState): Unit = paramLists match case Nil => () case ps :: tail => listenTypeValues(tpe): case callable: CallableTypeShape => @@ -238,18 +236,18 @@ class NewResolver: if param.sign.isEmpty then sign.foreach: tpe => signatureParameters(param.sym) = tpe listenTypeValues(tpe): shape => - if param.sym.shapes.add(shape) then param.sym.notifyShapeListeners(shape) + if param.sym.currentShapes.add(shape) then param.sym.notifyShapeListeners(shape) callable.result.foreach(bind(tail, _)) case _ => () bind(paramLists, declaredType(typeResolution(sign), Map.empty)) /** Synthesized constructor fields retain their source declaration on the symbol. */ - private def resultSignature(td: TermDefinition): Opt[Term] = td.tsym.decl match + private def resultSignature(td: TermDefinition)(using NewResolverState): Opt[Term] = td.tsym.decl match case S(p: Param) => p.sign case _ => td.resultSignature - private val capturedTypes = mutable.Map.empty[(DeclaredType, TermSymbol), DeclaredType] - private def captureType(bound: DeclaredType, scope: TermSymbol): DeclaredType = + private def capturedTypes(using rs: NewResolverState) = rs.capturedTypes + private def captureType(bound: DeclaredType, scope: TermSymbol)(using NewResolverState): DeclaredType = capturedTypes.getOrElseUpdate((bound, scope), { val captured = new TypeResolution(bound.resolution.source, bound.resolution.fail) captured.publish(TypeShape.Captured(bound.resolution, scope)) @@ -257,15 +255,15 @@ class NewResolver: }) private def listenDeclaredMember(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], flow: FlowSymbol, - source: Term, selected: DefinitionSymbol[?] => Unit)(listener: TermShape => Unit): Unit = + source: Term, selected: ShapeListener[DefinitionSymbol[?]])(listener: Listener)(using NewResolverState): Unit = member.onComplete: () => member.asModOrObj.orElse(member.asTrm).orElse(member.asCls) match case S(symbol: TermSymbol) if !symbol.isInstanceOf[ClassCtorSymbol] => selected(symbol) val td = symbol.defn.get - def publish(shape: TermShape): Unit = + def publish(shape: TermShape)(using NewResolverState): Unit = val generic = shape match - case callable: CallableTypeShape => callable.copy(tparams = td.tparams.toList.flatten.map(_.sym)) + case callable: CallableTypeShape => callable.copy(tparams = td.tparams.toList.flatten.map(p => new TypeShape.Parameter(p.sym, p.sym.inferenceHost))) case _ => shape // A synthesized field's signature is in the constructor parameter's // scope. Written member signatures are in their own definition scope. @@ -275,12 +273,12 @@ class NewResolver: exited match case value: TermShape => listener(value) case NoShape => () - def signature(sign: Term): DeclaredType = + def signature(sign: Term)(using NewResolverState): DeclaredType = // Legacy annotations have plain references instead of Capture nodes. // Rebase substituted class parameters into this member's scope, just // as a new-resolution Capture does, before the member exits it. val legacyParameters = mutable.Set.empty[VarSymbol] - def visit(term: Term): Unit = term match + def visit(term: Term)(using NewResolverState): Unit = term match case Ref(symbol: VarSymbol) if bindings.contains(symbol) => legacyParameters += symbol case _ => term.subTerms.foreach(visit) if !td.tsym.decl.exists(_.isInstanceOf[Param]) then visit(sign) @@ -301,77 +299,56 @@ class NewResolver: // selecting it does not inspect an instance field or method body. fromBMS(member, flow, Nil, listener, source, selected) - def isOwnedSym(sym: Symbol): Bool = - sym.getState is state - - def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])]): Unit = raise: + def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])])(using rs: NewResolverState): Unit = rs.report: ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) /** Explicit and inferred type arguments use the same entry/exit paths as term * arguments. An explicit argument fixes that instantiation; its value argument * must not add a more precise implementation shape to the declared interface. */ - private val explicitTypeArguments = mutable.Set.empty[(VarSymbol, Ls[Marks])] - // Imported declarations are shared between compilation units. Their inferred - // arguments belong to this resolver, not to the imported symbols' mutable hosts. - // Both hosts carry the same entry/exit marks used for locally defined functions. - private val importedTypeArguments = mutable.Map.empty[VarSymbol, TypeValues] - private def importedTypeArgument(symbol: VarSymbol): TypeValues = - importedTypeArguments.getOrElseUpdate(symbol, { - val host = new TypeValues - // Completed imports can already expose inferred arguments through exported - // values. Read those candidates without registering listeners on their host. - symbol.shapes.foreach: - case value: TermShape => host.publish(value) - case _ => softAssert(false, "A type parameter received a symbolic overload set") - host - }) - private def listenTypeArgument(symbol: VarSymbol)(listener: TermShape => Unit): Unit = - if isOwnedSym(symbol) then - def receive(shape: Shape): Unit = shape match - case value: TermShape => listener(value) - case _ => softAssert(false, "A type parameter received a symbolic overload set") - symbol.shapeListeners += receive - symbol.shapes.foreach(receive) - else importedTypeArgument(symbol).listen(listener) + private def listenTypeArgument(symbol: VarSymbol)(listener: Listener)(using NewResolverState): Unit = + listenTypeArgument(symbol.inferenceHost)(listener) - private def publishOwnedParameter(symbol: VarSymbol, shape: TermShape | NoShape): Unit = shape match - case value: TermShape if isOwnedSym(symbol) => - if symbol.shapes.add(value) then symbol.notifyShapeListeners(value) - case _ => () + private def listenTypeArgument(host: Publisher.Data[Shape])(listener: Listener)(using NewResolverState): Unit = + host.subscribe: + case value: TermShape => listener(value) + case _ => softAssert(false, "A type parameter received a symbolic overload set") + + private def publishParameter(symbol: VarSymbol, shape: TermShape | NoShape)(using NewResolverState): Unit = + publishParameter(symbol.inferenceHost, shape) - private def publishTypeArgument(symbol: VarSymbol, shape: TermShape | NoShape): Unit = shape match - case value: TermShape if !isOwnedSym(symbol) => importedTypeArgument(symbol).publish(value) - case _ => publishOwnedParameter(symbol, shape) + private def publishParameter(host: Publisher.Data[Shape], shape: TermShape | NoShape)(using NewResolverState): Unit = shape match + case value: TermShape => host.publish(value) + case _ => () - private def applyTypeArguments(callee: DefnShape | CallableTypeShape, marks: Ls[Marks], args: Ls[Term], source: Term): Unit = + private def applyTypeArguments(callee: DefnShape | CallableTypeShape, marks: Ls[Marks], args: Ls[Term], source: Term)(using NewResolverState): Unit = val params = callee match case callable: CallableTypeShape => callable.tparams case defn: DefnShape => defn.clsDef match - case S(cls) => cls.tparams.map(_.sym) + case S(cls) => cls.tparams.map(p => new TypeShape.Parameter(p.sym, p.sym.inferenceHost)) case N => defn.defn match - case td: TermDefinition => td.tparams.toList.flatten.map(_.sym) + case td: TermDefinition => td.tparams.toList.flatten.map(p => new TypeShape.Parameter(p.sym, p.sym.inferenceHost)) case _ => Nil if params.length != args.length then resolError(source, msg"${callee.describe.capitalize} expected ${params.length} type ${ "argument".pluralized(params.length)}, but got ${args.length}" -> callee.toLoc :: Nil) params.zip(args).foreach: (param, arg) => - explicitTypeArguments += ((param, marks)) + rstate.markExplicitTypeArgument(param.symbol, marks) listenTypeValues(arg): tpe => - publishTypeArgument(param, tpe.enter(marks)) + publishParameter(param.host, tpe.enter(marks)) // Install each constraint edge before subscribing: callback parameter/result // flow can revisit it immediately. Distinct instantiations retain their marks. - private val typeConstraints = mutable.Set.empty[(DeclaredType, TermShape, Ls[Marks])] - private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks]): Unit = + private def typeConstraints(using rs: NewResolverState) = rs.typeConstraints + private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = if !typeConstraints.add((tpe, value, marks)) then return def follow(tpe: DeclaredType, args: Ls[DeclaredType], captures: Ls[Marks], - seen: Set[TypeResolution]): Unit = if !seen(tpe.resolution) then + seen: Set[TypeResolution])(using NewResolverState): Unit = if !seen(tpe.resolution) then val next = seen + tpe.resolution tpe.resolution.listen: - case TypeShape.Parameter(symbol) => tpe.bindings.get(symbol) match + case TypeShape.Parameter(symbol, host) => tpe.bindings.get(symbol) match case S(bound) => follow(bound, Nil, captures, next) - case N if !explicitTypeArguments((symbol, marks)) => publishTypeArgument(symbol, value.enter(captures)) + case N if !rstate.hasExplicitTypeArgument(symbol, marks) => publishParameter(host, value.enter(captures)) case _ => () case TypeShape.Captured(base, thru) => follow(declaredType(base, tpe.bindings), args, @@ -398,12 +375,12 @@ class NewResolver: case NoShape => () case TypeShape.Nominal(cls) if args.nonEmpty => val (head, context) = value.applicationHead - def constrain(param: TyParam, pattern: DeclaredType): Unit = + def constrain(param: TyParam, pattern: DeclaredType)(using NewResolverState): Unit = listenTypeArgument(param.sym): shape => shape.exit(context) match case actual: TermShape => inferTypeArguments(pattern, actual, marks) case NoShape => () - def constrainNominal(nominal: NominalTypeShape): Unit = + def constrainNominal(nominal: NominalTypeShape)(using NewResolverState): Unit = if nominal.defn is cls then cls.tparams.zip(args).foreach: (param, pattern) => nominal.bindings.get(param.sym).foreach: bound => listenTypeValues(bound): shape => @@ -424,9 +401,9 @@ class NewResolver: * Read an implementation only while checking it against its declared interface; * calls through that interface continue to expose only the declared result. */ - private def constrainFunction(expected: DeclaredType, actual: TermShape, marks: Ls[Marks]): Unit = + private def constrainFunction(expected: DeclaredType, actual: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = val context = actual.applicationHead._2 - def results(listener: TermShape => Unit): Unit = actual.applicationHead._1 match + def results(listener: Listener)(using NewResolverState): Unit = actual.applicationHead._1 match case intro: IntroShape => intro.trm match case Lam(_, body) => listenTerm(body)(listener) case _ => () @@ -437,7 +414,7 @@ class NewResolver: case N => td.body.foreach(listenTerm(_)(listener)) case _ => () case _ => () - def matchLists(expected: DeclaredType, remaining: Ls[(ParamList, Ls[Marks])]): Unit = + def matchLists(expected: DeclaredType, remaining: Ls[(ParamList, Ls[Marks])])(using NewResolverState): Unit = listenTypeValues(expected): case Marked(callable: CallableTypeShape, expectedContext) => remaining match case (params, _) :: tail => @@ -486,7 +463,7 @@ class NewResolver: case NoShape => () case _ => () wanted.result.foreach: ret => - def receive(shape: TermShape): Unit = shape.exit(actualContext).enter(expectedContext) match + def receive(shape: TermShape)(using NewResolverState): Unit = shape.exit(actualContext).enter(expectedContext) match case value: TermShape => inferTypeArguments(ret, value, marks) case NoShape => () givenType.paramLists.tail match @@ -495,27 +472,27 @@ class NewResolver: case _ => () case _ => matchLists(expected, actual.unappliedParams) - private def listenSignatureResult(tpe: DeclaredType, count: Int)(listener: TermShape => Unit): Unit = + private def listenSignatureResult(tpe: DeclaredType, count: Int)(listener: Listener)(using NewResolverState): Unit = if count == 0 then listenTypeValues(tpe)(listener) else listenTypeValues(tpe): case Marked(callable: CallableTypeShape, _) => callable.result.foreach(listenSignatureResult(_, count - 1)(listener)) case _ => () - private val tupleArrayParents = mutable.Map.empty[TupleShape, NominalTypeShape] + private def tupleArrayParents(using rs: NewResolverState) = rs.tupleArrayParents /** A tuple is an Array whose element argument is the union of its field shapes. * Cache the parent before following fields: a recursive tuple can require its * own Array interface while one of those fields is still being resolved. */ - private[semantics] def tupleArrayParent(tuple: TupleShape): NominalTypeShape = - tupleArrayParents.get(tuple) match + private[semantics] def tupleArrayParent(tuple: TupleShape)(using NewResolverState): NominalTypeShape = + tupleArrayParents.get(new Identity(tuple)) match case S(parent) => parent case N => val cls = prelude.builtins.Array.defn.get softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") val elements = new TypeResolution(tuple.source, messages => resolError(tuple.source, messages)) val parent = NominalTypeShape(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), N) - tupleArrayParents(tuple) = parent + tupleArrayParents(new Identity(tuple)) = parent tuple.segments.foreach: case field: TupleShape.Fixed => listenTupleField(field): value => elements.publish(TypeShape.Inferred(value)) @@ -524,8 +501,8 @@ class NewResolver: case NoShape => () parent - private def listenTupleField(field: TupleShape.Fixed)(listener: TermShape => Unit): Unit = - def receive(shape: TermShape): Unit = shape.exit(field.marks) match + private def listenTupleField(field: TupleShape.Fixed)(listener: Listener)(using NewResolverState): Unit = + def receive(shape: TermShape)(using NewResolverState): Unit = shape.exit(field.marks) match case value: TermShape => listener(value) case NoShape => () field match @@ -536,36 +513,37 @@ class NewResolver: /** Subscribe once to complete argument-tuple candidates. A pending spread is not * an argument, nor evidence of an arity mismatch; each resolved combination is. */ - def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Term, src: Term, funSh: TermShape): Unit = + def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Term, src: Term, funSh: TermShape)(using NewResolverState): Unit = val params = ps ::: r.toList zipArgumentShapes(mss, ps.length, r.nonEmpty, args, src, funSh): (index, shape) => constrainParameter(params(index), shape, mss) - private def constrainParameter(p: Param, shape: TermShape | NoShape, marks: Ls[Marks]): Unit = + private def constrainParameter(p: Param, shape: TermShape | NoShape, marks: Ls[Marks])(using NewResolverState): Unit = shape match case NoShape => () case sh: TermShape => val sign = p.sign.map(sign => declaredType(typeResolution(sign), Map.empty)).orElse(signatureParameters.get(p.sym)) + .orElse(p.sym.getState.newResolverState.signatureParameters.get(p.sym)) sign match case S(tpe) => inferTypeArguments(tpe, sh, marks) - case N => publishOwnedParameter(p.sym, sh) + case N => publishParameter(p.sym, sh) private def zipArgumentShapes(mss: Ls[Marks], expectedCount: Int, hasRest: Bool, - args: Term, src: Term, funSh: TermShape)(publish: (Int, TermShape | NoShape) => Unit): Unit = - def matchSegments(tuple: TupleShape, marks: Marks): Unit = + args: Term, src: Term, funSh: TermShape)(publish: (Int, TermShape | NoShape) => NewResolverState ?=> Unit)(using NewResolverState): Unit = + def matchSegments(tuple: TupleShape, marks: Marks)(using NewResolverState): Unit = val segments = tuple.segments val knownCount = segments.count(_.isInstanceOf[TupleShape.Fixed]) // Distribute only successful argument counts. Unknown spreads retain all // possible fixed positions and the residual tail for a rest parameter. val extra = (expectedCount - knownCount).max(0) - def assign(segment: TupleShape.Segment, positions: Range): Unit = + def assign(segment: TupleShape.Segment, positions: Range)(using NewResolverState): Unit = positions.foreach: index => segment match case field: TupleShape.Fixed => listenTupleField(field): sh => publish(index, sh.exit(marks).enter(mss)) case TupleShape.Unknown(_, inner, value) => publish(index, value.exit(inner).exit(marks).enter(mss)) - def loop(rest: Ls[TupleShape.Segment], before: Int, unknownBefore: Bool): Unit = rest match + def loop(rest: Ls[TupleShape.Segment], before: Int, unknownBefore: Bool)(using NewResolverState): Unit = rest match case Nil => () case (field: TupleShape.Fixed) :: tail => val unknownAfter = tail.exists(_.isInstanceOf[TupleShape.Unknown]) @@ -587,7 +565,7 @@ class NewResolver: // unknown segment, retaining the suffix that must remain. Publishing // separate concrete tails here would turn uncertainty into false arity // failures when the rest tuple is spread into another call. - def drop(xs: Ls[TupleShape.Segment], count: Int, approximate: Bool): Ls[TupleShape.Segment] = + def drop(xs: Ls[TupleShape.Segment], count: Int, approximate: Bool)(using NewResolverState): Ls[TupleShape.Segment] = if count == 0 then xs else xs match case Nil => Nil @@ -606,15 +584,15 @@ class NewResolver: case (tuple, marks) => matchSegments(tuple, marks) private def checkArgumentArity(args: Term, expected: Int, rest: Bool, src: Term, - callable: TermShape)(matched: ((TupleShape, Marks)) => Unit): Unit = - val reportedCounts = mutable.Set.empty[Int] + callable: TermShape)(matched: ShapeListener[(TupleShape, Marks)])(using NewResolverState): Unit = + val reportKey = new Object listenTerm(args): case Marked(tuple: TupleShape, marks) => val known = tuple.segments.count(_.isInstanceOf[TupleShape.Fixed]) val unknown = known != tuple.segments.length val mismatch = (!unknown && known < expected) || (!rest && known > expected) if mismatch then - if reportedCounts.add(known) then + if rstate.reportedArities.getOrElseUpdate(reportKey, mutable.Set.empty).add(known) then val count = if unknown then msg"at least ${known}" else msg"${known}" resolError(src, msg"${callable.describe.capitalize} expected ${expected} ${ "argument".pluralized(expected)}, but got ${count}" -> callable.toLoc :: Nil) @@ -625,33 +603,33 @@ class NewResolver: * A class overload takes precedence over its term companion in this context. * Keep every candidate so lowering can diagnose ambiguity independently of * the order in which definitions and receiver shapes become available. */ - def constructorPattern(res: Pattern.Constructor): Unit = if newResolution then + def constructorPattern(res: Pattern.Constructor)(using NewResolverState): Unit = if newResolution then val lhs = res.target - def select(sym: DefinitionSymbol[?]): Bool = + def select(sym: DefinitionSymbol[?])(using NewResolverState): Bool = lhs.withoutCaptures match case trm: NewResolvable => - if !trm.resolvedTargets.contains(sym) then trm.resolvedTargets ::= sym + rstate.recordResolution(trm, trm.resolvedTargets.contains(sym))(trm.resolvedTargets ::= sym) case _ => () if res.resolvedTargets.contains(sym) then false else - res.resolvedTargets ::= sym + rstate.recordResolution(res, false)(res.resolvedTargets ::= sym) true - def reject(sh: TermShape): Unit = - res.isErroneous = true + def reject(sh: TermShape)(using NewResolverState): Unit = + rstate.markError(res)(res.isErroneous = true) resolError(res, msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> sh.toLoc :: Nil) - def classPattern(cls: ClassLikeDef): Unit = if select(cls.sym) then + def classPattern(cls: ClassLikeDef)(using NewResolverState): Unit = if select(cls.sym) then val assoc = res.arguments match case N => Nil case S(args) => cls.paramsOpt match case N => if args.nonEmpty || cls.isInstanceOf[ClassDef] then - res.isErroneous = true + rstate.markError(res)(res.isErroneous = true) resolError(res, msg"${cls.describe.capitalize} does not take pattern arguments." -> cls.toLoc :: Nil) Nil case S(ps) => if ps.restParam.nonEmpty then TODO(ps.restParam) if args.sizeCompare(ps.params) =/= 0 then - res.isErroneous = true + rstate.markError(res)(res.isErroneous = true) resolError(res, msg"${cls.describe.capitalize} expected ${ps.params.length} ${ "pattern argument".pluralized(ps.params.length)}, but got ${args.length}" -> cls.toLoc :: Nil) @@ -665,16 +643,16 @@ class NewResolver: // report another error for this already-diagnosed class. if cls.body.members.get(fldSym.nme).contains(fldSym) then (fldSym -> a) :: Nil else - res.isErroneous = true + rstate.markError(res)(res.isErroneous = true) Nil case _ => - res.isErroneous = true + rstate.markError(res)(res.isErroneous = true) resolError(res, msg"Pattern argument requires an accessible constructor field." -> p.toLoc :: Nil) Nil - if !res.isErroneous then - val psh = CtorPatternShape(cls, assoc, res, FlowSymbol.pat()) - if res.shapes.add(psh) then res.notifyShapeListeners(psh) - def valuePattern(sh: TermShape): Unit = sh.applicationHead match + if !rstate.hasError(res, res.isErroneous) then + val psh = CtorPatternShape(cls, assoc, res, FlowSymbol.pat()(using rstate.owner)) + if res.currentShapes.add(psh) then res.notifyShapeListeners(psh) + def valuePattern(sh: TermShape)(using NewResolverState): Unit = sh.applicationHead match case (ds: DefnShape, _) => ds.defn match case cls: ClassDef => classPattern(cls) case obj: ModuleOrObjectDef if obj.sym.asObj.isDefined => classPattern(obj) @@ -684,7 +662,7 @@ class NewResolver: case _ => reject(sh) case _ => reject(sh) lhs.withoutCaptures match - case Term.Error() => res.isErroneous = true + case Term.Error() => rstate.markError(res)(res.isErroneous = true) case Term.Ref(sym: VarSymbol) if sym.decl.exists(_.isPatternConstructor) => res.resolvedTargets ::= sym case Term.SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isPatternConstructor) => @@ -701,20 +679,20 @@ class NewResolver: case S(cls) => classPattern(cls) case N => softAssert(false, "Completed pattern member has no definition") case N => - fromBMS(bms, FlowSymbol.pat(), sh.markss, valuePattern, lhs, _ => ()) + fromBMS(bms, FlowSymbol.pat()(using rstate.owner), sh.markss, valuePattern, lhs, _ => ()) case sh: TermShape => valuePattern(sh) /** Propagate possible values to pattern bindings. Constructor tests filter by * nominal class; guards and literal tests may conservatively retain shapes. * Both scrutinee and constructor shapes can arrive after this registration. */ - def matchShapePat(shape: Shape, pattern: Pattern)(matched: Shape => Unit): Unit = + def matchShapePat(shape: Shape, pattern: Pattern)(matched: ShapeListener[Shape])(using NewResolverState): Unit = pattern match case al @ Pattern.Alias(pat, _) => matchShapePat(shape, pat): sh => // Rejected duplicate/negated bindings have no allocated symbol. Pattern // validation already reports them; only valid bindings receive flow. al.symbolOption.foreach: symbol => - if symbol.shapes.add(sh) then symbol.notifyShapeListeners(sh) + if symbol.currentShapes.add(sh) then symbol.notifyShapeListeners(sh) matched(sh) case Pattern.Wildcard() | Pattern.Literal(_) => matched(shape) case Pattern.Chain(left, right) => @@ -726,9 +704,9 @@ class NewResolver: // Compilation-strategy annotations do not change the pattern's bindings. case Pattern.Annotated(pat, _) => matchShapePat(shape, pat)(matched) case ctor: Pattern.Constructor => - def listenConstructor(psh: PatternShape): Unit = psh match + def listenConstructor(psh: PatternShape)(using NewResolverState): Unit = psh match case CtorPatternShape(cls, fs, _, resSym) => - def check(sh: TermShape): Unit = + def check(sh: TermShape)(using NewResolverState): Unit = if sh.isInstanceOfClass(cls) then fs.foreach: (bms, pat) => sh.getMember(bms.nme) match @@ -754,20 +732,19 @@ class NewResolver: shape match case sh: TermShape => check(sh) case sh: SymShape => fromSymbol(sh, check, ctor.target, _ => ()) - ctor.shapeListeners += listenConstructor - ctor.shapes.foreach(listenConstructor) + ctor.subscribeToShapes(listenConstructor) case Pattern.Tuple(_, _) => () // Tuple binding shapes are not inferred yet. case _ => TODO(pattern) - def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern): Unit = if newResolution then + def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern)(using NewResolverState): Unit = if newResolution then listenTerm(scrutinee)(sh => matchShapePat(sh, pattern)(_ => ())) - def appShape(lhs: TermShape, args: Term, res: App): Unit = + def appShape(lhs: TermShape, args: Term, res: App)(using NewResolverState): Unit = // An unknown element used as a callee stays a dynamic call. Propagate its // unknown result rather than inferring callability from a different candidate. lhs match case Marked(_: (DynShape | UnknownValueShape), _) => - if res.shapes.add(lhs) then res.notifyShapeListeners(lhs) + if res.currentShapes.add(lhs) then res.notifyShapeListeners(lhs) return case _ => () lhs match @@ -778,9 +755,9 @@ class NewResolver: value match case value: TermShape => inferTypeArguments(tpe, value, context :: Nil) case NoShape => () - def publish(shape: TermShape): Unit = shape.exit(context) match + def publish(shape: TermShape)(using NewResolverState): Unit = shape.exit(context) match case value: TermShape => - if res.shapes.add(value) then res.notifyShapeListeners(value) + if res.currentShapes.add(value) then res.notifyShapeListeners(value) case NoShape => () callable.paramLists.tail match case Nil => callable.result match @@ -799,11 +776,11 @@ class NewResolver: case (ps, mss) :: pss => zipArgs(mss, ps.params, ps.restParam, args, res, lhs) log(s"appShape isSaturated? ${sh.isSaturated}; head? ${sh.applicationHead}") - def register = if res.shapes.add(sh) then + def register(using NewResolverState) = if res.currentShapes.add(sh) then res.notifyShapeListeners(sh) log(s"lhs ${lhs.isSaturated} ${lhs.unappliedParams.map(_.mapFirst(_.showDbg).mapSecond(_.map(_.showDbg)))}") - if lhs.isSaturated && !res.isErroneous then - res.isErroneous = true + if lhs.isSaturated && !rstate.hasError(res, res.isErroneous) then + rstate.markError(res)(res.isErroneous = true) // Marks transport a value across scopes; they do not apply arguments. // Inspect the value beneath them, retaining actual calls/instantiations // so an already constructed instance never gets the suggestion to use new. @@ -816,12 +793,12 @@ class NewResolver: msg"${lhs.describe.capitalize} cannot be called like a function." resolError(res, message -> lhs.toLoc :: Nil) if sh.isSaturated then - def go(body: Term, mss: Ls[Marks]) = + def go(body: Term, mss: Ls[Marks])(using NewResolverState) = listenTerm(body): sh => sh.exit(mss) match case NoShape => case sh: TermShape => - if res.shapes.add(sh) then + if res.currentShapes.add(sh) then res.notifyShapeListeners(sh) sh.applicationHead match case (ds: DefnShape, mss) => @@ -846,27 +823,27 @@ class NewResolver: listenSignatureResult(declaredType(typeResolution(sign), Map.empty), count): result => result.exit(mss) match case value: TermShape => - if res.shapes.add(value) then res.notifyShapeListeners(value) + if res.currentShapes.add(value) then res.notifyShapeListeners(value) case NoShape => () case N => td.body.foreach(go(_, mss)) case _ => - softAssert(res.isErroneous) + softAssert(rstate.hasError(res, res.isErroneous)) case (sh: IntroShape, mss) => sh.trm match case Lam(params, body) => // Exit the same context that zipArgs enters, filtering results from other uses. go(body, mss) case _ => - softAssert(res.isErroneous) + softAssert(rstate.hasError(res, res.isErroneous)) case _ => - softAssert(res.isErroneous) + softAssert(rstate.hasError(res, res.isErroneous)) else register private def publishMember(host: NewResolvable & ShapeHost, member: BlockMemberSymbol | RecordMember, - flow: FlowSymbol, marks: Ls[Marks]): Unit = - def publish(shape: Shape): Unit = - if host.shapes.add(shape) then host.notifyShapeListeners(shape) - def definition(sym: BlockMemberSymbol): Unit = + flow: FlowSymbol, marks: Ls[Marks])(using NewResolverState): Unit = + def publish(shape: Shape)(using NewResolverState): Unit = + if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) + def definition(sym: BlockMemberSymbol)(using NewResolverState): Unit = publish(symShapes.getOrElseUpdate((sym, flow, marks), SymShape(sym, flow, marks))) member match case member: BlockMemberSymbol => definition(member) @@ -875,23 +852,23 @@ class NewResolver: // Assignments can replace the stored value, so its initializer no longer // determines what a read returns. Publish the known property symbol for // selection/assignment, but an unknown shape for the value being read. - if !host.resolvedTargets.contains(field.tsym) then host.resolvedTargets ::= field.tsym + rstate.recordResolution(host, host.resolvedTargets.contains(field.tsym))(host.resolvedTargets ::= field.tsym) publish(UnknownValueShape(field.rhs)) private def publishDeclared(host: NewResolvable & ShapeHost, member: BlockMemberSymbol, flow: FlowSymbol, - bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks]): Unit = + bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks])(using NewResolverState): Unit = val shape = declaredSymShapes.getOrElseUpdate((member, flow, marks, bindings), DeclaredSymShape(member, flow, marks, bindings)) - if host.shapes.add(shape) then host.notifyShapeListeners(shape) + if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) - private def publishDynamic(host: NewResolvable & ShapeHost, marks: Ls[Marks]): Unit = + private def publishDynamic(host: NewResolvable & ShapeHost, marks: Ls[Marks])(using NewResolverState): Unit = DynShape().exit(marks) match case shape: TermShape => - if host.shapes.add(shape) then host.notifyShapeListeners(shape) + if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) case NoShape => () - private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, loc: Opt[Loc]): Unit = - host.isErroneous = true + private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, loc: Opt[Loc])(using NewResolverState): Unit = + rstate.markError(host)(host.isErroneous = true) val message = reason match case MemberLookup.Uncertainty.ValueShape => msg"Cannot resolve member '$name' of a value with unknown shape." @@ -899,31 +876,33 @@ class NewResolver: msg"Cannot resolve member '$name' across a computed key or unknown record spread." resolError(host, message -> loc :: Nil) - def unresolvedRef(ref: UnresolvedRef): Unit = + def unresolvedRef(ref: UnresolvedRef)(using NewResolverState): Unit = ref.prefixes.foreach: prefix => listenTerm(prefix): shape => shape.getMember(ref.id.name) match - case MemberLookup.Found(member, marks) => + case MemberLookup.Found(member, marks) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member.memberSymbol) => val candidate = prefix -> member.memberSymbol - if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate + rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) publishMember(ref, member, ref.resSym, marks) - case MemberLookup.Declared(member, bindings, marks) => + case MemberLookup.Declared(member, bindings, marks) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member) => val candidate = prefix -> member - if !ref.resolvedMembers.contains(candidate) then ref.resolvedMembers ::= candidate + rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) publishDeclared(ref, member, ref.resSym, bindings, marks) - case MemberLookup.Indexed(_, _) => + case MemberLookup.Indexed(_, _) if rstate.canResolve(ref) => resolError(ref, msg"Tuple elements must be selected by index." -> ref.toLoc :: Nil) - case MemberLookup.Dynamic(marks) => - if !ref.dynamicPrefixes.contains(prefix) then ref.dynamicPrefixes ::= prefix + case MemberLookup.Dynamic(marks) if rstate.canResolve(ref) || ref.dynamicPrefixes.contains(prefix) => + rstate.recordResolution(ref, ref.dynamicPrefixes.contains(prefix))(ref.dynamicPrefixes ::= prefix) publishDynamic(ref, marks) case MemberLookup.Missing => // A known miss in one wildcard source is not an error: another may // provide the name. Lowering diagnoses references with no candidates. () - case MemberLookup.Unknown(reason, loc) => unknownMember(ref, ref.id.name, reason, loc) + case MemberLookup.Unknown(reason, loc) if rstate.canResolve(ref) => unknownMember(ref, ref.id.name, reason, loc) + + case _ => () // Completed references only route their selected members. /** Inspect an overload set only once its definitions have all been published. */ - private def completedClass(shape: SymShape)(selected: ClassDef => Unit, absent: () => Unit): Unit = + private def completedClass(shape: SymShape)(selected: ShapeListener[ClassDef], absent: () => NewResolverState ?=> Unit)(using NewResolverState): Unit = shape.sym.onComplete: () => shape.sym.asCls match case S(cls) => selected(cls.defn.get) @@ -932,14 +911,14 @@ class NewResolver: /** Class interpretations wait for completed overload sets, independently of term * companions. Aliases can supply constructor shapes; applied instances cannot. * Capture marks are retained for subsequent instance-member lookup. */ - private def listenClass(trm: Term)(selected: (ClassDef, Ls[Marks]) => Unit, reject: Shape => Unit): Unit = - def select(cls: ClassDef, marks: Ls[Marks]): Unit = + private def listenClass(trm: Term)(selected: (ClassDef, Ls[Marks]) => NewResolverState ?=> Unit, reject: ShapeListener[Shape])(using NewResolverState): Unit = + def select(cls: ClassDef, marks: Ls[Marks])(using NewResolverState): Unit = trm.classHead match case ref: NewResolvable => - if !ref.resolvedTargets.contains(cls.sym) then ref.resolvedTargets ::= cls.sym + rstate.recordResolution(ref, ref.resolvedTargets.contains(cls.sym))(ref.resolvedTargets ::= cls.sym) case _ => () selected(cls, marks) - def value(sh: TermShape): Unit = sh match + def value(sh: TermShape)(using NewResolverState): Unit = sh match case Marked(ds: DefnShape, marks) => ds.defn match case cls: ClassDef => select(cls, marks :: Nil) case td: TermDefinition => td.tsym match @@ -958,32 +937,35 @@ class NewResolver: () => fromSymbol(sh, value, trm, _ => ())) case sh: TermShape => value(sh) - def newSel(sel: NewSel): Unit = + def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") - def member(info: MemberLookup, description: Message, loc: Opt[Loc]): Unit = info match - case MemberLookup.Found(bms, marks) => + def member(info: MemberLookup, description: Message, loc: Opt[Loc])(using NewResolverState): Unit = info match + case MemberLookup.Found(bms, marks) if rstate.canResolve(sel) || sel.resolvedMembers.contains(bms.memberSymbol) => log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") - if !sel.resolvedMembers.contains(bms.memberSymbol) then sel.resolvedMembers ::= bms.memberSymbol + rstate.recordResolution(sel, sel.resolvedMembers.contains(bms.memberSymbol))(sel.resolvedMembers ::= bms.memberSymbol) publishMember(sel, bms, sel.resSym, marks) - case MemberLookup.Declared(member, bindings, marks) => - if !sel.resolvedMembers.contains(member) then sel.resolvedMembers ::= member + case MemberLookup.Declared(member, bindings, marks) if rstate.canResolve(sel) || sel.resolvedMembers.contains(member) => + rstate.recordResolution(sel, sel.resolvedMembers.contains(member))(sel.resolvedMembers ::= member) publishDeclared(sel, member, sel.resSym, bindings, marks) - case MemberLookup.Indexed(field, marks) => + case MemberLookup.Indexed(field, marks) if rstate.canResolve(sel) || sel.tupleIndex == sel.id.name.toIntOption => val index = sel.id.name.toIntOption softAssert(index.exists(_ >= 0), "Tuple lookup must identify a nonnegative index") - sel.tupleIndex = index + rstate.recordResolution(sel, sel.tupleIndex == index)(sel.tupleIndex = index) listenTupleField(field): shape => shape.exit(marks) match case value: TermShape => - if sel.shapes.add(value) then sel.notifyShapeListeners(value) + if sel.currentShapes.add(value) then sel.notifyShapeListeners(value) case NoShape => () - case MemberLookup.Dynamic(marks) => - sel.hasDynamicTarget = true + case MemberLookup.Dynamic(marks) if rstate.canResolve(sel) || sel.hasDynamicTarget => + rstate.recordResolution(sel, sel.hasDynamicTarget)(sel.hasDynamicTarget = true) publishDynamic(sel, marks) - case MemberLookup.Missing => - sel.isErroneous = true + case MemberLookup.Missing if rstate.canResolve(sel) => + rstate.markError(sel)(sel.isErroneous = true) resolError(sel, msg"$description does not contain member '${sel.id.name}'" -> loc :: Nil) - case MemberLookup.Unknown(reason, loc) => unknownMember(sel, sel.id.name, reason, loc) + case MemberLookup.Unknown(reason, loc) if rstate.canResolve(sel) => unknownMember(sel, sel.id.name, reason, loc) + // Later activations still transport field values through the compiled + // selection, but cannot choose a different member for that old syntax. + case _ => () sel.cls match case N => listenTerm(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") @@ -991,7 +973,7 @@ class NewResolver: case S(cls) => listenClass(cls)((cd, marks) => val candidate = cd.sym -> marks - if !sel.resolvedClasses.contains(candidate) then sel.resolvedClasses ::= candidate + rstate.recordResolution(sel, sel.resolvedClasses.contains(candidate))(sel.resolvedClasses ::= candidate) listenExt(cd.ext, ext => val info = DefnShape(cd, ext).getInstanceMember(sel.id.name).withMarks(marks) info match @@ -999,10 +981,10 @@ class NewResolver: // Resolve the projection target even in an unused function. Wait // for the receiver before deciding whether its result may expose // implementation flow or only the declared member signature. - if !sel.resolvedMembers.contains(bms) then sel.resolvedMembers ::= bms + rstate.recordResolution(sel, sel.resolvedMembers.contains(bms))(sel.resolvedMembers ::= bms) bms.onComplete: () => bms.asModOrObj.orElse(bms.asTrm).orElse(bms.asCls).foreach: sym => - if !sel.resolvedTargets.contains(sym) then sel.resolvedTargets ::= sym + rstate.recordResolution(sel, sel.resolvedTargets.contains(sym))(sel.resolvedTargets ::= sym) listenTerm(sel.prefix): receiver => val selected = receiver match case Marked(nominal: NominalTypeShape, context) => @@ -1017,7 +999,7 @@ class NewResolver: member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc) case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc)) , sh => - sel.isErroneous = true + rstate.markError(sel)(sel.isErroneous = true) resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc :: Nil) ) @@ -1025,7 +1007,7 @@ class NewResolver: * parameter lists, including auxiliary constructor(...) lists. A partial `new` * therefore retains the same callable head and contexts as a partial C(...). */ - private def constructorShape(cls: ClassDef, ext: Opt[TermShape]): DefnShape = + private def constructorShape(cls: ClassDef, ext: Opt[TermShape])(using NewResolverState): DefnShape = // The shared cache distinguishes the constructor's TermDefinition from its // ClassDef, even though both definitions cross the same resolution boundary. val symbol: ClassCtorSymbol | ClassSymbol = cls.ctorSym.getOrElse(cls.sym) @@ -1041,17 +1023,17 @@ class NewResolver: else softAssert(shape.ext == ext) shape - def resolveNew(nw: Term.New): Unit = nw.cls.classHead match - case Term.Error() => nw.isErroneous = true + def resolveNew(nw: Term.New)(using NewResolverState): Unit = nw.cls.classHead match + case Term.Error() => rstate.markError(nw)(nw.isErroneous = true) // Static construction records a resolved class on its reference for lowering. // Other expression forms require dynamic construction, even if they publish // no shapes (for example an unsupported reference in an extends clause). case _: NewResolvable => resolveNewClass(nw) case _ => - nw.isErroneous = true + rstate.markError(nw)(nw.isErroneous = true) resolError(nw, msg"Invalid class expression: ${nw.cls.describe}" -> nw.cls.toLoc :: Nil) - private def resolveNewClass(nw: Term.New): Unit = + private def resolveNewClass(nw: Term.New)(using NewResolverState): Unit = // listenClass preserves captures and supplies the same class-body exit as a // constructor value. In particular, a captured constructor already carries // that exit; adding a second one here would duplicate its instance boundary. @@ -1059,7 +1041,7 @@ class NewResolver: listenExt(cd.ext, extsh => val dsh = constructorShape(cd, extsh) val sh = newShapes.getOrElseUpdate((cd.sym, marks, nw.resSym), { - def typeArgs(term: Term): Opt[Ls[Term]] = term match + def typeArgs(term: Term)(using NewResolverState): Opt[Ls[Term]] = term match case TyApp(_, args) => S(args) case Capture(base, _) => typeArgs(base) case _ => N @@ -1068,15 +1050,15 @@ class NewResolver: case ((ps, _), args) => zipArgs(marks, ps.params, ps.restParam, args, nw, dsh) NewShape(dsh, cd.sym, marks, nw.args, nw) }) - if nw.shapes.add(sh) then nw.notifyShapeListeners(sh) + if nw.currentShapes.add(sh) then nw.notifyShapeListeners(sh) ) , shape => - if !nw.isErroneous then - nw.isErroneous = true + if !rstate.hasError(nw, nw.isErroneous) then + rstate.markError(nw)(nw.isErroneous = true) resolError(nw, msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.toLoc :: Nil) ) - def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term): DefineVar = + def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term)(using NewResolverState): DefineVar = if newResolution then sym match case sym: TermSymbol => // symShape(sym, rhs) @@ -1085,12 +1067,11 @@ class NewResolver: println(s"TODO: defineVar for TermSymbol ${sym.showDbg}") case sym: LocalSymbol => listen(rhs): sh => - assert(isOwnedSym(sym), s"defineVar: sym = ${sym.showDbg}, rhs = ${rhs.showDbg}") - if sym.shapes.add(sh) then + if sym.currentShapes.add(sh) then sym.notifyShapeListeners(sh) DefineVar(sym, rhs) - def listenDefn(sym: TermSymbol, listener: TermShape => Unit): Unit = + def listenDefn(sym: TermSymbol, listener: Listener)(using NewResolverState): Unit = sym.defn match case S(td: TermDefinition) if td.params.isEmpty => td.body match @@ -1103,13 +1084,13 @@ class NewResolver: case N => sym.defnListeners += (d => listener(defnShapes.getOrElseUpdate(sym, DefnShape(d, N)))) - def pipeTerm(from: Term, to: ShapeHost): Unit = - log(s"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.shapes}") + def pipeTerm(from: Term, to: ShapeHost)(using NewResolverState): Unit = + log(s"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.currentShapes}") listenTerm(from): sh => - if to.shapes.add(sh) then + if to.currentShapes.add(sh) then to.notifyShapeListeners(sh) - def listenExt(ext: Opt[Term], listener: Opt[TermShape] => Unit): Unit = + def listenExt(ext: Opt[Term], listener: ShapeListener[Opt[TermShape]])(using NewResolverState): Unit = ext match case S(trm) => listenTerm(trm): sh => @@ -1117,8 +1098,8 @@ class NewResolver: case N => listener(N) - private def fromSymbol(shape: SymShape, listener: TermShape => Unit, source: Term, - selected: DefinitionSymbol[?] => Unit): Unit = shape match + private def fromSymbol(shape: SymShape, listener: Listener, source: Term, + selected: ShapeListener[DefinitionSymbol[?]])(using NewResolverState): Unit = shape match case declared: DeclaredSymShape => listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, selected): value => value.exit(shape.markss) match @@ -1126,8 +1107,8 @@ class NewResolver: case NoShape => () case _ => fromBMS(shape.sym, shape.resSym, shape.markss, listener, source, selected) - def fromBMS(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: TermShape => Unit, - trm: Term, selected: DefinitionSymbol[?] => Unit) = + def fromBMS(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: Listener, + trm: Term, selected: ShapeListener[DefinitionSymbol[?]])(using NewResolverState) = log(s"listenBMS: bms = ${bms.describe}") bms.onComplete: () => log(s"listenedBMS: bms = ${bms.describe}") @@ -1137,7 +1118,7 @@ class NewResolver: // an assignment needs its target even if the value has no inferred shape. // Pattern resolution supplies its own interpretation of the selected head. selected(sym) - val wrappedListener: TermShape => Unit = sh => + val wrappedListener: Listener = sh => log(s"fromBMS: bms = ${bms.showDbg}, sh = ${sh.shwDbg}, flow = ${resSym.showDbg}, markss = ${markss.map(_.showDbg)}") val sh0 = sh // Modules and objects introduce no enter/exit boundary of their own. @@ -1162,7 +1143,16 @@ class NewResolver: td.tsym.decl match case S(_: Param) => wrappedListener(MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N)) case _ => wrappedListener(shape) - case N => td.body.foreach(listenTerm(_)(wrappedListener)) + case N => td.body.foreach: body => + listenTerm(body): shape => + // Legacy synthesized fields use a plain reference to their + // constructor parameter. Supply the field capture explicitly; + // new-resolution fields already carry it in their body syntax. + val captured = (td.tsym.decl, body) match + case (S(_: Param), Ref(_: VarSymbol)) => + MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N) + case _ => shape + wrappedListener(captured) case S(d: TermDefinition) => d.tsym match case ccs: ClassCtorSymbol => @@ -1180,12 +1170,12 @@ class NewResolver: // sym.defnListeners += (d => listener(defnShapes.getOrElseUpdate(sym, DefnShape(d)))) softAssert(false, s"Symbol definition of ${sym} is not set upon completion of ${bms}") case _ => - def reportError = resolError(trm, + def reportError(using NewResolverState) = resolError(trm, msg"Expected a term; got ${bms.describe} '${bms.nme}'" -> N :: Nil) trm.withoutCaptures match case ref: NewResolvable => - if !ref.isErroneous then - ref.isErroneous = true + if !rstate.hasError(ref, ref.isErroneous) then + rstate.markError(ref)(ref.isErroneous = true) reportError case _ => reportError @@ -1193,7 +1183,7 @@ class NewResolver: * of its value shape. Direct definition references already identify the target; * overload sets need listeners, including when their definitions arrive later. */ - def requireTerm(trm: Term): Unit = trm.withoutCaptures match + def requireTerm(trm: Term)(using NewResolverState): Unit = trm.withoutCaptures match case direct @ MemberRef(sym: TermSymbol) => // Selecting a known field does not require resolving the field's value. softAssert(direct.resolvedTargets.forall(_ is sym)) @@ -1206,25 +1196,25 @@ class NewResolver: * definitions. Keep validating selections/opens: a later distinct candidate * must be an error, since it cannot change an already interpreted annotation. */ - def annotationSymbol(trm: Term): Opt[Symbol] = trm match + def annotationSymbol(trm: Term)(using NewResolverState): Opt[Symbol] = trm match case Capture(base, _) => annotationSymbol(base) case TyApp(base, _) => annotationSymbol(base) case App(base, _) => annotationSymbol(base) case ref: NewRefImpl => S(ref.sym) case _: NewSel | _: UnresolvedRef => - val symbols = mutable.LinkedHashSet.empty[BlockMemberSymbol] - var collecting = true - var failed = false - def fail(): Unit = if !failed then - failed = true + val key = new Object + def selection(using rs: NewResolverState): NewResolverState.AnnotationSelection = + rs.annotations.getOrElseUpdate(key, new NewResolverState.AnnotationSelection) + def fail()(using NewResolverState): Unit = if !selection.failed then + selection.failed = true resolError(trm, msg"An annotation's main symbol must be uniquely known when the annotation is elaborated." -> N :: Nil) listen(trm): case sh: SymShape => - if symbols.add(sh.sym) && !collecting then fail() + if selection.symbols.add(sh.sym) && !selection.collecting then fail() case _ => fail() - collecting = false - symbols.toList match - case symbol :: Nil if !failed => S(symbol) + selection.collecting = false + selection.symbols.toList match + case symbol :: Nil if !selection.failed => S(symbol) case _ => fail(); N // TODO: Ref(sym: BuiltinSymbol) is a legacy representation that still needs // updating to the new reference forms, even when using new resolution. @@ -1233,7 +1223,7 @@ class NewResolver: resolError(trm, msg"An annotation must have a known main symbol." -> N :: Nil) N - def listenTerm(trm: Term)(listener: TermShape => Unit): Unit = + def listenTerm(trm: Term)(listener: Listener)(using NewResolverState): Unit = log(s"listenTerm: trm = ${trm.showDbg}") listen(trm): case sh: TermShape => @@ -1242,7 +1232,7 @@ class NewResolver: fromSymbol(ss, listener, trm, sym => trm.withoutCaptures match case ref: NewResolvable => - if !ref.resolvedTargets.contains(sym) then ref.resolvedTargets ::= sym + rstate.recordResolution(ref, ref.resolvedTargets.contains(sym))(ref.resolvedTargets ::= sym) case _ => () ) @@ -1252,20 +1242,20 @@ class NewResolver: * subscriptions while the node is waiting for a forward definition. Deliver * cached shapes to each listener, which is already registered for future shapes. */ - private def listenAggregate(aggregate: Tup | Rcd, listener: Shape => Unit) - (start: (TermShape => Unit) => Unit): Unit = + private def listenAggregate(aggregate: Tup | Rcd, listener: ShapeListener[Shape]) + (start: (Listener) => Unit)(using NewResolverState): Unit = val first = aggregateProducers.add(new Identity(aggregate)) // An imported definition can already have shapes computed by its own elaborator. // Send those shapes to this listener even if first is true; recomputing the same // shapes below will not notify it, because shapes.add rejects duplicates. - aggregate.shapes.foreach(listener) + aggregate.replayShapes(listener) if first then start: shape => - if aggregate.shapes.add(shape) then aggregate.notifyShapeListeners(shape) + if aggregate.currentShapes.add(shape) then aggregate.notifyShapeListeners(shape) - def listen(trm: Term, discardMarks: Bool = false)(listener: Shape => Unit): Unit = + def listen(trm: Term, discardMarks: Bool = false)(listener: ShapeListener[Shape])(using NewResolverState): Unit = log(s"listen: trm = ${trm.showDbg}") - trm.shapeListeners += listener + trm.addShapeListener(listener) trm match case _: SynthSel => lastWords("Synthetic selections must not enter new resolution") @@ -1273,7 +1263,7 @@ class NewResolver: case _: DynSel | _: DynNew => listener(DynShape()) case TyApp(underlying, args) => listen(underlying, discardMarks): shape => - def instantiate(value: TermShape): Unit = value.applicationHead match + def instantiate(value: TermShape)(using NewResolverState): Unit = value.applicationHead match case (callee: (DefnShape | CallableTypeShape), marks) => applyTypeArguments(callee, marks, args, trm) case _ => () shape match @@ -1291,7 +1281,7 @@ class NewResolver: case shape => listener(shape) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => listenAggregate(tuple, listener): publish => - def expand(elems: Ls[Elem], reversed: Ls[TupleShape.Element]): Unit = elems match + def expand(elems: Ls[Elem], reversed: Ls[TupleShape.Element])(using NewResolverState): Unit = elems match case Nil => // A sole spread preserves its operand's shape and context exactly. // Besides avoiding wrappers, this lets recursive rest forwarding @@ -1303,9 +1293,9 @@ class NewResolver: publish(shape) case (field: Fld) :: rest => expand(rest, TupleShape.Field(field, Nil) :: reversed) case Spd(_, term) :: rest => - val seen = mutable.Set.empty[TermShape] + val spreadKey = new Object listenTerm(term): sh => - if seen.add(sh) then sh match + if rstate.spreadInputs.getOrElseUpdate(spreadKey, mutable.Set.empty).add(sh) then sh match case Marked(shape: TupleShape, marks) => // Widen an incoming candidate that already contains its own // producer in this context. For `fun growing(n) = ‹...› [n, ...growing(n - 1)] ‹...›`, @@ -1328,15 +1318,15 @@ class NewResolver: expand(rest, TupleShape.Spread(TupleShape.unknown(term)(this), marks) :: reversed) expand(tuple.fields, Nil) case record: Rcd => listenAggregate(record, listener): publish => - def expand(stats: Ls[Statement], reversed: Ls[RecordShape.Element]): Unit = stats match + def expand(stats: Ls[Statement], reversed: Ls[RecordShape.Element])(using NewResolverState): Unit = stats match case Nil => val shape = RecordShape(record, reversed.reverse) publish(shape) case (field: RcdField) :: rest => expand(rest, RecordShape.Field(field) :: reversed) case RcdSpread(term) :: rest => - val seen = mutable.Set.empty[TermShape] + val spreadKey = new Object listenTerm(term): shape => - if seen.add(shape) then shape match + if rstate.spreadInputs.getOrElseUpdate(spreadKey, mutable.Set.empty).add(shape) then shape match case Marked(shape: RecordShape, marks) => // Bound recursive record producers just as for tuple spreads. // Keep surrounding explicit fields even when the spread widens. @@ -1349,7 +1339,7 @@ class NewResolver: case _ :: rest => expand(rest, reversed) expand(record.stats, Nil) case intro: IntroTerm => - val sh = introShapes.getOrElseUpdate(intro, { + val sh = introShapes.getOrElseUpdate(new Identity(intro), { log(s"introShape: intro = $intro") IntroShape(intro) }) @@ -1357,19 +1347,17 @@ class NewResolver: case Ref(sym) if sym is sym.getState.globalThisSymbol => listener(DynShape()) case SelfRef(sym) if sym is sym.getState.globalThisSymbol => listener(DynShape()) case ref @ Ref(loc: LocalSymbol) => - loc.shapes.foreach(listener) - loc.shapeListeners += listener + loc.subscribeToShapes(listener) case ref @ SimpleRef(sym) => sym match case loc: LocalSymbol => - loc.shapes.foreach(listener) - loc.shapeListeners += listener + loc.subscribeToShapes(listener) case _: BuiltinSymbol => lastWords("Builtin symbols must not enter new resolution as SimpleRef") case SelfRef(sym) => // A receiver can be referenced before its body is complete or after its // definition has already been published. The definition listener handles both. - def completed(defn: ClassLikeDef): Unit = + def completed(defn: ClassLikeDef)(using NewResolverState): Unit = listenExt(defn.ext, ext => // Each inner symbol has one self shape; repeated notifications must agree. val shape = selfShapes.getOrElseUpdate(sym, BaseShape(defn, ext)) @@ -1394,14 +1382,14 @@ class NewResolver: else listenTerm(base): sh => listener(MarkedShape.enter(sh, ResolutionBoundary(thru), N)) case ref @ Ref(sym: InnerSymbol) => // TODO: remove remaining occurrences of such refs - sym.shapeListeners += listener + sym.addShapeListener(listener) case ref @ Ref(bsym: BlockMemberSymbol) => ??? case res: ResolvableImpl => - res.shapes.foreach(listener) + res.replayShapes(listener) // ??? case sh: ShapeHost => - sh.shapes.foreach(listener) + sh.replayShapes(listener) case Blk(sts, rs) => listen(rs)(listener) // case u: UnitVal => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala new file mode 100644 index 0000000000..3c6ae2887f --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -0,0 +1,228 @@ +package hkmc2 +package semantics + +import scala.collection.mutable +import hkmc2.utils.*, shorthands.* +import Term.* + +inline def rstate(using state: NewResolverState): NewResolverState = state + +object NewResolverState: + type Listener = TermShape => NewResolverState ?=> Unit + + final class AnnotationSelection: + val symbols = mutable.LinkedHashSet.empty[BlockMemberSymbol] + var collecting = true + var failed = false + def copy(): AnnotationSelection = + val result = new AnnotationSelection + result.symbols ++= symbols + result.collecting = collecting + result.failed = failed + result + + /** Lazy source lookup preserves existing graph-node identities without copying + * a unit's memo tables. Mutable bookkeeping values supply an explicit copier. + */ + final class Cache[K, V](source: Opt[Cache[K, V]], copy: V => V): + private val entries = mutable.Map.empty[K, V] + private def peek(key: K): Opt[V] = entries.get(key).orElse(source.flatMap(_.peek(key))) + def get(key: K): Opt[V] = entries.get(key).orElse: + source.flatMap(_.peek(key)).map: value => + val local = copy(value) + entries(key) = local + local + def getOrElseUpdate(key: K, value: => V): V = get(key).getOrElse: + val result = value + entries(key) = result + result + def update(key: K, value: V): Unit = entries(key) = value + + final class Seen[A](source: Opt[Seen[A]]): + private val entries = mutable.Set.empty[A] + def apply(value: A): Bool = entries(value) || source.exists(_(value)) + def add(value: A): Bool = if apply(value) then false else entries.add(value) + +/** Inference belongs to the consuming compilation unit. Imported hosts retain their + * completed candidates and contextual listeners; this state supplies private copies + * before a consumer can extend either collection. + */ +final class NewResolverState private ( + val owner: Elaborator.State, private val consumer: Opt[NewResolverState], private val source: Opt[NewResolverState]): + import NewResolverState.{Cache, Seen, AnnotationSelection} + + def this(owner: Elaborator.State) = this(owner, N, N) + private def root: NewResolverState = consumer.getOrElse(this) + private var reporter: Raise | Null = null + def withReporter(raise: Raise): this.type = + root.reporter = raise + this + def report(diagnostic: Diagnostic): Unit = + assert(root.reporter != null, "Resolution requires a diagnostic collector") + root.reporter.nn(diagnostic) + + def isOwnedSym(symbol: Symbol): Bool = symbol.getState is owner + + // A listener carries its defining graph, not its defining mutable resolver. + // Views share the consumer's host copies, but consult the source's bindings and + // memoized nodes individually. Creating a view never traverses a source map. + private val views = mutable.Map.empty[NewResolverState, NewResolverState] + private val inheritedViews = mutable.Map.empty[(NewResolverState, NewResolverState), NewResolverState] + private[hkmc2] def inGraph(graph: NewResolverState): NewResolverState = rebase(graph, root) + private def rebase(graph: NewResolverState, destination: NewResolverState): NewResolverState = + val origin = source.fold(graph)(_.rebase(graph, destination)) + if origin.root eq root then origin + else if root eq destination then + root.views.getOrElseUpdate(origin, new NewResolverState(owner, S(root), S(origin))) + else root.views.get(origin).getOrElse: + // A previously unused path can require a view of an intermediate exporter. + // Memoize that read-only view in the consumer, never in the exporter. + destination.inheritedViews.getOrElseUpdate((root, origin), + new NewResolverState(owner, S(root), S(origin))) + + private val copies = mutable.Map.empty[Publisher.Data[?], Publisher.Data[?]] + private val pending = mutable.Map.empty[Identity[Publisher[?]], Publisher.Data[?]] + private[hkmc2] def local[A](ref: Publisher.Data[A]): Publisher.Data[A] = + // Explicit references (notably type parameters) need the same intermediate + // exporter lookup as references through their syntax or symbol host. + val origin = source.fold(ref)(_.peekReference(ref)) + if (origin.owner eq root) && !origin.completed then origin + else + // Each key and its copy share A; recover that type when looking up the + // heterogeneous graph-node map. + root.copies.getOrElseUpdate(origin, origin.copy(root)).asInstanceOf[Publisher.Data[A]] + + private def peekReference[A](ref: Publisher.Data[A]): Publisher.Data[A] = + val origin = source.fold(ref)(_.peekReference(ref)) + root.copies.get(origin).fold(origin)(_.asInstanceOf[Publisher.Data[A]]) + + private def peek[A](publisher: Publisher[A]): Publisher.Data[A] = + peekReference(publisher.originalData) + + private[hkmc2] def data[A](publisher: Publisher[A]): Publisher.Data[A] = + val ref = source.fold(publisher.initialData(root))(_.peek(publisher)) + if ref.owner == null then publisher.initialData(root) + if publisher.isOwnedBy(root) && !root.completedNodes(new Identity(publisher)) then + root.pending.getOrElseUpdate(new Identity(publisher), ref) + local(ref) + + /** Number of lazily copied graph nodes, for isolation/scale regressions. */ + private[hkmc2] def copiedHostCount: Int = root.copies.size + + private val completedNodes = mutable.Set.empty[Identity[Publisher[?]]] + + /** Seal the decisions and original host data read by erasure/lowering. The + * inference graph keeps private, live host data and all its listeners, so + * later calls can still transport arguments and results through these nodes. + * This boundary is per block, even when a worksheet reuses its symbol state. + */ + def completeBlock(block: Statement): Unit = + val visited = mutable.Set.empty[Identity[Statement]] + def pattern(pat: Pattern): Unit = + pat match + case ctor: Pattern.Constructor => data(ctor) + case _ => () + pat.children.foreach: + case child: Pattern => pattern(child) + case term: Term => visit(term) + case _ => () + def split(branches: SimpleSplit): Unit = branches match + case SimpleSplit.Cons(SimpleSplit.Head.Match(_, pat, consequent), tail) => + pattern(pat) + split(consequent) + split(tail) + case SimpleSplit.Cons(_, tail) => split(tail) + case _ => () + def visit(statement: Statement): Unit = if visited.add(new Identity(statement)) then + statement match + // Bind even unused syntax to this unit. Otherwise its first consumer + // could become the owner of an imported node's original listener buffer. + case term: Term => data(term) + case _ => () + statement match + case cls: ClassLikeDef => + cls.ext.foreach(visit) + cls.auxParams.foreach(_.subTerms.foreach(visit)) + cls match + case pat: PatternDef => pattern(pat.pattern) + case _ => () + case IfLike(_, _, branches) => split(branches) + case Rcd(_, stats) => stats.foreach(visit) + case New(_, _, refinement) => refinement.foreach(r => visit(r._2.blk)) + case _ => () + statement.subStatements.foreach(visit) + visit(block) + root.pending.foreach: (key, value) => + if !key.value.isInstanceOf[Symbol] && root.completedNodes.add(key) then value.completed = true + root.pending.clear() + + def canResolve(host: Publisher[?]): Bool = + data(host) + host.isOwnedBy(root) && !root.completedNodes(new Identity(host)) + + private val errors = mutable.Set.empty[Identity[Publisher[?]]] + def hasError(host: Publisher[?], original: Bool): Bool = original || root.errors(new Identity(host)) + def markError(host: Publisher[?])(update: => Unit): Unit = + root.errors += new Identity(host) + if canResolve(host) then update + + /** Inference may refine an imported result, but the imported code has already + * been compiled. Its member/constructor targets must remain unchanged. + */ + def recordResolution(host: Publisher[?], unchanged: Bool)(update: => Unit): Unit = + if !unchanged then + assert(canResolve(host), "Inference changed a completed reference target") + update + + val membersCache: Cache[(Identity[TermShape], Str), MemberLookup] = + new Cache(source.map(_.membersCache), identity) + val annotations: Cache[Object, AnnotationSelection] = + new Cache(source.map(_.annotations), _.copy()) + val spreadInputs: Cache[Object, mutable.Set[TermShape]] = + new Cache(source.map(_.spreadInputs), _.clone()) + val reportedArities: Cache[Object, mutable.Set[Int]] = + new Cache(source.map(_.reportedArities), _.clone()) + val appShapes: Cache[(TermShape, FlowSymbol), AppShape] = + new Cache(source.map(_.appShapes), identity) + val newShapes: Cache[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = + new Cache(source.map(_.newShapes), identity) + val introShapes: Cache[Identity[IntroTerm], IntroShape] = + new Cache(source.map(_.introShapes), identity) + val symShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = + new Cache(source.map(_.symShapes), identity) + val declaredSymShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks], Map[VarSymbol, DeclaredType]), DeclaredSymShape] = + new Cache(source.map(_.declaredSymShapes), identity) + val selfShapes: Cache[InnerSymbol, BaseShape] = + new Cache(source.map(_.selfShapes), identity) + val defnShapes: Cache[DefinitionSymbol[?], DefnShape] = + new Cache(source.map(_.defnShapes), identity) + val typeInterpretations: Cache[Identity[Term], TypeResolution] = + new Cache(source.map(_.typeInterpretations), identity) + val typeValues: Cache[DeclaredType, TypeValues] = + new Cache(source.map(_.typeValues), identity) + val abstractTypes: Cache[TypeResolution, DeclaredType] = + new Cache(source.map(_.abstractTypes), identity) + val signatureParameters: Cache[VarSymbol, DeclaredType] = + new Cache(source.map(_.signatureParameters), identity) + val capturedTypes: Cache[(DeclaredType, TermSymbol), DeclaredType] = + new Cache(source.map(_.capturedTypes), identity) + val tupleArrayParents: Cache[Identity[TupleShape], NominalTypeShape] = + new Cache(source.map(_.tupleArrayParents), identity) + val aggregateProducers: Seen[Identity[Tup | Rcd]] = + new Seen(source.map(_.aggregateProducers)) + // Explicit instantiations are visible to every graph view in this consumer; + // inherited flags are queried without enumerating an exporter's instantiations. + private val explicitTypeArguments = mutable.Set.empty[(VarSymbol, Ls[Marks])] + def markExplicitTypeArgument(symbol: VarSymbol, marks: Ls[Marks]): Unit = + root.explicitTypeArguments += ((symbol, marks)) + def hasExplicitTypeArgument(symbol: VarSymbol, marks: Ls[Marks]): Bool = + root.explicitTypeArguments((symbol, marks)) || source.exists(_.hasExplicitTypeArgument(symbol, marks)) + val typeConstraints: Seen[(DeclaredType, TermShape, Ls[Marks])] = + new Seen(source.map(_.typeConstraints)) + +private[semantics] final class TypeValues extends Host[TermShape]: + def showDbg(using DebugPrinter): Str = "declared type values" + def publish(shape: TermShape)(using NewResolverState): Unit = + if currentShapes.add(shape) then notifyShapeListeners(shape) + def listen(listener: NewResolverState.Listener)(using NewResolverState): Unit = + subscribeToShapes(listener) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 71f9d1aee8..c5ccda152e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -109,9 +109,9 @@ object Marked: end Marked case class MarkedShape(sh: NonMarkedShape, mark: SomeMarks) extends TermShape: - protected def getMemberImpl(name: Str): MemberLookup = + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = sh.getMember(name).withMarks(mark :: Nil) - override def isInstanceOfClass(cls: ClassLikeDef): Bool = sh.isInstanceOfClass(cls) + override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = sh.isInstanceOfClass(cls) def describe: Str = sh.describe def toLoc: Opt[Loc] = sh.toLoc object MarkedShape: @@ -137,14 +137,13 @@ end MarkedShape sealed trait TermShape extends Shape: // Cache all lookup outcomes per shape, without materializing all inherited members. - private val membersCache = mutable.Map.empty[Str, MemberLookup] - final def getMember(name: Str): MemberLookup = - membersCache.getOrElseUpdate(name, getMemberImpl(name)) - protected def getMemberImpl(name: Str): MemberLookup + final def getMember(name: Str)(using state: NewResolverState): MemberLookup = + state.membersCache.getOrElseUpdate((new Identity[TermShape](this), name), getMemberImpl(name)) + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup /** Whether this value is an instance of the nominal class. A saturated class * value (e.g. a class without parameters) is still not an instance. */ - def isInstanceOfClass(cls: ClassLikeDef): Bool = false + def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = false // Cache the head independently: following receivers does not need to construct // or unpack a (head, marks) pair at every intermediate shape. @@ -279,7 +278,7 @@ object MemberLookup: case ValueShape, RecordOverwrite /** Own members take precedence over inherited ones, including unknown spreads. */ - def inClass(defn: ClassLikeDef, ext: Opt[TermShape], name: Str): MemberLookup = + def inClass(defn: ClassLikeDef, ext: Opt[TermShape], name: Str)(using NewResolverState): MemberLookup = defn.body.members.get(name) match case S(member) => Found(member, Nil) case N => ext.fold[MemberLookup](Missing)(_.getMember(name)) @@ -291,11 +290,11 @@ extension (member: BlockMemberSymbol | RecordMember) class ErrShape(val err: ErrorReport) extends NonAppTermShape: def describe: Str = s"error: ${err.mainMsg}" - protected def getMemberImpl(name: Str): MemberLookup = MemberLookup.Missing + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Missing def toLoc: Opt[Loc] = N class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using DebugPrinter) extends NonMarkedShape: - protected def getMemberImpl(name: Str): MemberLookup = + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = // An unsaturated term definition is just a concrete function shape if !isSaturated then MemberLookup.Missing else @@ -303,7 +302,7 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using case (ds: DefnShape, mss) => ds.getInstanceMember(name).withMarks(mss) case _ => MemberLookup.Missing - override def isInstanceOfClass(cls: ClassLikeDef): Bool = + override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = isSaturated && (applicationHead._1 match case ds: DefnShape => ds.classExtends(cls) case _ => false) @@ -315,10 +314,10 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using // def target: Opt[AppTarget] class NewShape(val receiver: DefnShape, val cls: ClassLikeSymbol, val clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New)(using DebugPrinter) extends NonMarkedShape: - protected def getMemberImpl(name: Str): MemberLookup = + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = if isSaturated then receiver.getInstanceMember(name).withMarks(clsMarks) else MemberLookup.Missing - override def isInstanceOfClass(cls: ClassLikeDef): Bool = + override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = isSaturated && receiver.classExtends(cls) def describe: Str = // s"instantiation of ${receiver.describe}" @@ -350,10 +349,10 @@ class ThisShape(val defn: Definition) extends NonAppTermShape: // TODO: make it not a TermShape? class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppTermShape: - override def isInstanceOfClass(cls: ClassLikeDef): Bool = + override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = (defn is cls) || ext.exists(_.isInstanceOfClass(cls)) def describe: Str = s"${defn.describe}" - protected def getMemberImpl(name: Str): MemberLookup = + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.inClass(defn, ext, name) def toLoc: Opt[Loc] = defn.toLoc @@ -364,14 +363,14 @@ final case class NominalTypeShape(defn: ClassLikeDef, bindings: Map[VarSymbol, D parent: Opt[TermShape]) extends NonAppTermShape: def describe: Str = s"value of type '${defn.sym.nme}'" def toLoc: Opt[Loc] = defn.toLoc - override def isInstanceOfClass(cls: ClassLikeDef): Bool = + override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = (defn is cls) || parent.exists(_.isInstanceOfClass(cls)) def ancestor(cls: ClassLikeDef): Opt[NominalTypeShape] = if defn is cls then S(this) else parent.flatMap: case Marked(parent: NominalTypeShape, _) => parent.ancestor(cls) case _ => N - protected def getMemberImpl(name: Str): MemberLookup = + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = defn.body.members.get(name) match case S(member) => MemberLookup.Declared(member, bindings, Nil) case N => parent.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)) @@ -385,30 +384,30 @@ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool) * applications constrain the same parameters as inferred arguments. */ final case class CallableTypeShape(source: Term, paramLists: Ls[DeclaredParams], - result: Opt[DeclaredType], tparams: Ls[VarSymbol]) extends NonAppTermShape: + result: Opt[DeclaredType], tparams: Ls[TypeShape.Parameter]) extends NonAppTermShape: require(paramLists.nonEmpty) def describe: Str = "function with a declared signature" def toLoc: Opt[Loc] = source.toLoc - protected def getMemberImpl(name: Str): MemberLookup = MemberLookup.Missing + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Missing /** An abstract annotation authorizes no operations based on the implementation. */ final case class OpaqueTypeShape(source: Term) extends NonAppTermShape: def describe: Str = "value of abstract type" def toLoc: Opt[Loc] = source.toLoc - protected def getMemberImpl(name: Str): MemberLookup = + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, toLoc) class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTermShape: /** Instance lookup is shared by constructor calls and explicit `new`. * Inherited members retain their marks before the caller adds its captures. */ - def getInstanceMember(name: Str): MemberLookup = defn match + def getInstanceMember(name: Str)(using NewResolverState): MemberLookup = defn match case cd: ClassDef => MemberLookup.inClass(cd, ext, name) case _: TermDefinition => ext.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)) case _ => MemberLookup.Missing /** Nominal ancestry of this definition, independent of whether its value is an instance. */ - def classExtends(cls: ClassLikeDef): Bool = + def classExtends(cls: ClassLikeDef)(using NewResolverState): Bool = clsDef.contains(cls) || ext.exists(_.isInstanceOfClass(cls)) - override def isInstanceOfClass(cls: ClassLikeDef): Bool = defn match + override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = defn match case _: ModuleOrObjectDef => classExtends(cls) case _ => false lazy val clsDef = defn match @@ -433,7 +432,7 @@ class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTer }'${defn.bsym.nme}'" // override def toString: String = s"DefnShape(${defn.describe} ${defn.bsym.nme})" override def toString: String = s"DefnShape(${defn.describe})" - protected def getMemberImpl(name: Str): MemberLookup = + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = defn match case defn: ModuleOrObjectDef => MemberLookup.inClass(defn, ext, name) @@ -454,8 +453,8 @@ class RefinedShape(val base: TermShape, val refinements: Ls[Str -> Term]) extend * them instead of discarding either those fields or the unresolved possibilities. */ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element])(resolver: NewResolver) extends NonAppTermShape: - def arrayParent: NominalTypeShape = resolver.tupleArrayParent(this) - override def isInstanceOfClass(cls: ClassLikeDef): Bool = arrayParent.isInstanceOfClass(cls) + def arrayParent(using NewResolverState): NominalTypeShape = resolver.tupleArrayParent(this) + override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = arrayParent.isInstanceOfClass(cls) lazy val segments: Ls[TupleShape.Segment] = elements.flatMap: case segment: TupleShape.Segment => segment :: Nil case TupleShape.Rest(_, segments) => segments @@ -480,7 +479,7 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element])(reso ((shape.source is source) && inner == marks) || shape.containsSpread(source, marks) def describe: Str = "tuple literal" def toLoc: Opt[Loc] = source.toLoc - protected def getMemberImpl(name: Str): MemberLookup = name.toIntOption match + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = name.toIntOption match case S(index) if index >= 0 => def loop(rest: Ls[TupleShape.Segment], index: Int): MemberLookup = rest match case (field: TupleShape.Fixed) :: tail => @@ -500,7 +499,7 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element])(reso final case class DynShape() extends NonAppTermShape: def describe: Str = "dynamic value" def toLoc: Opt[Loc] = N - protected def getMemberImpl(name: Str): MemberLookup = MemberLookup.Dynamic(Nil) + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Dynamic(Nil) /** The element of an opaque or widened spread can be any value. Keep this * alternative in the flow graph so other, known arguments cannot silently make @@ -509,7 +508,7 @@ final case class DynShape() extends NonAppTermShape: final case class UnknownValueShape(source: Term) extends NonAppTermShape: def describe: Str = "value of unknown shape" def toLoc: Opt[Loc] = source.toLoc - protected def getMemberImpl(name: Str): MemberLookup = + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, toLoc) object TupleShape: @@ -554,7 +553,7 @@ final case class RecordMember(field: RcdField, mutable: Bool) final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element]) extends NonAppTermShape: def describe: Str = "record literal" def toLoc: Opt[Loc] = source.toLoc - protected def getMemberImpl(name: Str): MemberLookup = + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = import MemberLookup.* def unknown = Unknown(Uncertainty.RecordOverwrite, toLoc) def loop(rest: Ls[RecordShape.Element]): MemberLookup = rest match @@ -592,7 +591,7 @@ object RecordShape: type IntroTerm = Term.Lit | Term.UnitVal | Term.Lam //| Term.New class IntroShape(val trm: IntroTerm) extends NonAppTermShape: def describe: Str = trm.describe - protected def getMemberImpl(name: Str): MemberLookup = trm match + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = trm match case _: Term.Lit | _: Term.UnitVal => MemberLookup.Missing // TODO: methods on literals case lam: Term.Lam => MemberLookup.Missing // TODO: methods on lambdas // case newTerm: Term.New => @@ -602,7 +601,7 @@ class IntroShape(val trm: IntroTerm) extends NonAppTermShape: sealed trait LitShape extends NonAppTermShape: self: Term.Lit => - protected def getMemberImpl(name: Str): MemberLookup = MemberLookup.Missing // TODO: methods on literals, e.g. string methods + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Missing // TODO: methods on literals, e.g. string methods type ShapePublisher = Publisher[Shape] diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 117c3f8239..beb72e2d44 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -15,7 +15,9 @@ enum TypeShape: case Tuple(fields: Ls[TypeResolution]) case Function(params: Term, result: TypeResolution) case Applied(base: TypeResolution, args: Ls[TypeResolution]) - case Parameter(symbol: VarSymbol) + // The same third-party symbol can have different inference in two exporters. + // Retain its originating host so importing a result needs no whole-state copy. + case Parameter(symbol: VarSymbol, host: Publisher.Data[Shape]) // Synthesized generic arguments can retain inferred value shapes, for example // the element union of the Array supertype of a tuple. Written annotations // never introduce this case by inspecting their implementation. @@ -32,9 +34,9 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => private var reported = false def hasErrors: Bool = reported def showDbg(using DebugPrinter): Str = s"type of ${source.showDbg}" - def publish(shape: TypeShape): Unit = - if shapes.add(shape) then notifyShapeListeners(shape) - def listen(listener: TypeShape => Unit): Unit = + def publish(shape: TypeShape)(using NewResolverState): Unit = + if currentShapes.add(shape) then notifyShapeListeners(shape) + def listen(listener: ShapeListener[TypeShape])(using NewResolverState): Unit = subscribeToShapes(listener) def fail(messages: Ls[(Message, Opt[Loc])]): Unit = if !reported then reported = true diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala index a9364bbffa..eb3e143045 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala @@ -4,28 +4,82 @@ package utils import scala.collection.mutable.{ArrayBuffer, LinkedHashSet} import hkmc2.utils.*, shorthands.* +import hkmc2.semantics.{NewResolverState, Symbol} +type ShapeListener[-A] = A => NewResolverState ?=> Unit + +object Publisher: + private[hkmc2] final class Data[A]: + private[hkmc2] var owner: NewResolverState | Null = null + private[hkmc2] var completed = false + val listeners: ArrayBuffer[ShapeListener[A]] = ArrayBuffer.empty + val shapes: LinkedHashSet[A] = LinkedHashSet.empty + def copy(state: NewResolverState): Data[A] = + val result = new Data[A] + result.owner = state + // Preserve intermediate exporters' private graph nodes as well as the + // listener's original registration graph when this host is re-exported. + val origin = owner + listeners.foreach: listener => + result.listeners += (shape => (current: NewResolverState) ?=> + listener(shape)(using if origin == null then current else current.inGraph(origin))) + result.shapes ++= shapes + result + + def publish(shape: A)(using state: NewResolverState): Unit = + val data = state.local(this) + if data.shapes.add(shape) then data.notify(shape) + def notify(shape: A)(using NewResolverState): Unit = + // Appended listeners receive this candidate through replay. + val count = listeners.size + var i = 0 + while i < count do + listeners(i)(shape) + i += 1 + def addListener(listener: ShapeListener[A])(using origin: NewResolverState): Unit = + origin.local(this).listeners += + (shape => (current: NewResolverState) ?=> listener(shape)(using current.inGraph(origin))) + def replay(listener: ShapeListener[A])(using state: NewResolverState): Unit = + val candidates = state.local(this).shapes + // Apply the contextual listener, rather than discard a contextual thunk. + candidates.iterator.take(candidates.size).foreach[Unit](shape => listener(shape)) + def subscribe(listener: ShapeListener[A])(using NewResolverState): Unit = + addListener(listener) + replay(listener) + trait Publisher[A]: - // Listeners are append-only so callbacks may subscribe during notification. - private[hkmc2] val shapeListeners: ArrayBuffer[A => Unit] = ArrayBuffer.empty - - private[hkmc2] def notifyShapeListeners(shape: A): Unit = - // A new subscriber receives the current shape through replay. Only visit the - // original listeners here, avoiding duplicate delivery and iterator invalidation. - val count = shapeListeners.size - var i = 0 - while i < count do - shapeListeners(i)(shape) - i += 1 + private[hkmc2] val originalData = new Publisher.Data[A] + // Syntax nodes do not all carry an elaborator owner. Bind their original host + // on first use; subsequent consumers only mutate copies in their own state. + private[hkmc2] def initialData(state: NewResolverState): Publisher.Data[A] = + val foreignSymbol = this match + case symbol: Symbol => !state.isOwnedSym(symbol) + case _ => false + if originalData.owner == null && !foreignSymbol then originalData.owner = state + originalData + + private[hkmc2] def isOwnedBy(state: NewResolverState): Bool = + originalData.owner eq state + + private[hkmc2] def shapeListeners: ArrayBuffer[ShapeListener[A]] = originalData.listeners + private[hkmc2] def inferenceHost(using state: NewResolverState): Publisher.Data[A] = state.data(this) + private[hkmc2] def addShapeListener(listener: ShapeListener[A])(using origin: NewResolverState): Unit = + inferenceHost.addListener(listener) + + private[hkmc2] def currentShapes(using state: NewResolverState): LinkedHashSet[A] = + state.data(this).shapes + private[hkmc2] def shapes: LinkedHashSet[A] = originalData.shapes + + private[hkmc2] def notifyShapeListeners(shape: A)(using NewResolverState): Unit = + inferenceHost.notify(shape) trait Host[A] extends Publisher[A]: - private[hkmc2] val shapes: LinkedHashSet[A] = LinkedHashSet.empty - private[hkmc2] def subscribeToShapes(listener: A => Unit): Unit = - shapeListeners += listener - // Shapes are append-only and LinkedHashSet iteration preserves insertion order. - // Shapes added by a replay callback are already delivered by notification. - shapes.iterator.take(shapes.size).foreach(listener) + private[hkmc2] def subscribeToShapes(listener: ShapeListener[A])(using NewResolverState): Unit = + addShapeListener(listener) + replayShapes(listener) + private[hkmc2] def replayShapes(listener: ShapeListener[A])(using NewResolverState): Unit = + inferenceHost.replay(listener) def showDbg(using DebugPrinter): Str diff --git a/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls b/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls index c2cb90ac73..e3d3cca530 100644 --- a/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls +++ b/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls @@ -5,6 +5,7 @@ module GenericLibrary with fun identity[A](x: A): A = x class Methods with fun identity[A](x: A): A = x + fun inferred[A](x: A) = x val known = identity(Known(3)) val mapped = [Known(4)].map((item, ...) => Known(5)) fun inferred[A](x: A) = x diff --git a/hkmc2/shared/src/test/mlscript/basics/Indentation.mls b/hkmc2/shared/src/test/mlscript/basics/Indentation.mls index 6ede3021b4..3b5f645014 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Indentation.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Indentation.mls @@ -155,6 +155,14 @@ id(0 //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). //│ ║ l.151: id)(1) //│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.150: id(0 +//│ ║ ^^^^ +//│ ║ l.151: id)(1) +//│ ║ ^^^^^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.150: id(0 +//│ ╙── ^ //│ ═══[RUNTIME ERROR] TypeError: 0 is not a function diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls index c40d13263d..e89a3bb95a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls @@ -168,8 +168,8 @@ invalid(0) //│ ║ l.165: fun invalid(x) = if x is NotAPattern then 1 else 0 //│ ║ ^^^^^^^^^^^ //│ ╟── Integer literal cannot be used as a constructor pattern. -//│ ║ l.165: fun invalid(x) = if x is NotAPattern then 1 else 0 -//│ ╙── ^ +//│ ║ l.164: val NotAPattern = 1 +//│ ╙── ^ //│ = 0 //│ NotAPattern = 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 5ebbc01b14..97df063852 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -159,11 +159,25 @@ callid1(Predef) //│ | appShape: td.body = Some(Ref(x)) //│ | listenTerm: trm = x⁰ //│ | listen: trm = x⁰ +//│ | .exit MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | .exit MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .exit MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) +//│ | | | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | | .exit MarkedShape (MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ), term:Predef¹/id¹, ⋅id⁰) +//│ | | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | = MarkedShape(IntroShape(1), ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | appShape isSaturated? true; head? (DefnShape(term definition 'callid1'),List(ExitMark(term:callid1,Some(callid1),NoMarks))) //│ | lhs false List(((p⁰),List(↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ))) //│ | appShape: td.body = Some(App(NewSel(SimpleRef(p),Ident(id),None),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))) //│ | listenTerm: trm = p⁰.id(1) //│ | listen: trm = p⁰.id(1) +//│ | .exit MarkedShape(IntroShape(1), ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .exit MarkedShape(IntroShape(1), ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) +//│ | | = MarkedShape(IntroShape(1), ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | | .exit MarkedShape (MarkedShape(IntroShape(1), ↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ), term:callid1², callid1¹) +//│ | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) +//│ | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ ~> { fun member:callid1(p⁰) = p⁰.id(1); member:callid1⁰(member:Predef⁰) } //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ fun callid1⁰::callid1¹(p⁰) ‹flow:callid1⁰› = p⁰.id⁰(1)‹app⁰›, callid1⁰(Predef⁰)‹app¹› diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index d4bdf4870e..d96a66bb43 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -108,28 +108,24 @@ fun test(m: Methods) = m.identity[Item](Item(4)).value test(new Methods) //│ = 4 -// FIXME: The defining unit's inferred Array argument lives in its local host. -// Importing the value must also transport that dependency, not only its shape. -:fixme +// Imported values retain the dependencies of their inferred Array arguments. :expect [5] GenericLibrary.mapped.map((item, ...) => item.known) -//│ ╔══[COMPILATION ERROR] This selection of member 'known' has no resolved target -//│ ║ l.115: GenericLibrary.mapped.map((item, ...) => item.known) -//│ ╙── ^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '[5]', got: 'undefined' +//│ = [5] -// FIXME: The imported parameter-to-result listener still belongs to the defining -// resolver. Rebuilding a declared result works; importing this inferred flow does not. -:fixme +// Imported parameter-to-result listeners run in the consuming resolver state. :expect 6 class Item(val value: Int) GenericLibrary.inferred(Item(6)).value -//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.127: GenericLibrary.inferred(Item(6)).value -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '6', got: 'undefined' +//│ = 6 + +// Inferred generic method results use the same imported listener graph. +:expect [7, 8] +class Item(val value: Int) +let methods = new GenericLibrary.Methods +[methods.inferred(Item(7)).value, methods.inferred[Item](Item(8)).value] +//│ = [7, 8] +//│ methods = Methods // FIXME: A callback reading a constructor field in a module initializer still // crosses incompatible lexical capture paths, before any consumer imports it. diff --git a/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls index 940cd65927..079f263862 100644 --- a/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls +++ b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls @@ -97,13 +97,10 @@ m.id let x = id(Foo.Bar) //│ x = class Bar -// * We don't currently resolve *through* external definitions -:todo +// External identity functions preserve their argument's inferred interface. +:expect fun id x.id -//│ ╔══[COMPILATION ERROR] This selection of member 'id' has no resolved target -//│ ║ l.102: x.id -//│ ╙── ^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = fun id fun localid(x) = x From 156e167b1108ec59cce0d284afea4d9fb61ff2ed Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 15:01:08 +0800 Subject: [PATCH 071/228] Keep annotation validation local to its defining block --- .../scala/hkmc2/semantics/NewResolver.scala | 28 +++++++++++-------- .../hkmc2/semantics/NewResolverState.scala | 15 +--------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 90299ba6ce..7ef03e0ad3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1202,19 +1202,23 @@ class NewResolver: case App(base, _) => annotationSymbol(base) case ref: NewRefImpl => S(ref.sym) case _: NewSel | _: UnresolvedRef => - val key = new Object - def selection(using rs: NewResolverState): NewResolverState.AnnotationSelection = - rs.annotations.getOrElseUpdate(key, new NewResolverState.AnnotationSelection) - def fail()(using NewResolverState): Unit = if !selection.failed then - selection.failed = true + val symbols = mutable.LinkedHashSet.empty[BlockMemberSymbol] + var collecting = true + var failed = false + def fail()(using NewResolverState): Unit = if !failed then + failed = true resolError(trm, msg"An annotation's main symbol must be uniquely known when the annotation is elaborated." -> N :: Nil) - listen(trm): - case sh: SymShape => - if selection.symbols.add(sh.sym) && !selection.collecting then fail() - case _ => fail() - selection.collecting = false - selection.symbols.toList match - case symbol :: Nil if !selection.failed => S(symbol) + // This listener validates an elaboration decision; it transports no values. + // Keep checking late candidates in the defining block, but never mutate its + // local bookkeeping when a completed block's listeners run in a consumer. + listen(trm): shape => + if rstate.canResolve(trm) then shape match + case sh: SymShape => + if symbols.add(sh.sym) && !collecting then fail() + case _ => fail() + collecting = false + symbols.toList match + case symbol :: Nil if !failed => S(symbol) case _ => fail(); N // TODO: Ref(sym: BuiltinSymbol) is a legacy representation that still needs // updating to the new reference forms, even when using new resolution. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 3c6ae2887f..102f517822 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -10,17 +10,6 @@ inline def rstate(using state: NewResolverState): NewResolverState = state object NewResolverState: type Listener = TermShape => NewResolverState ?=> Unit - final class AnnotationSelection: - val symbols = mutable.LinkedHashSet.empty[BlockMemberSymbol] - var collecting = true - var failed = false - def copy(): AnnotationSelection = - val result = new AnnotationSelection - result.symbols ++= symbols - result.collecting = collecting - result.failed = failed - result - /** Lazy source lookup preserves existing graph-node identities without copying * a unit's memo tables. Mutable bookkeeping values supply an explicit copier. */ @@ -49,7 +38,7 @@ object NewResolverState: */ final class NewResolverState private ( val owner: Elaborator.State, private val consumer: Opt[NewResolverState], private val source: Opt[NewResolverState]): - import NewResolverState.{Cache, Seen, AnnotationSelection} + import NewResolverState.{Cache, Seen} def this(owner: Elaborator.State) = this(owner, N, N) private def root: NewResolverState = consumer.getOrElse(this) @@ -176,8 +165,6 @@ final class NewResolverState private ( val membersCache: Cache[(Identity[TermShape], Str), MemberLookup] = new Cache(source.map(_.membersCache), identity) - val annotations: Cache[Object, AnnotationSelection] = - new Cache(source.map(_.annotations), _.copy()) val spreadInputs: Cache[Object, mutable.Set[TermShape]] = new Cache(source.map(_.spreadInputs), _.clone()) val reportedArities: Cache[Object, mutable.Set[Int]] = From ad031bb929ed4c8aaf097b870dfab7a0eb222b92 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 15:04:01 +0800 Subject: [PATCH 072/228] Centralize erroneous syntax flags in PossiblyErroneous --- .../scala/hkmc2/semantics/NewResolver.scala | 42 +++++++++---------- .../hkmc2/semantics/NewResolverState.scala | 8 ++-- .../main/scala/hkmc2/semantics/Pattern.scala | 3 +- .../src/main/scala/hkmc2/semantics/Term.scala | 4 +- .../main/scala/hkmc2/semantics/TermImpl.scala | 8 ++-- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 7ef03e0ad3..9b45e26841 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -615,7 +615,7 @@ class NewResolver: rstate.recordResolution(res, false)(res.resolvedTargets ::= sym) true def reject(sh: TermShape)(using NewResolverState): Unit = - rstate.markError(res)(res.isErroneous = true) + rstate.markError(res) resolError(res, msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> sh.toLoc :: Nil) def classPattern(cls: ClassLikeDef)(using NewResolverState): Unit = if select(cls.sym) then val assoc = res.arguments match @@ -623,13 +623,13 @@ class NewResolver: case S(args) => cls.paramsOpt match case N => if args.nonEmpty || cls.isInstanceOf[ClassDef] then - rstate.markError(res)(res.isErroneous = true) + rstate.markError(res) resolError(res, msg"${cls.describe.capitalize} does not take pattern arguments." -> cls.toLoc :: Nil) Nil case S(ps) => if ps.restParam.nonEmpty then TODO(ps.restParam) if args.sizeCompare(ps.params) =/= 0 then - rstate.markError(res)(res.isErroneous = true) + rstate.markError(res) resolError(res, msg"${cls.describe.capitalize} expected ${ps.params.length} ${ "pattern argument".pluralized(ps.params.length)}, but got ${args.length}" -> cls.toLoc :: Nil) @@ -643,13 +643,13 @@ class NewResolver: // report another error for this already-diagnosed class. if cls.body.members.get(fldSym.nme).contains(fldSym) then (fldSym -> a) :: Nil else - rstate.markError(res)(res.isErroneous = true) + rstate.markError(res) Nil case _ => - rstate.markError(res)(res.isErroneous = true) + rstate.markError(res) resolError(res, msg"Pattern argument requires an accessible constructor field." -> p.toLoc :: Nil) Nil - if !rstate.hasError(res, res.isErroneous) then + if !rstate.hasError(res) then val psh = CtorPatternShape(cls, assoc, res, FlowSymbol.pat()(using rstate.owner)) if res.currentShapes.add(psh) then res.notifyShapeListeners(psh) def valuePattern(sh: TermShape)(using NewResolverState): Unit = sh.applicationHead match @@ -662,7 +662,7 @@ class NewResolver: case _ => reject(sh) case _ => reject(sh) lhs.withoutCaptures match - case Term.Error() => rstate.markError(res)(res.isErroneous = true) + case Term.Error() => rstate.markError(res) case Term.Ref(sym: VarSymbol) if sym.decl.exists(_.isPatternConstructor) => res.resolvedTargets ::= sym case Term.SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isPatternConstructor) => @@ -779,8 +779,8 @@ class NewResolver: def register(using NewResolverState) = if res.currentShapes.add(sh) then res.notifyShapeListeners(sh) log(s"lhs ${lhs.isSaturated} ${lhs.unappliedParams.map(_.mapFirst(_.showDbg).mapSecond(_.map(_.showDbg)))}") - if lhs.isSaturated && !rstate.hasError(res, res.isErroneous) then - rstate.markError(res)(res.isErroneous = true) + if lhs.isSaturated && !rstate.hasError(res) then + rstate.markError(res) // Marks transport a value across scopes; they do not apply arguments. // Inspect the value beneath them, retaining actual calls/instantiations // so an already constructed instance never gets the suggestion to use new. @@ -827,16 +827,16 @@ class NewResolver: case NoShape => () case N => td.body.foreach(go(_, mss)) case _ => - softAssert(rstate.hasError(res, res.isErroneous)) + softAssert(rstate.hasError(res)) case (sh: IntroShape, mss) => sh.trm match case Lam(params, body) => // Exit the same context that zipArgs enters, filtering results from other uses. go(body, mss) case _ => - softAssert(rstate.hasError(res, res.isErroneous)) + softAssert(rstate.hasError(res)) case _ => - softAssert(rstate.hasError(res, res.isErroneous)) + softAssert(rstate.hasError(res)) else register private def publishMember(host: NewResolvable & ShapeHost, member: BlockMemberSymbol | RecordMember, @@ -868,7 +868,7 @@ class NewResolver: case NoShape => () private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, loc: Opt[Loc])(using NewResolverState): Unit = - rstate.markError(host)(host.isErroneous = true) + rstate.markError(host) val message = reason match case MemberLookup.Uncertainty.ValueShape => msg"Cannot resolve member '$name' of a value with unknown shape." @@ -960,7 +960,7 @@ class NewResolver: rstate.recordResolution(sel, sel.hasDynamicTarget)(sel.hasDynamicTarget = true) publishDynamic(sel, marks) case MemberLookup.Missing if rstate.canResolve(sel) => - rstate.markError(sel)(sel.isErroneous = true) + rstate.markError(sel) resolError(sel, msg"$description does not contain member '${sel.id.name}'" -> loc :: Nil) case MemberLookup.Unknown(reason, loc) if rstate.canResolve(sel) => unknownMember(sel, sel.id.name, reason, loc) // Later activations still transport field values through the compiled @@ -999,7 +999,7 @@ class NewResolver: member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc) case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc)) , sh => - rstate.markError(sel)(sel.isErroneous = true) + rstate.markError(sel) resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc :: Nil) ) @@ -1024,13 +1024,13 @@ class NewResolver: shape def resolveNew(nw: Term.New)(using NewResolverState): Unit = nw.cls.classHead match - case Term.Error() => rstate.markError(nw)(nw.isErroneous = true) + case Term.Error() => rstate.markError(nw) // Static construction records a resolved class on its reference for lowering. // Other expression forms require dynamic construction, even if they publish // no shapes (for example an unsupported reference in an extends clause). case _: NewResolvable => resolveNewClass(nw) case _ => - rstate.markError(nw)(nw.isErroneous = true) + rstate.markError(nw) resolError(nw, msg"Invalid class expression: ${nw.cls.describe}" -> nw.cls.toLoc :: Nil) private def resolveNewClass(nw: Term.New)(using NewResolverState): Unit = @@ -1053,8 +1053,8 @@ class NewResolver: if nw.currentShapes.add(sh) then nw.notifyShapeListeners(sh) ) , shape => - if !rstate.hasError(nw, nw.isErroneous) then - rstate.markError(nw)(nw.isErroneous = true) + if !rstate.hasError(nw) then + rstate.markError(nw) resolError(nw, msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.toLoc :: Nil) ) @@ -1174,8 +1174,8 @@ class NewResolver: msg"Expected a term; got ${bms.describe} '${bms.nme}'" -> N :: Nil) trm.withoutCaptures match case ref: NewResolvable => - if !rstate.hasError(ref, ref.isErroneous) then - rstate.markError(ref)(ref.isErroneous = true) + if !rstate.hasError(ref) then + rstate.markError(ref) reportError case _ => reportError diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 102f517822..a57fe0e725 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -149,11 +149,11 @@ final class NewResolverState private ( data(host) host.isOwnedBy(root) && !root.completedNodes(new Identity(host)) - private val errors = mutable.Set.empty[Identity[Publisher[?]]] - def hasError(host: Publisher[?], original: Bool): Bool = original || root.errors(new Identity(host)) - def markError(host: Publisher[?])(update: => Unit): Unit = + private val errors = mutable.Set.empty[Identity[PossiblyErroneous]] + def hasError(host: PossiblyErroneous): Bool = host.isErroneous || root.errors(new Identity(host)) + def markError(host: Publisher[?] & PossiblyErroneous): Unit = root.errors += new Identity(host) - if canResolve(host) then update + if canResolve(host) then host.isErroneous = true /** Inference may refine an imported result, but the imported code has already * been compiled. Its member/constructor targets must remain unchanged. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index cc4ca9b49c..d6134c03ac 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -147,10 +147,9 @@ object Pattern: /** A shorthand for creating a variable pattern. */ def Variable = Pattern.Wildcard() binds (_: Ident) - trait ConstructorImpl extends PatternShapeHost: + trait ConstructorImpl extends PatternShapeHost, PossiblyErroneous: self: Pattern.Constructor => - var isErroneous: Bool = false // val resSym: FlowSymbol = FlowSymbol.app var resolvedTargets: Ls[DefinitionSymbol[?] | VarSymbol] = Nil diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 1f22cf9300..8dacd8a90b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -119,7 +119,6 @@ sealed trait AnySel extends ResolvableImpl: def validResolvedTargets = resolvedTargets.filterNot: case flow.SelectionTarget.Err(_) => true case _ => false - // var isErroneous: Bool = false // * to avoid reporting follow-on errors after a flow/resolution error end AnySel object AnySel: @@ -138,12 +137,11 @@ end AppImpl type Resolvable = Term & ResolvableImpl -sealed trait ResolvableImpl extends ShapeHost: +sealed trait ResolvableImpl extends ShapeHost, PossiblyErroneous: this: Term => import Resolvable.CallableDefinition - var isErroneous: Bool = false // * to avoid reporting follow-on errors after a flow/resolution error // private[semantics] val shapes: MutSet[Shape] = MutSet.empty def getShapes: Ls[Shape] = shapes.toList diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala index cdca2434f1..bf96a9add2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala @@ -27,13 +27,15 @@ type AnyResolvable = Resolvable | NewResolvableImpl type NewResolvable = NewResolvableImpl & Term -trait NewResolvableImpl: +/** Suppress follow-on diagnostics after a resolution or flow error. */ +trait PossiblyErroneous: + var isErroneous: Bool = false + +trait NewResolvableImpl extends PossiblyErroneous: self: MemberRef | NewSel | UnresolvedRef => var resolvedTargets: Ls[DefinitionSymbol[?]] = Nil // * filled during flow analysis // val resSym: FlowSymbol = FlowSymbol.simpleRef(self.sym.name) // var disamb: Opt[Disambiguation] = None // * filled during flow analysis - var isErroneous: Boolean = false // * filled during resolution - From d52a96c8c5034e6877d94ed8502ab8bc8feb438e Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 16:15:21 +0800 Subject: [PATCH 073/228] Check exposed resolution interfaces with lazy unknown provenance --- doc/new-resolution-suite-migration.md | 56 ++- .../src/test/scala/hkmc2/CompilerTest.scala | 18 + .../src/test/scala/hkmc2/PublisherTest.scala | 46 +++ .../src/main/scala/hkmc2/CompilerCtx.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 25 +- .../hkmc2/semantics/InterfaceExposure.scala | 169 ++++++++ .../scala/hkmc2/semantics/NewResolver.scala | 123 ++++-- .../main/scala/hkmc2/semantics/Shape.scala | 62 ++- .../src/main/scala/hkmc2/semantics/Term.scala | 2 +- .../main/scala/hkmc2/utils/Publisher.scala | 12 + .../shared/src/test/mlscript/basics/AppOp.mls | 2 +- .../src/test/mlscript/basics/DataClass.mls | 4 +- .../test/mlscript/basics/DynamicSelection.mls | 4 +- .../src/test/mlscript/basics/LazySpreads.mls | 2 +- .../test/mlscript/basics/ObjectExtensions.mls | 22 +- .../src/test/mlscript/basics/OfLambdaArgs.mls | 6 +- .../mlscript/basics/PureTermStatements.mls | 14 +- .../src/test/mlscript/codegen/BadWhile.mls | 4 +- .../src/test/mlscript/codegen/Bindings.mls | 2 +- .../test/mlscript/codegen/BlockPrinter.mls | 3 +- .../test/mlscript/codegen/BuiltinSymbols.mls | 12 +- .../src/test/mlscript/codegen/FunnyOpen.mls | 2 +- .../src/test/mlscript/codegen/Lambdas.mls | 2 +- .../shared/src/test/mlscript/codegen/Misc.mls | 18 +- .../test/mlscript/codegen/PlainClasses.mls | 20 +- .../test/mlscript/codegen/SingletonInit.mls | 4 +- .../src/test/mlscript/codegen/Spreads.mls | 2 + .../src/test/mlscript/newres/Annotations.mls | 16 +- .../src/test/mlscript/newres/Arrays.mls | 5 +- .../src/test/mlscript/newres/BareClasses.mls | 2 +- .../src/test/mlscript/newres/Basics.mls | 26 +- .../test/mlscript/newres/CallSiteShapes.mls | 75 ++-- .../src/test/mlscript/newres/CtxSens.mls | 38 +- .../test/mlscript/newres/DeclaredTypes.mls | 59 +-- .../src/test/mlscript/newres/Dynamic.mls | 2 +- .../src/test/mlscript/newres/Functions.mls | 37 +- .../test/mlscript/newres/GenericMethods.mls | 2 +- .../src/test/mlscript/newres/Inheritance.mls | 2 +- .../mlscript/newres/InterfaceExposure.mls | 371 ++++++++++++++++++ .../shared/src/test/mlscript/newres/Opens.mls | 16 +- .../mlscript/newres/OverloadedClassValues.mls | 20 +- .../src/test/mlscript/newres/ParamClasses.mls | 18 +- .../src/test/mlscript/newres/PatMat.mls | 36 +- .../src/test/mlscript/newres/Projections.mls | 41 +- .../src/test/mlscript/newres/Records.mls | 60 +-- .../mlscript/newres/RecursiveEnvironment.mls | 62 ++- .../src/test/mlscript/newres/ReslScratch.mls | 17 + .../src/test/mlscript/newres/SpreadCalls.mls | 74 +++- .../test/mlscript/newres/TypeResolution.mls | 2 + .../src/test/mlscript/newres/ValCtxSens.mls | 34 +- .../src/test/mlscript/newres/WIPTest.mls | 30 ++ .../test/mlscript/newres/loose/Targets.mls | 13 +- .../newres/wasm/WasmMethodResolution.mls | 4 +- .../mlscript/ucs/examples/Permutations.mls | 2 +- .../mlscript/ucs/future/SymbolicClass.mls | 2 +- .../test/mlscript/ucs/general/DualOptions.mls | 8 +- .../mlscript/ucs/general/InterleavedLet.mls | 2 +- .../src/test/mlscript/ucs/general/List.mls | 8 +- .../mlscript/ucs/patterns/AliasPattern.mls | 4 +- .../mlscript/ucs/patterns/Compilation.mls | 33 +- .../test/mlscript/ucs/patterns/Parameters.mls | 45 ++- .../mlscript/ucs/syntax/PlainConditionals.mls | 2 +- .../src/test/mlscript/ups/UpsBugsBacklog.mls | 4 +- .../ups/fixpoint/UnsupportedShapes.mls | 4 +- .../mlscript/ups/syntax/MixedParameters.mls | 14 +- .../test/mlscript/ups/syntax/WrongArity.mls | 10 +- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- hkmc2/shared/src/test/mlscript/wasm/Casts.mls | 6 +- .../src/test/mlscript/wasm/NumericTypes.mls | 8 +- .../test/mlscript/wasm/TailRecOptCasts.mls | 4 +- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 2 +- 71 files changed, 1479 insertions(+), 385 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala create mode 100644 hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 494eb85995..4058acabb9 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -167,12 +167,52 @@ provides it to a parameter independently of call-site inference. Type aliases ma also denote `dyn`. These rules apply in both strict and non-strict resolution. Dynamic instantiation uses the existing `new!` syntax. -Call validation still depends on the shapes available during elaboration. An -unused callback, a callback used in a later REPL block, or an unsupported function -signature may have no inferred callee shapes yet. Such an empty set does not prove -that the callee is non-callable. Diagnosing all targetless applications requires -separating pending or incomplete inference from a completed lookup with no target; -`dyn` does not fill arbitrary empty shape sets. +Before sealing a file or diff-test block, `InterfaceExposure` checks the values +reachable through its exposed interface. It seeds unannotated parameters and +unconstrained generic parameters with `UnknownValueShape`, then follows returned +functions, record/tuple contents, and public instance members. Local examples do +not close an exposed interface: both `fun foo(x) = x.a` and +`fun foo[A](x: A) = x.a` are rejected even if this unit calls `foo({a: 1})`. +Private helpers can retain local inference unless their function values escape. +Parameter annotations provide their declared shapes; callable result annotations +constrain returned implementations through their declared domains. + +Discovery uses a queue and temporary observers of shape publishers. Each reached +shape is processed once, with listeners discovering subsequent candidates; there +is no repeated whole-graph scan. Observers are detached before the unit is sealed +and are never copied into consumer states. New-resolution imports have already had +their interfaces checked; the importer does not recursively recheck definitions +from other compilation units. +An unknown reaching a previously sealed elimination can still report an error, +without changing its recorded target. + +Unknown values and unresolved spreads carry shared, lazy provenance. Messages, +locations, and diagnostic chains are materialized only on an error; provenance is +excluded from shape equality. Diagnostics can identify a generic parameter or +ordinary parameter and then show the record/tuple storage and return steps that +expose it. `newres/InterfaceExposure.mls` covers these paths, recursive discovery, +private helpers, returned typed closures, overloaded class exports, and later +exposure of a private definition from an earlier block. Constructor patterns +narrow unknown inputs to declared class interfaces, retaining unknown type +arguments and provenance for unannotated fields. They cannot discard the external +alternative and specialize those fields from local calls alone. + +Flooding also reveals existing limits of capture precision. Forwarding through +captured functions or constructor aliases, rebuilding an instance of the same +class, and exposing partially applied constructors can lose the originating +activation. These cases conservatively report unknown-value errors and remain +explicit `:fixme` regressions in `CtxSens`, `ValCtxSens`, `Projections`, +`RecursiveEnvironment`, and `SpreadCalls`; they need a more precise representation +of captured activations. Unknowns are not converted to dynamic values to bypass +these failures. Unsupported inference forms can still have no candidates at all; +completion of such empty flows remains separate from rejecting an unknown target. + +An additional gap is recorded in `InterfaceExposure.mls`: a private class returned +through a nominal result annotation can expose callable methods without those +implementations being flooded. Fixing this requires following the declared member +interface back to its implementations, while still keeping unannotated fields and +results opaque; traversing every initializer would incorrectly expose values the +annotation hides. Dynamic selections have no fabricated nominal definition. Lowering retains their runtime receiver and property name. Dynamic values propagate through record and @@ -183,7 +223,7 @@ requirements for a known, unambiguous identity. `DynShape` is distinct from `UnknownValueShape`: recursive widening, mutable record reads, and other losses of inference precision do not automatically -license dynamic member lookup. A known missing member in another receiver +license dynamic member lookup or calls. A known missing member in another receiver candidate still reports an error even if a dynamic candidate is also present. The same rule applies to values recovered from constructor patterns. @@ -192,7 +232,7 @@ Regression coverage is in `newres/Dynamic.mls`, `newres/Records.mls`, ### 2. Pattern transfer and synthesized references -Evidence: new shape propagation lacks record, conjunction, negation, string +Evidence: new shape propagation lacks record, negation, string concatenation, and transformation cases. Examples include `ucs/general/BooleanPatterns.mls`, `patterns/String.mls`, and `normalization/RecordImpliedByClass.mls`. Tuple bindings currently publish no diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index 3f6907b02d..69fc13de85 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -345,6 +345,24 @@ class CompilerTest extends AnyFunSuite: assert(parameters.map(p => (p.shapes.toVector, p.shapeListeners.toVector)) == before, "A consumer must not change the cached definition's inference graph") + test("an exported generic selection is rejected before a consumer can specialize it"): + val fs = new InMemoryFileSystem(loadStandardLibrary()) + given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace = false + val errors = scala.collection.mutable.ArrayBuffer.empty[Diagnostic] + given Raise = errors += _ + fs.write("/Unsafe.mls", """#lang(0.3.x, strictResolution: true) + |module Unsafe with + | fun foo[A](x: A) = x.a + |Unsafe.foo({a: 1}) + |""".stripMargin) + val compiler = new MLsCompiler(_ => summon[Raise]) + compiler.compileModule(Path("/Unsafe.mls")) + assert(errors.exists(_.theMsg.contains("Resolution error"))) + assert(errors.exists(_.allMsgs.exists(_._1.show.contains("Type parameter 'A'")))) + test("compiler can report errors"): val (fs, compiler) = createCompiler() diff --git a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala index cfe4b3bf70..9ff8b30928 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala @@ -147,3 +147,49 @@ class PublisherTest extends AnyFunSuite: next.inferenceHost assert(state.canResolve(next)) state.recordResolution(use, true)(fail("An unchanged decision must not be written again")) + + test("temporary observers replay once, detach, and are not inherited by consumers"): + given original: NewResolverState = new Elaborator.State().newResolverState + val host = new IntHost + val observed = ArrayBuffer.empty[Int] + val routed = ArrayBuffer.empty[Int] + host.publish(1) + host.subscribeToShapes(routed += _) + val detach = host.inferenceHost.observe(observed += _) + host.publish(2) + val consumer = new Elaborator.State().newResolverState + host.publish(3)(using consumer) + assert(observed.toList == List(1, 2)) + assert(routed.toList == List(1, 2, 3)) + detach() + host.publish(4) + assert(observed.toList == List(1, 2)) + assert(routed.toList == List(1, 2, 3, 4)) + + test("unknown provenance is lazy and does not affect shape identity"): + import semantics.{ShapeProvenance, UnknownValueShape, RecordShape} + import Message.MessageContext + var evaluated = 0 + def provenance = ShapeProvenance { + evaluated += 1 + Nil + } + val source = Term.UnitVal() + val first = UnknownValueShape(source)(provenance) + val second = UnknownValueShape(source)(provenance) + assert(Set(first, second).size == 1) + assert(RecordShape.Unknown(source)(provenance) == RecordShape.Unknown(source)(provenance)) + assert(evaluated == 0) + assert(first.provenance.diagnosticNotes.isEmpty) + assert(first.provenance.diagnosticNotes.isEmpty) + assert(evaluated == 1) + var noteEvaluated = 0 + val extended = first.provenance.via { + noteEvaluated += 1 + msg"A deferred provenance step" -> None + } + assert(noteEvaluated == 0) + assert(extended.diagnosticNotes.size == 1) + assert(extended.diagnosticNotes.size == 1) + assert(noteEvaluated == 1) + assert(evaluated == 1) diff --git a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala index 8010f6e1d8..b37884f384 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala @@ -111,7 +111,7 @@ class CompilerCtx( if !isRuntime then state.initRuntimeSymbolsFromFile(paths.runtimeSourceFile, prelude)( using tl, summon[Raise], artifactCtx) - val (blk0, _) = elaborationConfig.givenIn(elab.importFrom(parsed)) + val (blk0, _) = elaborationConfig.givenIn(elab.importFrom(parsed, exportedSymbol.toList)) if isRuntime then state.initRuntimeSymbolsFromBlock(blk0) @@ -221,7 +221,7 @@ class CompilerCtx( val parse = ParserSetup(file) val elab = Elaborator(tl, file.up, Ctx.empty) val initCtx = State.init.nestLocal("prelude") - val (blk, ctx) = elab.importFrom(parse.resultBlk)(using initCtx) + val (blk, ctx) = elab.importFrom(parse.resultBlk, Nil)(using initCtx) // Prelude declarations have no executable program, but their signatures and nominal hierarchy // must be erased before any compilation unit can use them. given Ctx = ctx diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index c445155c5e..551c728a34 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -209,6 +209,11 @@ object Elaborator: go(ctx.parent, nextCrossedFunction, nextCrossedLambdaOrHandler) go(S(this), false, false) + // Explicit `this` must carry the same intervening captures as a lexical + // member reference; inherited member results can retain the receiver's scope. + def getReceiver: Opt[Ctx.Elem] = + outer.inner.map(Ctx.RefElem(_)).orElse(parent.flatMap(_.getReceiver).map(capture)) + def getOuter: Opt[InnerSymbol] = outer.inner.orElse(parent.flatMap(_.getOuter)) def getNonLocalRetHandler: Opt[TempSymbol] = outer match case OuterCtx.Function(rsym, _) => S(rsym) @@ -465,7 +470,7 @@ object Elaborator: def isImport: Bool = true final case class CaptElem(base: Elem, thru: DefinitionSymbol[?]) extends Elem: def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver, NewResolverState): Term = - Term.Capture(base.ref(Ident(base.nme)), thru) + Term.Capture(base.ref(new Ident(base.nme).withLocOf(id)), thru).withLocOf(id) def symbol = base.symbol def isImport: Bool = false def nme: Str = base.nme @@ -1359,8 +1364,8 @@ extends Importer: h.toLoc :: Nil)) error case id @ Ident("this") => - ctx.getOuter match - case S(sym) => sym.ref(id) + (if newResolution then ctx.getReceiver else ctx.getOuter.map(Ctx.RefElem(_))) match + case S(elem) => elem.ref(id) case N => raise: ErrorReport(msg"Cannot use 'this' outside of an object scope" -> tree.toLoc :: Nil) @@ -2930,18 +2935,26 @@ extends Importer: msg"Expected a type parameter list (a tuple of identifiers), but found ${t.describe}" -> t.toLoc :: Nil (Nil, ctx) - def importFrom(sts: Block): Ctxl[(Blk, Ctx)] = + def importFrom(sts: Block, exports: Ls[BlockMemberSymbol]): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) val (res, newCtx) = block(sts, hasResult = false, resultInterp = Trm) // TODO handle name clashes - if newResolution then rstate.completeBlock(res) + if newResolution then + InterfaceExposure(this).check(exports, Nil) + rstate.completeBlock(res) (res, newCtx) def topLevel(sts: Block): Ctxl[(Blk, Ctx)] = given UnderCtx = new UnderCtx(N) val (res, ctx) = block(sts, hasResult = false, resultInterp = Trm) computeVariances(res) - if newResolution then rstate.completeBlock(res) + if newResolution then + val exports = res.stats.collect: + case d: Definition if InterfaceExposure.isPublic(d) => d.bsym + val values = res.stats.collect: + case DefineVar(sym: LocalVarSymbol, rhs) => Term.SimpleRef(sym)(new Ident(sym.nme).withLocOf(rhs)) + InterfaceExposure(this).check(exports, res.res :: values) + rstate.completeBlock(res) (res, ctx) def computeVariances(s: Statement): Unit = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala new file mode 100644 index 0000000000..f007d7d960 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -0,0 +1,169 @@ +package hkmc2 +package semantics + +import scala.collection.mutable +import hkmc2.utils.*, shorthands.* +import Term.* +import hkmc2.Message.MessageContext +import NewResolverState.Listener + +object InterfaceExposure: + def isPublic(definition: Definition): Bool = + !definition.annotations.contains(Annot.Private) && (definition match + case td: TermDefinition => (td.k isnt syntax.LetBind) || td.annotations.contains(Annot.Modifier(syntax.Keyword.`public`)) + case _: TypeDef => false + case _ => true) + +/** Check the inputs admitted by the exposed interface before resolution is sealed. + * Existing flow listeners propagate external inputs; temporary observers discover + * escaping values incrementally. Calling a private helper does not expose it, but + * returning that helper (possibly inside a record or tuple) does. + */ +final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL): + private given Elaborator.State = rstate.owner + private val work = mutable.Queue.empty[() => Unit] + private val detach = mutable.ArrayBuffer.empty[() => Unit] + private val watched = mutable.Set.empty[Any] + private val exposed = mutable.Set.empty[TermShape] + private val flows = mutable.Map.empty[BlockMemberSymbol, FlowSymbol] + + /** Forwarding edges belong to the inference graph. Only the final observer + * captures this pass's queue and visited sets, and it is detached on exit. + */ + private def watch(key: Any)(connect: Listener => Unit)(receive: Listener)(using state: NewResolverState): Unit = + if watched.add((key, state)) then + val host = new TypeValues + connect(host.publish) + detach += host.inferenceHost.observe: shape => + val current = rstate + work.enqueue(() => receive(shape)(using current)) + + private type Path = ShapeProvenance + + private def term(value: Term, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = + watch((new Identity(value), marks))(resolver.listenTerm(value)): shape => + emit(shape.exit(marks), path) + + private def emit(shape: TermShape | NoShape, path: Path)(using NewResolverState): Unit = shape match + case shape: TermShape if exposed.add(shape) => + val current = rstate + work.enqueue(() => value(shape, path)(using current)) + case _ => () + + private def source(symbol: BlockMemberSymbol): MemberRef = + val flow = flows.getOrElseUpdate(symbol, FlowSymbol("exposed")) + MemberRef(symbol)(new syntax.Tree.Ident(symbol.nme).withLocOf(symbol), flow) + + private def member(symbol: BlockMemberSymbol, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = + if (symbol.getState is rstate.owner) && (symbol.asModOrObj.isDefined || symbol.asTrm.isDefined || symbol.asCls.isDefined) then + val ref = source(symbol) + watch((symbol, marks))(resolver.fromBMS(symbol, ref.resSym, marks, _, ref, _ => ()))(emit(_, path)) + // A term companion can hide the constructor in term position, but clients + // still have access to the class through `new` and class projections. + if symbol.asModOrObj.isDefined || symbol.asTrm.exists(!_.isInstanceOf[ClassCtorSymbol]) then + symbol.asCls.flatMap(_.defn).foreach: cls => + val resolver = this.resolver + watch((cls.sym, marks))(listener => resolver.listenExt(cls.ext, ext => + DefnShape(cls, ext).exit(ExitMark(ResolutionBoundary(cls.sym), S(ref.resSym), NoMarks)).exit(marks) match + case shape: TermShape => listener(shape) + case NoShape => ()))(emit(_, path)) + + private def parameter(param: Param, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = if resolver.parameterSignature(param).isEmpty then + val ref = SimpleRef(param.sym)(param.sym.id) + val unknown = UnknownValueShape(ref)(ShapeProvenance( + (msg"Parameter '${param.sym.nme}' admits values of unknown shape." -> param.toLoc) :: path.diagnosticNotes)) + resolver.constrainParameter(param, unknown.enter(marks), marks) + + private def parameters(lists: Ls[(ParamList, Ls[Marks])], path: Path)(using NewResolverState): Unit = + lists.foreach: (params, marks) => + params.params.foreach(parameter(_, marks, path)) + params.restParam.foreach: rest => + val ref = SimpleRef(rest.sym)(rest.sym.id) + val unknown = UnknownValueShape(ref)(ShapeProvenance( + (msg"Rest parameter '${rest.sym.nme}' admits elements of unknown shape." -> rest.toLoc) :: path.diagnosticNotes)) + val tuple = TupleShape(ref, TupleShape.Unknown(ref, Nil, unknown) :: Nil)(resolver) + resolver.constrainParameter(rest, tuple.enter(marks), marks) + + private def typeParameters(params: Ls[VarSymbol], marks: Ls[Marks], path: Path)(using NewResolverState): Unit = + params.foreach: param => + if !rstate.hasExplicitTypeArgument(param, marks) then + val ref = SimpleRef(param)(param.id) + resolver.publishParameter(param, UnknownValueShape(ref)(ShapeProvenance( + (msg"Type parameter '${param.nme}' does not specify a member interface." -> param.toLoc) :: path.diagnosticNotes)).enter(marks)) + + private def members(cls: ClassLikeDef, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = + cls.body.blk.stats.foreach: + case d: Definition if InterfaceExposure.isPublic(d) => member(d.bsym, marks, path.via(msg"Member '${d.bsym.nme}' is accessible here." -> d.toLoc)) + case _ => () + cls.ext.foreach: parent => + term(parent, marks, path.via(msg"This parent contributes inherited members." -> parent.toLoc)) + + private def result(body: Term, sign: Opt[Term], marks: Ls[Marks], path: Path)(using NewResolverState): Unit = sign match + case N => term(body, marks, path.via(msg"This value is returned here." -> body.toLoc)) + case S(sign) => + // Check returned implementations against the declared calling interface. + // Its parameter types constrain returned closures instead of unrestricted + // unknowns, and consumers see only the annotated result. + val declared = resolver.declaredType(resolver.typeResolution(sign), Map.empty) + watch((new Identity(body), declared, marks))(resolver.listenTerm(body)): shape => + resolver.constrainFunction(declared, shape, marks) + watch((declared, marks))(resolver.listenTypeValues(declared)): shape => + emit(shape.exit(marks), path) + + private def value(shape: TermShape, path: Path)(using NewResolverState): Unit = + val (head, marks) = shape.applicationHead + head match + case ds: DefnShape if ds.defn.sym.getState is rstate.owner => + shape match + case Marked(_: DefnShape, _) => + typeParameters(ds.defn match + case td: TermDefinition => td.tparams.toList.flatten.map(_.sym) + case cls: ClassLikeDef => cls.tparams.map(_.sym) + case _ => Nil + , marks, path) + case _ => () + parameters(shape.unappliedParams, path) + ds.clsDef match + case S(cls) => + // Exposing a constructor also exposes instances clients can create. + shape match + case Marked(_: DefnShape, _) if cls.paramsOpt.isEmpty => + parameters(cls.auxParams.map(_ -> marks), path) + case _ => () + members(cls, marks, path) + case N => ds.defn match + case td: TermDefinition => td.body.foreach(result(_, resolver.resultSignature(td), marks, path)) + case _ => () + case _: DefnShape => () // Imported definitions were checked before publication. + case intro: IntroShape => intro.trm match + case Lam(_, body) => + parameters(shape.unappliedParams, path) + term(body, marks, path.via(msg"This value is returned here." -> body.toLoc)) + case _ => () + case tuple: TupleShape => tuple.segments.foreach: + case field: TupleShape.Fixed => + watch((tuple, field, marks))(resolver.listenTupleField(field)): shape => + emit(shape.exit(marks), path.via(msg"This value is stored in this tuple." -> tuple.source.toLoc)) + case _ => () + case record: RecordShape => record.elements.foreach: + case RecordShape.Field(field) => term(field.rhs, marks, + path.via(msg"This value is stored in this record field." -> field.toLoc)) + case RecordShape.Spread(inner, context) => emit(inner.exit(context).exit(marks), path) + case _ => () + case base: BaseShape => members(base.defn, marks, path) + case callable: CallableTypeShape => + callable.result.foreach: result => + watch((result, marks))(resolver.listenTypeValues(result)): shape => + emit(shape.exit(marks), path) + // Nominal annotations expose their declared interface, not an initializer's + // inferred implementation. Unknown and dynamic values have no static graph. + case _: (NominalTypeShape | OpaqueTypeShape | UnknownValueShape | DynShape | ErrShape) => () + + def check(exports: Ls[BlockMemberSymbol], values: Ls[Term]): Unit = + try + exports.foreach: symbol => + member(symbol, Nil, ShapeProvenance.empty.via(msg"'${symbol.nme}' is exposed by this compilation unit." -> symbol.toLoc)) + values.foreach: value => + term(value, Nil, ShapeProvenance.empty.via(msg"This value is exposed by this compilation unit." -> value.toLoc)) + while work.nonEmpty do work.dequeue()() + finally detach.reverseIterator.foreach(_()) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 9b45e26841..d329878317 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -91,6 +91,16 @@ class NewResolver: // Imported prelude declarations can still contain legacy references. case Ref(symbol: TypeSymbol) => select(symbol) case Ref(member: BlockMemberSymbol) => selectMember(member) + // Imported parent declarations can contain legacy synthesized selections. + // Their identities were fixed by the old resolver; do not reinterpret + // their term syntax or consult unfinished new-resolution candidates. + case ref: (SynthSel | Sel | Resolved) => ref.legacyResolvedSym match + case S(symbol: TypeSymbol) => select(symbol) + case S(ctor: ClassCtorSymbol) => select(ctor.associatedCls) + case S(member: BlockMemberSymbol) => selectMember(member) + case _ => + result.fail(msg"This reference does not denote a type." -> ref.toLoc :: Nil) + result.publish(TypeShape.Abstract) case Capture(base, thru) => result.publish(TypeShape.Captured(typeResolution(base), thru)) case TyApp(base, args) => result.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) case Forall(_, _, body) => typeResolution(body).listen(result.publish) @@ -137,7 +147,7 @@ class NewResolver: * interfaces such as Tree[A] = Node[A] | Empty would accumulate equivalent * A-to-A environments every time a recursive field is selected. */ - private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType])(using NewResolverState): DeclaredType = + private[semantics] def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType])(using NewResolverState): DeclaredType = resolution.currentShapes.toList match case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => bindings(symbol) case _ => DeclaredType(resolution, bindings) @@ -159,7 +169,7 @@ class NewResolver: def listenTypeValues(sign: Term)(listener: Listener)(using NewResolverState): Unit = listenTypeValues(declaredType(typeResolution(sign), Map.empty))(listener) - private def listenTypeValues(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = + private[semantics] def listenTypeValues(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = typeValues.get(tpe) match case S(host) => host.listen(listener) case N => @@ -242,7 +252,7 @@ class NewResolver: bind(paramLists, declaredType(typeResolution(sign), Map.empty)) /** Synthesized constructor fields retain their source declaration on the symbol. */ - private def resultSignature(td: TermDefinition)(using NewResolverState): Opt[Term] = td.tsym.decl match + private[semantics] def resultSignature(td: TermDefinition)(using NewResolverState): Opt[Term] = td.tsym.decl match case S(p: Param) => p.sign case _ => td.resultSignature @@ -293,7 +303,7 @@ class NewResolver: td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), ps.restParam.nonEmpty)), result, Nil)) else result match case S(tpe) => listenTypeValues(tpe)(publish) - case N => publish(UnknownValueShape(source)) + case N => publish(UnknownValueShape.at(source)) case _ => // A nested nominal declaration denotes its statically selected symbol; // selecting it does not inspect an instance field or method body. @@ -314,10 +324,10 @@ class NewResolver: case value: TermShape => listener(value) case _ => softAssert(false, "A type parameter received a symbolic overload set") - private def publishParameter(symbol: VarSymbol, shape: TermShape | NoShape)(using NewResolverState): Unit = + private[semantics] def publishParameter(symbol: VarSymbol, shape: TermShape | NoShape)(using NewResolverState): Unit = publishParameter(symbol.inferenceHost, shape) - private def publishParameter(host: Publisher.Data[Shape], shape: TermShape | NoShape)(using NewResolverState): Unit = shape match + private[semantics] def publishParameter(host: Publisher.Data[Shape], shape: TermShape | NoShape)(using NewResolverState): Unit = shape match case value: TermShape => host.publish(value) case _ => () @@ -401,7 +411,7 @@ class NewResolver: * Read an implementation only while checking it against its declared interface; * calls through that interface continue to expose only the declared result. */ - private def constrainFunction(expected: DeclaredType, actual: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = + private[semantics] def constrainFunction(expected: DeclaredType, actual: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = val context = actual.applicationHead._2 def results(listener: Listener)(using NewResolverState): Unit = actual.applicationHead._1 match case intro: IntroShape => intro.trm match @@ -433,7 +443,7 @@ class NewResolver: case S(tpe) => TupleShape.TypedField(tpe, Nil) case N => TupleShape.UnknownField(callable.source, Nil) val suffix = if declared.hasRest - then TupleShape.Unknown(callable.source, Nil, UnknownValueShape(callable.source)) :: Nil + then TupleShape.Unknown(callable.source, Nil, UnknownValueShape.at(callable.source)) :: Nil else Nil val tuple = TupleShape(callable.source, fields ::: suffix)(this) constrainParameter(rest, tuple.exit(expectedContext).enter(context), context) @@ -501,14 +511,15 @@ class NewResolver: case NoShape => () parent - private def listenTupleField(field: TupleShape.Fixed)(listener: Listener)(using NewResolverState): Unit = + private[semantics] def listenTupleField(field: TupleShape.Fixed)(listener: Listener)(using NewResolverState): Unit = def receive(shape: TermShape)(using NewResolverState): Unit = shape.exit(field.marks) match case value: TermShape => listener(value) case NoShape => () field match case TupleShape.Field(field, _) => listenTerm(field.term)(receive) case TupleShape.TypedField(tpe, _) => listenTypeValues(tpe)(receive) - case TupleShape.UnknownField(source, _) => receive(UnknownValueShape(source)) + case TupleShape.UnknownField(source, _) => receive(UnknownValueShape.at(source)) + case TupleShape.ValueField(value, _) => receive(value) /** Subscribe once to complete argument-tuple candidates. A pending spread is not * an argument, nor evidence of an arity mismatch; each resolved combination is. @@ -518,16 +529,18 @@ class NewResolver: zipArgumentShapes(mss, ps.length, r.nonEmpty, args, src, funSh): (index, shape) => constrainParameter(params(index), shape, mss) - private def constrainParameter(p: Param, shape: TermShape | NoShape, marks: Ls[Marks])(using NewResolverState): Unit = + private[semantics] def constrainParameter(p: Param, shape: TermShape | NoShape, marks: Ls[Marks])(using NewResolverState): Unit = shape match case NoShape => () case sh: TermShape => - val sign = p.sign.map(sign => declaredType(typeResolution(sign), Map.empty)).orElse(signatureParameters.get(p.sym)) - .orElse(p.sym.getState.newResolverState.signatureParameters.get(p.sym)) - sign match + parameterSignature(p) match case S(tpe) => inferTypeArguments(tpe, sh, marks) case N => publishParameter(p.sym, sh) + private[semantics] def parameterSignature(p: Param)(using NewResolverState): Opt[DeclaredType] = + p.sign.map(sign => declaredType(typeResolution(sign), Map.empty)).orElse(signatureParameters.get(p.sym)) + .orElse(p.sym.getState.newResolverState.signatureParameters.get(p.sym)) + private def zipArgumentShapes(mss: Ls[Marks], expectedCount: Int, hasRest: Bool, args: Term, src: Term, funSh: TermShape)(publish: (Int, TermShape | NoShape) => NewResolverState ?=> Unit)(using NewResolverState): Unit = def matchSegments(tuple: TupleShape, marks: Marks)(using NewResolverState): Unit = @@ -576,7 +589,7 @@ class NewResolver: if approximate then unknown :: Nil else Nil case (_: TupleShape.Unknown) :: rest => val suffix = drop(rest, count, false) - if approximate then TupleShape.Unknown(tuple.source, Nil, UnknownValueShape(tuple.source)) :: suffix else suffix + if approximate then TupleShape.Unknown(tuple.source, Nil, UnknownValueShape.at(tuple.source)) :: suffix else suffix val remaining = drop(segments, expectedCount, true) val rest = if expectedCount == 0 then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil)(this) publish(expectedCount, rest.exit(marks).enter(mss)) @@ -697,6 +710,8 @@ class NewResolver: case Pattern.Wildcard() | Pattern.Literal(_) => matched(shape) case Pattern.Chain(left, right) => matchShapePat(shape, left)(sh => matchShapePat(sh, right)(matched)) + case Pattern.Composition(false, left, right) => + matchShapePat(shape, left)(sh => matchShapePat(sh, right)(matched)) case Pattern.Composition(true, left, right) => matchShapePat(shape, left)(matched) matchShapePat(shape, right)(matched) @@ -706,7 +721,7 @@ class NewResolver: case ctor: Pattern.Constructor => def listenConstructor(psh: PatternShape)(using NewResolverState): Unit = psh match case CtorPatternShape(cls, fs, _, resSym) => - def check(sh: TermShape)(using NewResolverState): Unit = + def check(sh: TermShape, unknown: Opt[TermShape])(using NewResolverState): Unit = if sh.isInstanceOfClass(cls) then fs.foreach: (bms, pat) => sh.getMember(bms.nme) match @@ -716,7 +731,15 @@ class NewResolver: case MemberLookup.Declared(member, bindings, marks) => listenDeclaredMember(member, bindings, resSym, ctor.target, _ => ()): field => field.exit(marks) match - case value: TermShape => matchShapePat(value, pat)(_ => ()) + case value: TermShape => + val field = (value.applicationHead._1, unknown) match + case (_: UnknownValueShape, S(Marked(origin: UnknownValueShape, marks))) => + UnknownValueShape(origin.source)(origin.provenance.via( + msg"This constructor field may contain a value of unknown shape." -> member.toLoc)).exit(marks) + case _ => value + field match + case field: TermShape => matchShapePat(field, pat)(_ => ()) + case NoShape => () case NoShape => () case _ => // classPattern only publishes fields present in cls.body. @@ -729,21 +752,40 @@ class NewResolver: // from a malformed class (filtered by classPattern above). softAssert(false, "Matched constructor is missing its field") matched(sh) + def narrow(sh: TermShape)(using NewResolverState): Unit = sh match + case Marked(unknown: UnknownValueShape, _) => + // A class test can succeed for an external input. Its fields then + // expose their declarations, not shapes observed in local calls. + // Unknown type arguments retain the original diagnostic witness. + val base = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) + base.publish(TypeShape.Nominal(cls)) + val arg = new TypeResolution(unknown.source, messages => resolError(unknown.source, messages)) + arg.publish(TypeShape.Inferred(sh)) + val applied = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) + applied.publish(TypeShape.Applied(base, cls.tparams.map(_ => arg))) + listenTypeValues(declaredType(applied, Map.empty))(check(_, S(sh))) + case Marked(opaque: OpaqueTypeShape, marks) => + UnknownValueShape.at(opaque.source).exit(marks) match + case value: TermShape => narrow(value) + case NoShape => () + case _ => check(sh, N) shape match - case sh: TermShape => check(sh) - case sh: SymShape => fromSymbol(sh, check, ctor.target, _ => ()) + case sh: TermShape => narrow(sh) + case sh: SymShape => fromSymbol(sh, narrow, ctor.target, _ => ()) ctor.subscribeToShapes(listenConstructor) case Pattern.Tuple(_, _) => () // Tuple binding shapes are not inferred yet. - case _ => TODO(pattern) + case _ => + raise(ErrorReport(msg"This pattern is not supported during shape resolution." -> pattern.toLoc :: Nil)) def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern)(using NewResolverState): Unit = if newResolution then listenTerm(scrutinee)(sh => matchShapePat(sh, pattern)(_ => ())) def appShape(lhs: TermShape, args: Term, res: App)(using NewResolverState): Unit = - // An unknown element used as a callee stays a dynamic call. Propagate its - // unknown result rather than inferring callability from a different candidate. + // Only explicit dynamic values authorize calls without a known interface. + // An unknown alternative must invalidate the call even if other candidates + // happen to be callable in this compilation unit. lhs match - case Marked(_: (DynShape | UnknownValueShape), _) => + case Marked(_: DynShape, _) => if res.currentShapes.add(lhs) then res.notifyShapeListeners(lhs) return case _ => () @@ -762,7 +804,7 @@ class NewResolver: callable.paramLists.tail match case Nil => callable.result match case S(tpe) => listenTypeValues(tpe)(publish) - case N => publish(UnknownValueShape(callable.source)) + case N => publish(UnknownValueShape.at(callable.source)) case tail => publish(callable.copy(paramLists = tail)) return case _ => () @@ -791,7 +833,10 @@ class NewResolver: msg"${lhs.describe.capitalize} cannot receive more argument lists." case _ => msg"${lhs.describe.capitalize} cannot be called like a function." - resolError(res, message -> lhs.toLoc :: Nil) + val notes = lhs.applicationHead._1 match + case unknown: UnknownValueShape => unknown.provenance.diagnosticNotes + case _ => Nil + resolError(res, (message -> lhs.toLoc) :: notes) if sh.isSaturated then def go(body: Term, mss: Ls[Marks])(using NewResolverState) = listenTerm(body): sh => @@ -853,7 +898,7 @@ class NewResolver: // determines what a read returns. Publish the known property symbol for // selection/assignment, but an unknown shape for the value being read. rstate.recordResolution(host, host.resolvedTargets.contains(field.tsym))(host.resolvedTargets ::= field.tsym) - publish(UnknownValueShape(field.rhs)) + publish(UnknownValueShape.at(field.rhs)) private def publishDeclared(host: NewResolvable & ShapeHost, member: BlockMemberSymbol, flow: FlowSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks])(using NewResolverState): Unit = @@ -867,14 +912,14 @@ class NewResolver: if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) case NoShape => () - private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, loc: Opt[Loc])(using NewResolverState): Unit = + private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, provenance: ShapeProvenance)(using NewResolverState): Unit = if !rstate.hasError(host) then rstate.markError(host) val message = reason match case MemberLookup.Uncertainty.ValueShape => msg"Cannot resolve member '$name' of a value with unknown shape." case MemberLookup.Uncertainty.RecordOverwrite => msg"Cannot resolve member '$name' across a computed key or unknown record spread." - resolError(host, message -> loc :: Nil) + resolError(host, (message -> N) :: provenance.diagnosticNotes) def unresolvedRef(ref: UnresolvedRef)(using NewResolverState): Unit = ref.prefixes.foreach: prefix => @@ -897,7 +942,7 @@ class NewResolver: // A known miss in one wildcard source is not an error: another may // provide the name. Lowering diagnoses references with no candidates. () - case MemberLookup.Unknown(reason, loc) if rstate.canResolve(ref) => unknownMember(ref, ref.id.name, reason, loc) + case MemberLookup.Unknown(reason, provenance) => unknownMember(ref, ref.id.name, reason, provenance) case _ => () // Completed references only route their selected members. @@ -962,7 +1007,7 @@ class NewResolver: case MemberLookup.Missing if rstate.canResolve(sel) => rstate.markError(sel) resolError(sel, msg"$description does not contain member '${sel.id.name}'" -> loc :: Nil) - case MemberLookup.Unknown(reason, loc) if rstate.canResolve(sel) => unknownMember(sel, sel.id.name, reason, loc) + case MemberLookup.Unknown(reason, provenance) => unknownMember(sel, sel.id.name, reason, provenance) // Later activations still transport field values through the compiled // selection, but cannot choose a different member for that old syntax. case _ => () @@ -1278,7 +1323,7 @@ class NewResolver: case Marked(tuple: TupleShape, marks) => // Array methods can change both the elements and the length. Retain the // producer dependency, but none of the initializer's fixed layout. - val fields = TupleShape.Unknown(trm, Nil, UnknownValueShape(trm)) :: Nil + val fields = TupleShape.Unknown(trm, Nil, UnknownValueShape.at(trm)) :: Nil TupleShape(trm, TupleShape.Rest(tuple, fields) :: Nil)(this).exit(marks) match case shape: TermShape => listener(shape) case NoShape => () @@ -1316,10 +1361,11 @@ class NewResolver: case Marked(_: DynShape, marks) => val spread = TupleShape(term, TupleShape.Unknown(term, Nil, DynShape()) :: Nil)(this) expand(rest, TupleShape.Spread(spread, marks) :: reversed) - case Marked(_, marks) => + case shape @ Marked(_, marks) => // Opaque iterables (e.g. external Arrays) have no resolved // element layout. Their runtime spread is still permitted. - expand(rest, TupleShape.Spread(TupleShape.unknown(term)(this), marks) :: reversed) + val unknown = TupleShape(term, TupleShape.Unknown(term, Nil, UnknownValueShape.spread(term, shape)) :: Nil)(this) + expand(rest, TupleShape.Spread(unknown, marks) :: reversed) expand(tuple.fields, Nil) case record: Rcd => listenAggregate(record, listener): publish => def expand(stats: Ls[Statement], reversed: Ls[RecordShape.Element])(using NewResolverState): Unit = stats match @@ -1335,11 +1381,11 @@ class NewResolver: // Bound recursive record producers just as for tuple spreads. // Keep surrounding explicit fields even when the spread widens. val spread = if shape.containsSpread(shape.source, marks) - then RecordShape(shape.source, RecordShape.Unknown :: Nil) + then RecordShape(shape.source, RecordShape.Unknown(term)(UnknownValueShape.spread(term, shape).provenance) :: Nil) else shape expand(rest, RecordShape.Spread(spread, marks) :: reversed) case Marked(_: DynShape, marks) => expand(rest, RecordShape.Dynamic(marks :: Nil) :: reversed) - case _ => expand(rest, RecordShape.Unknown :: reversed) + case _ => expand(rest, RecordShape.Unknown(term)(UnknownValueShape.spread(term, shape).provenance) :: reversed) case _ :: rest => expand(rest, reversed) expand(record.stats, Nil) case intro: IntroTerm => @@ -1373,9 +1419,10 @@ class NewResolver: case S(defn) => completed(defn) case N => symbol.defnListeners += completed case ref @ MemberRef(sym: TermSymbol) => - ??? - // listenDefn(sym, sh => - // listener(MarkedShape.enter(sh, sym, S(ref.resSym)))) + listenDefn(sym, sh => + MarkedShape.exit(sh, ResolutionBoundary(sym), S(ref.resSym)) match + case sh: TermShape => listener(sh) + case NoShape => ()) case ref @ MemberRef(sym: BlockMemberSymbol) => val fs = ref.resSym val sh = symShapes.getOrElseUpdate((sym, fs, Nil), SymShape(sym, fs, Nil)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index c5ccda152e..c8e31d3d68 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -3,6 +3,7 @@ package semantics import hkmc2.utils.*, shorthands.* import syntax.* +import hkmc2.Message.MessageContext import hkmc2.document.* import hkmc2.document.Document.* import scala.collection.mutable @@ -127,7 +128,7 @@ object MarkedShape: marks match case marks: ExitMark => MarkedShape(sh, ExitMark(boundary, id, marks)) case EntryMark(entered, id2, rest) => - assert(entered == boundary, "Entry and exit cross different lexical resolution scopes") + assert(entered == boundary, s"Entry and exit cross different lexical resolution scopes: ${entered.showDbg} vs ${boundary.showDbg} for ${sh.shwDbg}") if id.forall(i1 => id2.forall(i2 => i1 is i2)) then rest match case NoMarks => sh @@ -264,7 +265,7 @@ enum MemberLookup: case Indexed(field: TupleShape.Fixed, marks: Ls[Marks]) case Dynamic(marks: Ls[Marks]) case Missing - case Unknown(reason: MemberLookup.Uncertainty, loc: Opt[Loc]) + case Unknown(reason: MemberLookup.Uncertainty, provenance: ShapeProvenance) def withMarks(marks: Ls[Marks]): MemberLookup = this match case Found(member, inner) => Found(member, inner ::: marks) @@ -395,7 +396,8 @@ final case class OpaqueTypeShape(source: Term) extends NonAppTermShape: def describe: Str = "value of abstract type" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, toLoc) + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, + ShapeProvenance(msg"This abstract type does not specify a member interface." -> toLoc :: Nil)) class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTermShape: /** Instance lookup is shared by constructor calls and explicit `new`. @@ -484,10 +486,10 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element])(reso def loop(rest: Ls[TupleShape.Segment], index: Int): MemberLookup = rest match case (field: TupleShape.Fixed) :: tail => if index == 0 then MemberLookup.Indexed(field, Nil) else loop(tail, index - 1) - case (_: TupleShape.Unknown) :: _ => + case (segment: TupleShape.Unknown) :: _ => // The position is a valid array operation even when a spread or // mutation has erased the element layout. Its value remains unknown. - MemberLookup.Indexed(TupleShape.UnknownField(source, Nil), Nil) + MemberLookup.Indexed(TupleShape.ValueField(segment.value, segment.marks), Nil) case Nil => MemberLookup.Missing loop(segments, index) case _ => arrayParent.getMember(name) @@ -501,15 +503,40 @@ final case class DynShape() extends NonAppTermShape: def toLoc: Opt[Loc] = N protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Dynamic(Nil) -/** The element of an opaque or widened spread can be any value. Keep this - * alternative in the flow graph so other, known arguments cannot silently make - * an unresolved member selection appear to have a unique static target. +/** A shared diagnostic witness, independent of resolution marks. Messages, + * locations, and the flattened chain are evaluated only when reporting an error. + * Discovery retains one witness per reached shape to bound recursive paths. */ -final case class UnknownValueShape(source: Term) extends NonAppTermShape: +final class ShapeProvenance(notes: => Ls[(Message, Opt[Loc])]): + lazy val diagnosticNotes: Ls[(Message, Opt[Loc])] = notes + def via(note: => (Message, Opt[Loc])): ShapeProvenance = + ShapeProvenance(note :: diagnosticNotes) + +object ShapeProvenance: + val empty = ShapeProvenance(Nil) + +/** An unknown input or the element of an opaque or widened spread can be any + * value. Keep this alternative in the flow graph so known candidates cannot + * silently make an unresolved operation appear to have a static target. + * Provenance is outside case-class equality: another diagnostic witness must + * not turn the same unknown into a new inference candidate. + */ +final case class UnknownValueShape(source: Term)(val provenance: ShapeProvenance) extends NonAppTermShape: def describe: Str = "value of unknown shape" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, toLoc) + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, provenance) + +object UnknownValueShape: + def at(source: Term): UnknownValueShape = + UnknownValueShape(source)(ShapeProvenance(msg"The shape of this value is unknown." -> source.toLoc :: Nil)) + def spread(source: Term, value: TermShape): UnknownValueShape = + UnknownValueShape(source)(ShapeProvenance { + val notes = value.applicationHead._1 match + case unknown: UnknownValueShape => unknown.provenance.diagnosticNotes + case _ => Nil + (msg"This spread has no known element shape." -> source.toLoc) :: notes + }) object TupleShape: sealed trait Element @@ -520,7 +547,9 @@ object TupleShape: case Field(field, inner) => Field(field, inner ::: outer) case TypedField(tpe, inner) => TypedField(tpe, inner ::: outer) case UnknownField(source, inner) => UnknownField(source, inner ::: outer) + case ValueField(value, inner) => ValueField(value, inner ::: outer) final case class Field(field: Fld, marks: Ls[Marks]) extends Fixed + final case class ValueField(value: TermShape, marks: Ls[Marks]) extends Fixed final case class TypedField(tpe: DeclaredType, marks: Ls[Marks]) extends Fixed // Mutable tuple slots retain their positions, but their initializer cannot // supply the shape of later reads (including reads through a spread copy). @@ -532,7 +561,7 @@ object TupleShape: final case class Unknown(source: Term, marks: Ls[Marks], value: NonMarkedShape) extends Segment final case class Spread(shape: TupleShape, marks: Marks) extends Element def unknown(source: Term)(resolver: NewResolver): TupleShape = - TupleShape(source, Unknown(source, Nil, UnknownValueShape(source)) :: Nil)(resolver) + TupleShape(source, Unknown(source, Nil, UnknownValueShape.at(source)) :: Nil)(resolver) /** Retain the original candidate as well as the selected residual segments: * flattening away the parent would hide recursive producer dependencies from * containsSpread, allowing recursion through rest slicing to evade widening. */ @@ -555,18 +584,19 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = import MemberLookup.* - def unknown = Unknown(Uncertainty.RecordOverwrite, toLoc) + def unknown(source: Located) = Unknown(Uncertainty.RecordOverwrite, + ShapeProvenance(msg"This computed key can overwrite the selected member." -> source.toLoc :: Nil)) def loop(rest: Ls[RecordShape.Element]): MemberLookup = rest match case Nil => Missing case RecordShape.Field(field) :: rest => field.field match case Term.Lit(Tree.StrLit(key)) => if key == name then Found(RecordMember(field, source.mut), Nil) else loop(rest) - case _ => unknown - case RecordShape.Unknown :: _ => unknown + case _ => unknown(field.field) + case (unknown: RecordShape.Unknown) :: _ => Unknown(Uncertainty.RecordOverwrite, unknown.provenance) case RecordShape.Dynamic(marks) :: _ => Dynamic(marks) case RecordShape.Spread(shape, marks) :: rest => shape.getMember(name) match case Dynamic(inner) => Dynamic(inner ::: marks :: Nil) - case Unknown(_, _) => unknown + case Unknown(_, provenance) => Unknown(Uncertainty.RecordOverwrite, provenance) case Missing => loop(rest) case Found(member: RecordMember, inner) => Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil) @@ -583,7 +613,7 @@ object RecordShape: enum Element: case Field(field: RcdField) case Spread(shape: RecordShape, marks: Marks) - case Unknown + case Unknown(source: Term)(val provenance: ShapeProvenance) case Dynamic(marks: Ls[Marks]) export Element.* diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 8dacd8a90b..247ca3a442 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -972,7 +972,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: protected def children: Vector[Located] = this match case t: Lit => Vector.single(t.lit.asTree) - case t: Ref => treeOrSubterms(t.tree) + case t: AnyRef_ => treeOrSubterms(t.tree) case t: Tup => treeOrSubterms(t.tree) case l: Lam => Vector.double(l.params, l.body) case t: App => treeOrSubterms(t.tree) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala index eb3e143045..023e541c42 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/Publisher.scala @@ -15,6 +15,13 @@ object Publisher: private[hkmc2] var completed = false val listeners: ArrayBuffer[ShapeListener[A]] = ArrayBuffer.empty val shapes: LinkedHashSet[A] = LinkedHashSet.empty + // Pass-local observers never become part of a copied inference graph. + private val observers: ArrayBuffer[ShapeListener[A]] = ArrayBuffer.empty + def observe(listener: ShapeListener[A])(using state: NewResolverState): () => Unit = + val data = state.local(this) + data.observers += listener + data.replay(listener) + () => { data.observers -= listener; () } def copy(state: NewResolverState): Data[A] = val result = new Data[A] result.owner = state @@ -33,10 +40,15 @@ object Publisher: def notify(shape: A)(using NewResolverState): Unit = // Appended listeners receive this candidate through replay. val count = listeners.size + val observerCount = observers.size var i = 0 while i < count do listeners(i)(shape) i += 1 + i = 0 + while i < observerCount do + observers(i)(shape) + i += 1 def addListener(listener: ShapeListener[A])(using origin: NewResolverState): Unit = origin.local(this).listeners += (shape => (current: NewResolverState) ?=> listener(shape)(using current.inGraph(origin))) diff --git a/hkmc2/shared/src/test/mlscript/basics/AppOp.mls b/hkmc2/shared/src/test/mlscript/basics/AppOp.mls index d0b974fafa..a3de36baf9 100644 --- a/hkmc2/shared/src/test/mlscript/basics/AppOp.mls +++ b/hkmc2/shared/src/test/mlscript/basics/AppOp.mls @@ -59,7 +59,7 @@ add@1@2 //│ = [[1], 2, 3] -fun test(f) = f(42) +private fun test(f) = f(42) // :dp test @ x => x + 1 diff --git a/hkmc2/shared/src/test/mlscript/basics/DataClass.mls b/hkmc2/shared/src/test/mlscript/basics/DataClass.mls index 4383e0c843..8e475fad67 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DataClass.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DataClass.mls @@ -15,7 +15,9 @@ data Foo(x: Int) //│ ╟── This annotation is not supported on application terms. //│ ║ l.13: data Foo(x: Int) //│ ╙── ^^^^^^^^^^^ -//│ ═══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ╔══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ║ l.13: data Foo(x: Int) +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls b/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls index 60c6d03d5d..77e1c3de8b 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls @@ -30,7 +30,7 @@ r! a //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Builtin '!' is not a binary operator //│ ║ l.27: r! a -//│ ╙── ^^^ +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e @@ -41,7 +41,7 @@ r ! a //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Builtin '!' is not a binary operator //│ ║ l.38: r ! a -//│ ╙── ^^^ +//│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // * Note: artifact from BbML/InvalML diff --git a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls index 1d6015100f..347e26cdac 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls @@ -120,7 +120,7 @@ sum2(..arr) //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 2 -fun map2(f) = case +private fun map2(f) = case [] then [] [x, ..xs, y] then [f(x), ..map2(f)(xs), f(y)] diff --git a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls index 1bc7f592a8..c3250d17bf 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls @@ -12,7 +12,9 @@ new Foo //│ ╟── Object 'Foo' cannot be instantiated with keyword 'new'. //│ ║ l.5: object Foo //│ ╙── ^^^ -//│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ║ l.8: new Foo +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :re @@ -22,7 +24,7 @@ new! Foo :fixme // Support? new! Foo.class //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.23: new! Foo.class +//│ ║ l.25: new! Foo.class //│ ║ ^^^^^^^^^ //│ ╟── Object 'Foo' does not contain member 'class' //│ ║ l.5: object Foo @@ -35,32 +37,34 @@ new! Foo!class :fixme // Support? object Bar extends Foo //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.36: object Bar extends Foo +//│ ║ l.38: object Bar extends Foo //│ ║ ^^^ //│ ╟── Object 'Foo' cannot be instantiated with keyword 'new'. //│ ║ l.5: object Foo //│ ╙── ^^^ -//│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ║ l.38: object Bar extends Foo +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :re object Bar extends Foo!class //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.48: object Bar extends Foo!class +//│ ║ l.52: object Bar extends Foo!class //│ ║ ^^^^^^^^^ //│ ╟── Invalid class expression: dynamic selection -//│ ║ l.48: object Bar extends Foo!class -//│ ╙── ^^^^^^ +//│ ║ l.52: object Bar extends Foo!class +//│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme // Support? object Bar extends! Foo!class //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.58: object Bar extends! Foo!class +//│ ║ l.62: object Bar extends! Foo!class //│ ║ ^^^^^^^^^^^ //│ ╟── Invalid class expression: reference -//│ ║ l.58: object Bar extends! Foo!class +//│ ║ l.62: object Bar extends! Foo!class //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls b/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls index f771e6303c..68986da21c 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls @@ -3,7 +3,7 @@ fun id(x) = x -fun foo(f, g) = f(g(1)) +private fun foo(f, g) = f(g(1)) foo(id(x => 1), id(y => 2)) @@ -25,8 +25,8 @@ foo(id of x => 1, id of y => 2) //│ ║ l.17: foo(id of x => 1, id of y => 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'foo' expected 2 arguments, but got 1 -//│ ║ l.6: fun foo(f, g) = f(g(1)) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.6: private fun foo(f, g) = f(g(1)) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'id' expected 1 argument but got 2 diff --git a/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls b/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls index 348b1a454d..d4341556d7 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls @@ -27,7 +27,7 @@ if true then () else 1 x => x, 0 //│ ╔══[WARNING] Pure expression in statement position //│ ║ l.27: x => x, 0 -//│ ╙── ^ +//│ ╙── ^^^^^^ //│ = 0 :w @@ -58,21 +58,27 @@ case x then x fun foo(x) = x x -//│ ═══[WARNING] Pure expression in statement position +//│ ╔══[WARNING] Pure expression in statement position +//│ ║ l.59: x +//│ ╙── ^ :w fun foo() = val x = 1 x x -//│ ═══[WARNING] Pure expression in statement position +//│ ╔══[WARNING] Pure expression in statement position +//│ ║ l.68: x +//│ ╙── ^ :w fun foo() = let x = 1 x x -//│ ═══[WARNING] Pure expression in statement position +//│ ╔══[WARNING] Pure expression in statement position +//│ ║ l.77: x +//│ ╙── ^ // * Note that uses of `;` currently do not warn... diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls b/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls index 0a57957cae..d645e9322a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls @@ -21,4 +21,6 @@ fun f() = while i > 0 do set i -= 1 i -//│ ═══[WARNING] Pure expression in statement position +//│ ╔══[WARNING] Pure expression in statement position +//│ ║ l.23: i +//│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls b/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls index 0c22fe05a5..014a81bee7 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls @@ -43,7 +43,7 @@ let x = 1 //│ x = 1 fun incr(n) = n + 1 -fun (|>) pipe(x, f) = f(x) +private fun (|>) pipe(x, f) = f(x) let x = x |> incr //│ x = 2 diff --git a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls index 39b592aca6..3398567997 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls @@ -112,11 +112,12 @@ fun f(x) = fun incr(n) = n + 1 -fun (|>) pipe(x, f) = f(x) +private fun (|>) pipe(x, f) = f(x) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define incr⁰ as fun incr¹(n) { //│ return +⁰(n, 1) //│ }; +//│ @private //│ define pipe⁰ as fun pipe¹(x, f) { return f(x) }; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/codegen/BuiltinSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/BuiltinSymbols.mls index dcf5543d3d..75c928c20d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BuiltinSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BuiltinSymbols.mls @@ -6,17 +6,23 @@ :ge Int -//│ ═══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ╔══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ║ l.8: Int +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge Str -//│ ═══[COMPILATION ERROR] Symbol 'Str' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ╔══[COMPILATION ERROR] Symbol 'Str' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ║ l.15: Str +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge Bool -//│ ═══[COMPILATION ERROR] Symbol 'Bool' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ╔══[COMPILATION ERROR] Symbol 'Bool' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ║ l.22: Bool +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :soir diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls b/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls index b76e96117a..bfa95e4877 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls @@ -4,7 +4,7 @@ module Mod with fun printt(x) = console.log(x) - fun (|>>) pipee(x, f) = f(x) + fun (|>>) pipee[A, B](x: A, f: A -> B): B = f(x) :ctx open Mod diff --git a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls index 4d04191368..c7c208c541 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls @@ -59,7 +59,7 @@ x => if x is (0 | 1) then 1 //│ = 1 -fun foo(f) = f(0) > 0 +private fun foo(f) = f(0) > 0 if (foo of x => x + 1) then 1 //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/codegen/Misc.mls b/hkmc2/shared/src/test/mlscript/codegen/Misc.mls index ce076fa800..2fa1771979 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Misc.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Misc.mls @@ -13,7 +13,9 @@ :ge // Shouldn't allow using `Int` in term position 1 : Int -//│ ═══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ╔══[COMPILATION ERROR] Symbol 'Int' is virtual (i.e., "compiler fiction"); cannot be used as a term +//│ ║ l.15: 1 : Int +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -66,23 +68,29 @@ bar(1, 2) // the target is JS rather than Wasm. :e wasm -//│ ═══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly +//│ ╔══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly +//│ ║ l.70: wasm +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e wasm.i32 -//│ ═══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly +//│ ╔══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly +//│ ║ l.77: wasm.i32 +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e wasm.i32.const -//│ ═══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly +//│ ╔══[COMPILATION ERROR] Module 'wasm' is virtual (i.e., "compiler fiction"); cannot be used directly +//│ ║ l.84: wasm.i32.const +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e wasm.i32.const(42) //│ ╔══[COMPILATION ERROR] WebAssembly intrinsics are not available when targeting JS -//│ ║ l.83: wasm.i32.const(42) +//│ ║ l.91: wasm.i32.const(42) //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index 98dc7aee5b..603e1c7b00 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -130,7 +130,9 @@ new t //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.129: new t //│ ║ ^^^^^ -//│ ╙── Invalid class expression: reference +//│ ╟── Invalid class expression: reference +//│ ║ l.129: new t +//│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ throw "This code cannot be run as its compilation yielded an error." //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -146,9 +148,11 @@ new! t :e new t() //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.147: new t() +//│ ║ l.149: new t() //│ ║ ^^^^^^^ -//│ ╙── Invalid class expression: reference +//│ ╟── Invalid class expression: reference +//│ ║ l.149: new t() +//│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ throw "This code cannot be run as its compilation yielded an error." //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -269,10 +273,10 @@ class Foo with val x = 2 //│ ╔══[COMPILATION ERROR] Multiple definitions of symbol 'x' //│ ╟── defined here -//│ ║ l.268: val x = 1 +//│ ║ l.272: val x = 1 //│ ║ ^^^^^^^^^ //│ ╟── defined here -//│ ║ l.269: val x = 2 +//│ ║ l.273: val x = 2 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo14; @@ -294,10 +298,10 @@ class Foo with val x = 1 let x = 2 //│ ╔══[COMPILATION ERROR] Name 'x' is already used -//│ ║ l.295: let x = 2 +//│ ║ l.299: let x = 2 //│ ║ ^^^^^ //│ ╟── by a member declared in the same block -//│ ║ l.294: val x = 1 +//│ ║ l.298: val x = 1 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo16; @@ -323,7 +327,7 @@ class Foo with :e class Foo with val x = 1 //│ ╔══[COMPILATION ERROR] Illegal body of class definition (should be a block; found term definition). -//│ ║ l.324: class Foo with val x = 1 +//│ ║ l.328: class Foo with val x = 1 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo18; diff --git a/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls b/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls index fdbcee2987..92b9aa6b93 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SingletonInit.mls @@ -25,7 +25,9 @@ module A with print(x, y) A val y = 2 -//│ ═══[WARNING] Pure expression in statement position +//│ ╔══[WARNING] Pure expression in statement position +//│ ║ l.26: A +//│ ╙── ^ //│ > 1 undefined diff --git a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls index a82e4658bc..1b291e5d24 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls @@ -102,6 +102,8 @@ new A(1, 2, 3).getR //│ ║ l.91: fun getR = r //│ ╙── ^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'r' +//│ ║ l.91: fun getR = r +//│ ║ ^ //│ ╟── which references the symbol introduced here //│ ║ l.90: data class A(...r) with //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/newres/Annotations.mls b/hkmc2/shared/src/test/mlscript/newres/Annotations.mls index d9f0b9a104..b334a084c4 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Annotations.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Annotations.mls @@ -65,7 +65,9 @@ fun shadowed() = @noInline fun inner(x) = x inner(3) -//│ ═══[WARNING] This annotation has no effect. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.65: @noInline +//│ ╙── ^^^^^^^^^ // A known application head is enough; its argument need not have a value shape. :w @@ -75,7 +77,7 @@ fun customAnnotation(arg) = fun inner() = 4 inner() //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.74: @Custom(arg) +//│ ║ l.76: @Custom(arg) //│ ╙── ^^^^^^^^^^^^ // A qualified annotation cannot wait for its receiver's forward definition. @@ -86,7 +88,7 @@ fun forwardQualified() = inner(5) module Later { object noInline } //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.84: @Later.noInline +//│ ║ l.86: @Later.noInline //│ ║ ^^^^^^^^^^^^^^^ //│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. @@ -99,7 +101,7 @@ fun forwardWildcard() = inner(6) module Future { object noInline } //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.97: @noInline +//│ ║ l.99: @noInline //│ ║ ^^^^^^^^^ //│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. @@ -110,7 +112,7 @@ fun forwardAlias() = inner(7) val alias = Later //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.108: @alias.noInline +//│ ║ l.110: @alias.noInline //│ ║ ^^^^^^^^^^^^^^^ //│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. //│ alias = class Later { noInline: noInline } @@ -126,7 +128,7 @@ fun ambiguousAnnotation() = fun inner(x) = x inner(8) //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.125: @marker +//│ ║ l.127: @marker //│ ║ ^^^^^^^ //│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. @@ -140,6 +142,6 @@ fun lateAmbiguity() = inner(9) module Last { object noInline } //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.138: @noInline +//│ ║ l.140: @noInline //│ ║ ^^^^^^^^^ //│ ╙── An annotation's main symbol must be uniquely known when the annotation is elaborated. diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 1b64f20d85..610c639795 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -71,7 +71,7 @@ fun singleton(x) = [x] // Nominal argument inference sees the tuple's Array supertype. :expect [4] class Item(val value: Int) -fun values[A](xs: Array[A]) = xs.map((item, ...) => item.value) +private fun values[A](xs: Array[A]) = xs.map((item, ...) => item.value) values([Item(4)]) //│ = [4] @@ -89,7 +89,7 @@ values([Item(4)]) //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:85 //│ ║ l.85: [1].map(x => x) -//│ ╙── ^ +//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 3 // Mutable arrays do not propagate stale initializer element shapes to callbacks. @@ -103,6 +103,7 @@ xs.map((x, ...) => x.original) //│ ║ l.101: xs.map((x, ...) => x.original) //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'original' of a value with unknown shape. +//│ ╟── The shape of this value is unknown. //│ ║ l.99: let xs = mut [Original(1)] //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls index 73ed053208..5173a0f0a1 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls @@ -32,7 +32,7 @@ class Foo { this.b } //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ class Foo⁰::Foo¹ { val b⁰::b¹ ‹flow:b⁰› = 1, Foo¹.b⁰ } +//│ class Foo⁰::Foo¹ { val b⁰::b¹ ‹flow:b⁰› = 1, Foo¹.this.b⁰ } //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define Foo² as class Foo³ { val b²; constructor() { define b² as val b³ = 1; end } }; end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls index 5da0ad0398..32632357f2 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -56,6 +56,14 @@ Predef.id //│ | | ~> member:Predef⁰.id //│ | ~> member:Predef⁰.id //│ ~> { member:Predef⁰.id } +//│ listenTerm: trm = member:Predef⁰.id +//│ listen: trm = member:Predef⁰.id +//│ listenBMS: bms = function +//│ listenedBMS: bms = function +//│ fromBMS: bms = member:id¹, sh = member:id¹, flow = ⋅id⁰, markss = List() +//│ .exit MarkedShape (member:id¹, term:Predef¹/id⁰, ⋅id⁰) +//│ = MarkedShape(member:id¹, ↗⟨term:Predef¹/id⁰⟩%⟨⋅id⁰⟩ϵ) +//│ PUSH MarkedShape(member:id¹, ↗⟨term:Predef¹/id⁰⟩%⟨⋅id⁰⟩ϵ) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ Predef⁰.id⁰ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— @@ -80,7 +88,7 @@ Predef.id(1) :e Predef.idd(1) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.81: Predef.idd(1) +//│ ║ l.89: Predef.idd(1) //│ ║ ^^^^^^^^^^ //│ ╟── Module 'Predef' does not contain member 'idd' //│ ║ in ../../mlscript-compile/Predef.mls @@ -133,30 +141,30 @@ p.id :e (1).id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.134: (1).id +//│ ║ l.142: (1).id //│ ║ ^^^^^^ //│ ╟── Integer literal does not contain member 'id' -//│ ║ l.134: (1).id +//│ ║ l.142: (1).id //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme // parse 1.id //│ ╔══[LEXICAL ERROR] Expected at least one digit after the decimal point -//│ ║ l.144: 1.id +//│ ║ l.152: 1.id //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). -//│ ║ l.144: 1.id +//│ ║ l.152: 1.id //│ ╙── ^^ //│ = 1 :fixme "ha ".repeat(2) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.154: "ha ".repeat(2) +//│ ║ l.162: "ha ".repeat(2) //│ ║ ^^^^^^^^^^^^ //│ ╟── String literal does not contain member 'repeat' -//│ ║ l.154: "ha ".repeat(2) +//│ ║ l.162: "ha ".repeat(2) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -169,9 +177,9 @@ print(1) :e ().id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.170: ().id +//│ ║ l.178: ().id //│ ║ ^^^^^ //│ ╟── Unit value does not contain member 'id' -//│ ║ l.170: ().id +//│ ║ l.178: ().id //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls index 23fb4f50fc..a0d9d5adf5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls @@ -8,15 +8,26 @@ class B { fun get() = 23 } fun identity(value) = value fun forwarded(value) = identity(value) forwarded(new A).get() + forwarded(new B).get() -//│ ╔══[COMPILATION ERROR] This selection of member 'get' is ambiguous, as it has multiple resolved targets +//│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.10: forwarded(new A).get() + forwarded(new B).get() //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ╟── target: function 'get' in class 'B' -//│ ║ l.7: class B { fun get() = 23 } -//│ ║ ^^^ -//│ ╟── target: function 'get' in class 'A' -//│ ║ l.6: class A { fun get() = 17 } -//│ ╙── ^^^ +//│ ╟── Cannot resolve member 'get' of a value with unknown shape. +//│ ╟── Parameter 'value' admits values of unknown shape. +//│ ║ l.9: fun forwarded(value) = identity(value) +//│ ║ ^^^^^ +//│ ╟── 'forwarded' is exposed by this compilation unit. +//│ ║ l.9: fun forwarded(value) = identity(value) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.10: forwarded(new A).get() + forwarded(new B).get() +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'get' of a value with unknown shape. +//│ ╟── Parameter 'value' admits values of unknown shape. +//│ ║ l.9: fun forwarded(value) = identity(value) +//│ ║ ^^^^^ +//│ ╟── 'forwarded' is exposed by this compilation unit. +//│ ║ l.9: fun forwarded(value) = identity(value) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '40', got: 'undefined' @@ -27,15 +38,22 @@ class Id { fun apply(value) = value } let id = new Id val apply = Id::apply apply(id, new A).get() + apply(id, new B).get() -//│ ╔══[COMPILATION ERROR] This selection of member 'get' is ambiguous, as it has multiple resolved targets -//│ ║ l.29: apply(id, new A).get() + apply(id, new B).get() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.40: apply(id, new A).get() + apply(id, new B).get() //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ╟── target: function 'get' in class 'B' -//│ ║ l.7: class B { fun get() = 23 } -//│ ║ ^^^ -//│ ╟── target: function 'get' in class 'A' -//│ ║ l.6: class A { fun get() = 17 } -//│ ╙── ^^^ +//│ ╟── Cannot resolve member 'get' of a value with unknown shape. +//│ ╟── Rest parameter 'args' admits elements of unknown shape. +//│ ╟── 'apply' is exposed by this compilation unit. +//│ ║ l.39: val apply = Id::apply +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.40: apply(id, new A).get() + apply(id, new B).get() +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'get' of a value with unknown shape. +//│ ╟── Rest parameter 'args' admits elements of unknown shape. +//│ ╟── 'apply' is exposed by this compilation unit. +//│ ║ l.39: val apply = Id::apply +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '40', got: 'undefined' //│ apply = fun @@ -46,14 +64,25 @@ apply(id, new A).get() + apply(id, new B).get() :expect 40 fun restIdentity(ignored, ...args) = identity(...args) restIdentity(0, new A).get() + restIdentity(0, new B).get() -//│ ╔══[COMPILATION ERROR] This selection of member 'get' is ambiguous, as it has multiple resolved targets -//│ ║ l.48: restIdentity(0, new A).get() + restIdentity(0, new B).get() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.66: restIdentity(0, new A).get() + restIdentity(0, new B).get() //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── target: function 'get' in class 'B' -//│ ║ l.7: class B { fun get() = 23 } -//│ ║ ^^^ -//│ ╟── target: function 'get' in class 'A' -//│ ║ l.6: class A { fun get() = 17 } -//│ ╙── ^^^ +//│ ╟── Cannot resolve member 'get' of a value with unknown shape. +//│ ╟── Rest parameter 'args' admits elements of unknown shape. +//│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) +//│ ║ ^^^^ +//│ ╟── 'restIdentity' is exposed by this compilation unit. +//│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.66: restIdentity(0, new A).get() + restIdentity(0, new B).get() +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'get' of a value with unknown shape. +//│ ╟── Rest parameter 'args' admits elements of unknown shape. +//│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) +//│ ║ ^^^^ +//│ ╟── 'restIdentity' is exposed by this compilation unit. +//│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '40', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls b/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls index 1de7a9e537..00fc62fffe 100644 --- a/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/CtxSens.mls @@ -44,29 +44,45 @@ fun bar(y) = //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— // :de -// :fixme +// FIXME: Forwarding through a captured function loses the caller activation. +:fixme :sr bar(A).u +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.50: bar(A).u +//│ ║ ^^^^^^^^ +//│ ╟── Cannot resolve member 'u' of a value with unknown shape. +//│ ╟── Parameter 'y' admits values of unknown shape. +//│ ║ l.40: fun bar(y) = +//│ ║ ^ +//│ ╟── 'bar' is exposed by this compilation unit. +//│ ║ l.40: fun bar(y) = +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.41: foo(y) +//│ ╙── ^^^^^^^^ //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ bar⁰(A⁰)‹app³›.u⁰ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = 1 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // * As expected, we currently lose context sensitivity when going through the call of a captured function (foo) :todo :sr bar(B).u +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.71: bar(B).u +//│ ║ ^^^^^^^^ +//│ ╟── Cannot resolve member 'u' of a value with unknown shape. +//│ ╟── Parameter 'y' admits values of unknown shape. +//│ ║ l.40: fun bar(y) = +//│ ║ ^ +//│ ╟── 'bar' is exposed by this compilation unit. +//│ ║ l.40: fun bar(y) = +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.41: foo(y) +//│ ╙── ^^^^^^^^ //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ bar⁰(B⁰)‹app⁴›.u‹u², u⁰› -//│ ╔══[COMPILATION ERROR] This selection of member 'u' is ambiguous, as it has multiple resolved targets -//│ ║ l.58: bar(B).u -//│ ║ ^^^^^^^^ -//│ ╟── target: value 'u' in module 'B' -//│ ║ l.9: val u = 2 -//│ ║ ^ -//│ ╟── target: value 'u' in module 'A' -//│ ║ l.6: val u = 1 -//│ ╙── ^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 0bd528fab5..d15f646fa8 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -77,6 +77,7 @@ read(Box(new Item)) //│ ║ l.74: fun read(box: Box) = box.item.extra //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. +//│ ╟── The shape of this value is unknown. //│ ║ l.74: fun read(box: Box) = box.item.extra //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -90,10 +91,11 @@ class Box with fun read(box: Box) = box.item().extra read(new Box) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.90: fun read(box: Box) = box.item().extra +//│ ║ l.91: fun read(box: Box) = box.item().extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ║ l.90: fun read(box: Box) = box.item().extra +//│ ╟── The shape of this value is unknown. +//│ ║ l.91: fun read(box: Box) = box.item().extra //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -139,11 +141,11 @@ read(() => Item(42)) :ge fun unused(factory: () -> Int) = factory(1) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.140: fun unused(factory: () -> Int) = factory(1) +//│ ║ l.142: fun unused(factory: () -> Int) = factory(1) //│ ║ ^^^^^^^^^^ //│ ╟── Function with a declared signature expected 0 arguments, but got 1 -//│ ║ l.140: fun unused(factory: () -> Int) = factory(1) -//│ ╙── ^^ +//│ ║ l.142: fun unused(factory: () -> Int) = factory(1) +//│ ╙── ^^^^^^^^^ // A callable annotation also hides extra members of the concrete result. :ge @@ -153,7 +155,7 @@ class Child extends Base with fun read(factory: () -> Base) = factory().extra read(() => new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.153: fun read(factory: () -> Base) = factory().extra +//│ ║ l.155: fun read(factory: () -> Base) = factory().extra //│ ║ ^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -167,7 +169,7 @@ fun hidden: (Base) -> Int fun hidden(base) = base.extra hidden(new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.167: fun hidden(base) = base.extra +//│ ║ l.169: fun hidden(base) = base.extra //│ ║ ^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -181,7 +183,7 @@ fun make: () -> Base fun make() = new Child make().extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.182: make().extra +//│ ║ l.184: make().extra //│ ║ ^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -206,15 +208,16 @@ class Box[T] type Loop[T] = Loop[Box[T]] fun unused(value: Loop[Int]) = value.field //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.207: fun unused(value: Loop[Int]) = value.field +//│ ║ l.209: fun unused(value: Loop[Int]) = value.field //│ ║ ^^^^^^^^^^^ //│ ╟── Cannot resolve member 'field' of a value with unknown shape. -//│ ║ l.207: fun unused(value: Loop[Int]) = value.field +//│ ╟── This abstract type does not specify a member interface. +//│ ║ l.209: fun unused(value: Loop[Int]) = value.field //│ ╙── ^^^^^^^^^ // Type arguments provide callable shapes through the generic parameter. :expect 42 -fun invoke[T](value: T) = value() +private fun invoke[T](value: T) = value() invoke[() -> Int](() => 42) //│ = 42 @@ -226,10 +229,11 @@ class Box(val item) fun read(box: Box) = box.Box#item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.226: fun read(box: Box) = box.Box#item.extra +//│ ║ l.229: fun read(box: Box) = box.Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ║ l.226: fun read(box: Box) = box.Box#item.extra +//│ ╟── The shape of this value is unknown. +//│ ║ l.229: fun read(box: Box) = box.Box#item.extra //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -259,7 +263,7 @@ class Child extends Base with fun identity[T](x: T): T = x identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.260: identity[Base](new Child).extra +//│ ║ l.264: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -301,11 +305,11 @@ class Box[T](val value: T) (new Box[Base](new Child)).value.extra Box[Base](new Child).value.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.301: (new Box[Base](new Child)).value.extra +//│ ║ l.305: (new Box[Base](new Child)).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.302: Box[Base](new Child).value.extra +//│ ║ l.306: Box[Base](new Child).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -348,10 +352,11 @@ class Box with val item = new Item (new Box as Box).Box#item.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.349: (new Box as Box).Box#item.extra +//│ ║ l.353: (new Box as Box).Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ║ l.349: (new Box as Box).Box#item.extra +//│ ╟── The shape of this value is unknown. +//│ ║ l.353: (new Box as Box).Box#item.extra //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -365,10 +370,11 @@ module Types with fun hidden(module types: Types) = types.Item.extra hidden(Types) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.365: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.370: fun hidden(module types: Types) = types.Item.extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ║ l.365: fun hidden(module types: Types) = types.Item.extra +//│ ╟── The shape of this value is unknown. +//│ ║ l.370: fun hidden(module types: Types) = types.Item.extra //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -387,7 +393,7 @@ class Child extends Base with fun apply[A](f: A -> A, x: A): A = f(x) apply[Base](arg => (arg.extra; arg), new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.388: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ l.394: apply[Base](arg => (arg.extra; arg), new Child) //│ ║ ^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -410,7 +416,7 @@ class Child extends Base with fun read(xs: [Base, Int]) = xs.0.extra read([new Child, 0]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.410: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ l.416: fun read(xs: [Base, Int]) = xs.0.extra //│ ║ ^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -431,10 +437,11 @@ let xs = mut [First(1)] set xs.0 = Second(2) [...xs].0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.432: [...xs].0.first +//│ ║ l.438: [...xs].0.first //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'first' of a value with unknown shape. -//│ ║ l.430: let xs = mut [First(1)] +//│ ╟── The shape of this value is unknown. +//│ ║ l.436: let xs = mut [First(1)] //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -443,10 +450,10 @@ set xs.0 = Second(2) :ge [1, 2].2 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.444: [1, 2].2 +//│ ║ l.451: [1, 2].2 //│ ║ ^^^^^^^^ //│ ╟── Tuple literal does not contain member '2' -//│ ║ l.444: [1, 2].2 +//│ ║ l.451: [1, 2].2 //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls index 268a574c42..33d33118c3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls @@ -49,7 +49,7 @@ identity({value: 7} as dyn).value // Dynamic spreads retain dynamic element values through parameter propagation. :expect 8 -fun readSpread(x) = x.value +fun readSpread(x: dyn) = x.value readSpread(...([{value: 8}] as dyn)) //│ = 8 diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 97df063852..600e14eb31 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -1,7 +1,7 @@ :. -fun foo(x) = x + 1 +private fun foo(x) = x + 1 :e foo() @@ -9,8 +9,8 @@ foo() //│ ║ l.7: foo() //│ ║ ^^^^^ //│ ╟── Function 'foo' expected 1 argument, but got 0 -//│ ║ l.4: fun foo(x) = x + 1 -//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ ║ l.4: private fun foo(x) = x + 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'foo' expected 1 argument but got 0 :e @@ -19,8 +19,8 @@ foo(1, 2) //│ ║ l.17: foo(1, 2) //│ ║ ^^^^^^^^^ //│ ╟── Function 'foo' expected 1 argument, but got 2 -//│ ║ l.4: fun foo(x) = x + 1 -//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ ║ l.4: private fun foo(x) = x + 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'foo' expected 1 argument but got 2 @@ -34,16 +34,23 @@ id :e fun callid0(p) = p.id(1) -//│ ╔══[COMPILATION ERROR] This selection of member 'id' has no resolved target +//│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.36: fun callid0(p) = p.id(1) -//│ ╙── ^^^^ +//│ ║ ^^^^ +//│ ╟── Cannot resolve member 'id' of a value with unknown shape. +//│ ╟── Parameter 'p' admits values of unknown shape. +//│ ║ l.36: fun callid0(p) = p.id(1) +//│ ║ ^ +//│ ╟── 'callid0' is exposed by this compilation unit. +//│ ║ l.36: fun callid0(p) = p.id(1) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ :de :sr -fun callid1(p) = p.id(1) +private fun callid1(p) = p.id(1) callid1(Predef) -//│ Elab block List(TermDef(Fun,App(Ident(callid1),Tup(List(Ident(p)))),Some(App(Sel(Ident(p),Ident(id)),Tup(List(I[...] LocalScope(block:7) +//│ Elab block List(Annotated(Keywrd(keyword 'private'),TermDef(Fun,App(Ident(callid1),Tup(List(Ident(p)))),Some(Ap[...] LocalScope(block:7) //│ | Processing term definition App(Ident(callid1),Tup(List(Ident(p)))) //│ | Elaborating ParamTree: ParamTree(‹›,Ident(p),None,None,Set()) //│ | Elab term App(Sel(Ident(p),Ident(id)),Tup(List(IntLit(1)))) @@ -179,17 +186,19 @@ callid1(Predef) //│ | | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | = MarkedShape(IntroShape(1), ↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ ~> { fun member:callid1(p⁰) = p⁰.id(1); member:callid1⁰(member:Predef⁰) } +//│ listenTerm: trm = member:callid1⁰(member:Predef⁰) +//│ listen: trm = member:callid1⁰(member:Predef⁰) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun callid1⁰::callid1¹(p⁰) ‹flow:callid1⁰› = p⁰.id⁰(1)‹app⁰›, callid1⁰(Predef⁰)‹app¹› +//│ @private fun callid1⁰::callid1¹(p⁰) ‹flow:callid1⁰› = p⁰.id⁰(1)‹app⁰›, callid1⁰(Predef⁰)‹app¹› //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 :sr callid2(Predef) -fun callid2(p) = p.id(1) +private fun callid2(p) = p.id(1) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ callid2⁰(Predef⁰)‹app²›, fun callid2⁰::callid2¹(p¹) ‹flow:callid2⁰› = p¹.id⁰(1)‹app³› +//│ callid2⁰(Predef⁰)‹app²›, @private fun callid2⁰::callid2¹(p¹) ‹flow:callid2⁰› = p¹.id⁰(1)‹app³› //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -205,12 +214,12 @@ getPredef().id // :de :sr -fun foo(pred) = +private fun foo(pred) = let p = pred val r = p.id(1) foo(Predef) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun foo⁰::foo¹(pred⁰) ‹flow:foo⁰› = { +//│ @private fun foo⁰::foo¹(pred⁰) ‹flow:foo⁰› = { //│ let p², //│ p² = pred⁰, //│ val r⁰::r¹ ‹flow:r⁰› = p²^r¹.id⁰(1)‹app⁵›, diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index d96a66bb43..1cf6f47a3d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -135,4 +135,4 @@ module Local with class Known(val known: Int) val mapped = [Known(4)].map((item, ...) => Known(item.known + 1)) Local.mapped.map((item, ...) => item.known) -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: term:Local⁰/mapped⁰ vs term:Array⁰/map⁰ for NominalTypeShape(class:Int⁰) diff --git a/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls b/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls index 701d696cd3..bae7e7bdfe 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Inheritance.mls @@ -18,7 +18,7 @@ class Bar(y) extends Foo(y + 1) { //│ fun Bar⁰ //│ class Bar¹::Bar²(y⁰) extends new Foo¹^Bar²(+(y⁰, 1)‹app⁰›) { //│ @private val y¹::y² ‹flow:y⁰› = y⁰^y², -//│ val z⁰::z¹ ‹flow:z⁰› = +(Bar².x¹, 1)‹app¹› +//│ val z⁰::z¹ ‹flow:z⁰› = +(Bar².this^z¹.x¹, 1)‹app¹› //│ } //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Bar1; diff --git a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls new file mode 100644 index 0000000000..0cd2a089e3 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls @@ -0,0 +1,371 @@ +:. + +// Observed calls cannot close an exposed unannotated parameter. +:e +fun foo(x) = x.a +foo({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.5: fun foo(x) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.5: fun foo(x) = x.a +//│ ║ ^ +//│ ╟── 'foo' is exposed by this compilation unit. +//│ ║ l.5: fun foo(x) = x.a +//│ ╙── ^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Nor can a type parameter acquire an interface from local instantiations. +:e +fun foo[A](x: A) = x.a +foo({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.21: fun foo[A](x: A) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.21: fun foo[A](x: A) = x.a +//│ ║ ^ +//│ ╟── 'foo' is exposed by this compilation unit. +//│ ║ l.21: fun foo[A](x: A) = x.a +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:expect 1 +private fun foo(x) = x.a +foo({a: 1}) +//│ = 1 + +:expect 1 +class A(val a: Int) +fun foo(x: A) = x.a +foo(new A(1)) +//│ = 1 + +// An unknown callable alternative is an error despite a known local call. +:e +fun apply(f) = f(1) +apply(x => x) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.48: fun apply(f) = f(1) +//│ ║ ^^^^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.48: fun apply(f) = f(1) +//│ ║ ^ +//│ ╟── Parameter 'f' admits values of unknown shape. +//│ ║ l.48: fun apply(f) = f(1) +//│ ║ ^ +//│ ╟── 'apply' is exposed by this compilation unit. +//│ ║ l.48: fun apply(f) = f(1) +//│ ╙── ^^^^^^^^^^^^^^^^^^^ +//│ = 1 + +// A declared callable interface admits calls. +:expect 1 +fun apply(f: Int -> Int) = f(1) +apply(x => x) +//│ = 1 + +// Exposure follows returned closures and aggregate elements. +:e +fun escaped() = x => x.a +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.72: fun escaped() = x => x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.72: fun escaped() = x => x.a +//│ ║ ^ +//│ ╟── This value is returned here. +//│ ║ l.72: fun escaped() = x => x.a +//│ ║ ^^^^^^^^ +//│ ╟── 'escaped' is exposed by this compilation unit. +//│ ║ l.72: fun escaped() = x => x.a +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ + +:e +private fun hidden(x) = x.a +val escaped = [hidden] +hidden({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.88: private fun hidden(x) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.88: private fun hidden(x) = x.a +//│ ║ ^ +//│ ╟── This value is stored in this tuple. +//│ ║ l.89: val escaped = [hidden] +//│ ║ ^^^^^^^^ +//│ ╟── 'escaped' is exposed by this compilation unit. +//│ ║ l.89: val escaped = [hidden] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ escaped = [fun hidden] + +:e +private fun hidden(x) = x.a +val escaped = {f: hidden} +hidden({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.108: private fun hidden(x) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.108: private fun hidden(x) = x.a +//│ ║ ^ +//│ ╟── This value is stored in this record field. +//│ ║ l.109: val escaped = {f: hidden} +//│ ║ ^ +//│ ╟── 'escaped' is exposed by this compilation unit. +//│ ║ l.109: val escaped = {f: hidden} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ escaped = {f: fun hidden} + +// Calling a private helper does not expose arbitrary inputs to it. +:expect 1 +private fun hidden(x) = x.a +fun closed() = hidden({a: 1}) +closed() +//│ = 1 + +// An annotated returned callback is checked under its declared domain. +:expect 1 +class A(val a: Int) +fun typed(): A -> Int = x => x.a +typed()(new A(1)) +//│ = 1 + +// The diagnostic retains the parameter, field storage, and returned record. +:e +private fun hiddenRecord(x) = x.a +fun exportRecord() = {callback: hiddenRecord} +hiddenRecord({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.143: private fun hiddenRecord(x) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.143: private fun hiddenRecord(x) = x.a +//│ ║ ^ +//│ ╟── This value is stored in this record field. +//│ ║ l.144: fun exportRecord() = {callback: hiddenRecord} +//│ ║ ^^^^^^^^ +//│ ╟── This value is returned here. +//│ ║ l.144: fun exportRecord() = {callback: hiddenRecord} +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── 'exportRecord' is exposed by this compilation unit. +//│ ║ l.144: fun exportRecord() = {callback: hiddenRecord} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Exposing a constructor must not pollute separately constructed instances. +// FIXME: Rebuilding the same class loses the original constructor activation. +:fixme +:expect 42 +class CopyBox(val value) with + fun copy() = CopyBox(value) +CopyBox({answer: 42}).copy().value.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.170: CopyBox({answer: 42}).copy().value.answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── Parameter 'value' admits values of unknown shape. +//│ ║ l.168: class CopyBox(val value) with +//│ ║ ^^^^^ +//│ ╟── 'CopyBox' is exposed by this compilation unit. +//│ ║ l.168: class CopyBox(val value) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.169: fun copy() = CopyBox(value) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' + +// A previously compiled private definition cannot acquire an open interface later. +private fun oldHidden(x) = x.a +oldHidden({a: 1}) +//│ = 1 + +:e +val oldEscape = {callback: oldHidden} +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.187: private fun oldHidden(x) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.187: private fun oldHidden(x) = x.a +//│ ║ ^ +//│ ╟── This value is stored in this record field. +//│ ║ l.192: val oldEscape = {callback: oldHidden} +//│ ║ ^^^^^^^^ +//│ ╟── 'oldEscape' is exposed by this compilation unit. +//│ ║ l.192: val oldEscape = {callback: oldHidden} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ oldEscape = {callback: fun oldHidden} + +// A spread retains the generic input that made its member interface unknown. +:e +fun spreadRecord[A](x: A) = ({...x}).a +spreadRecord({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.210: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. +//│ ╟── This spread has no known element shape. +//│ ║ l.210: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ ^ +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.210: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ ^ +//│ ╟── 'spreadRecord' is exposed by this compilation unit. +//│ ║ l.210: fun spreadRecord[A](x: A) = ({...x}).a +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// The same provenance survives indexing into an unresolved tuple spread. +:e +fun spreadTuple[A](x: A) = [...x].0.a +spreadTuple([{a: 1}]) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.229: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This spread has no known element shape. +//│ ║ l.229: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ ^ +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.229: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ ^ +//│ ╟── 'spreadTuple' is exposed by this compilation unit. +//│ ║ l.229: fun spreadTuple[A](x: A) = [...x].0.a +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Explicit dynamic spreads still permit dynamic element operations. +:expect 3 +[...([{a: 3}] as dyn)].0.a +//│ = 3 + +// Recursive discovery observes later shapes without repeatedly scanning the graph. +:e +private fun recursiveHidden(x) = x.a +fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +recursiveHidden({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.253: private fun recursiveHidden(x) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.253: private fun recursiveHidden(x) = x.a +//│ ║ ^ +//│ ╟── This value is stored in this record field. +//│ ║ l.254: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ ^^^^^^^^ +//│ ╟── This value is returned here. +//│ ║ l.254: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── 'recursiveExport' is exposed by this compilation unit. +//│ ║ l.254: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A term overload cannot hide an exposed class interface from this check. +:e +class Shadowed { + fun bad(x) = x.a +} +val Shadowed = 0 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.277: fun bad(x) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.277: fun bad(x) = x.a +//│ ║ ^ +//│ ╟── Member 'bad' is accessible here. +//│ ║ l.277: fun bad(x) = x.a +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── 'Shadowed' is exposed by this compilation unit. +//│ ║ l.276: class Shadowed { +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ║ l.277: fun bad(x) = x.a +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ║ l.278: } +//│ ║ ^ +//│ ║ l.279: val Shadowed = 0 +//│ ╙── ^^^^^^^^^^^^^^^^ +//│ Shadowed = 0 + +// A constructor test narrows the class, but cannot close its unannotated fields. +:e +class Holder(val value) +fun inspect(x) = if x is Holder(y) then y.a else 0 +inspect(Holder({a: 1})) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.304: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This constructor field may contain a value of unknown shape. +//│ ║ l.303: class Holder(val value) +//│ ║ ^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.304: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ ^ +//│ ╟── 'inspect' is exposed by this compilation unit. +//│ ║ l.304: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Declared fields remain usable after narrowing an unknown input. +:expect 2 +class KnownField(val a: Int) +class TypedHolder(val value: KnownField) +fun inspectTyped(x) = if x is TypedHolder(y) then y.a else 0 +inspectTyped(TypedHolder(KnownField(2))) +//│ = 2 + +// A class pattern also admits arbitrary instantiations of its type parameters. +:e +class GenericHolder[A](val value: A) +fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +inspectGeneric(GenericHolder({a: 1})) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.332: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This constructor field may contain a value of unknown shape. +//│ ║ l.331: class GenericHolder[A](val value: A) +//│ ║ ^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.332: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ ^ +//│ ╟── 'inspectGeneric' is exposed by this compilation unit. +//│ ║ l.332: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Narrowing does not mix the external activation into concrete calls. +:expect 3 +val unwrap = case Holder(y) then y +unwrap(Holder({a: 3})).a +//│ = 3 +//│ unwrap = fun + +// Abstract annotations still admit a class test and its declared field interface. +:expect 4 +fun inspectAbstract(x: Any) = if x is TypedHolder(y) then y.a else 0 +inspectAbstract(TypedHolder(KnownField(4))) +//│ = 4 + +// A result annotation does not hide callable methods of a returned private class. +// FIXME: Follow the declared member interface to its implementation without +// exposing initializer shapes hidden by the nominal annotation. +:breakme +:e +private class EscapedClass with + fun inspect(x) = x.a +fun annotatedInstance(): EscapedClass = new EscapedClass +(new EscapedClass).inspect({a: 1}) +//│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/Opens.mls b/hkmc2/shared/src/test/mlscript/newres/Opens.mls index 17964ff260..666990c39c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Opens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Opens.mls @@ -282,7 +282,9 @@ new Shadowed //│ ╟── Integer literal cannot be instantiated with keyword 'new'. //│ ║ l.276: val Shadowed = 20 //│ ╙── ^^ -//│ ═══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ║ l.278: new Shadowed +//│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ Shadowed = 20 @@ -311,19 +313,19 @@ fun ambiguousReversed() = { } ambiguousReversed() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous -//│ ║ l.310: extra +//│ ║ l.312: extra //│ ║ ^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.38: module Selected { val extra = 6 } //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.308: open Selected +//│ ║ l.310: open Selected //│ ║ ^^^^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.8: val extra = 3 //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.309: open Values +//│ ║ l.311: open Values //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -380,18 +382,18 @@ fun inheritedAmbiguity() = { } inheritedAmbiguity() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous -//│ ║ l.378: fun nested() = extra +//│ ║ l.380: fun nested() = extra //│ ║ ^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.8: val extra = 3 //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.376: open Values +//│ ║ l.378: open Values //│ ║ ^^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.38: module Selected { val extra = 6 } //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.377: open Selected +//│ ║ l.379: open Selected //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls index 6755e0d064..1b06d55072 100644 --- a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls @@ -96,16 +96,14 @@ class Aux { } fun Aux(x) = new Aux(x + 1) -// FIXME: constructor-field references are not yet supported by the new resolver. -:fixme +// Constructor fields retain the selected constructor and its capture path. :expect 42 Aux(41).answer() -//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +//│ = 42 -:fixme :expect 41 (new Aux(41)).answer() -//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +//│ = 41 class Parent { @@ -178,20 +176,20 @@ MutableMembers.Item class Parameterized() fun Parameterized() = 0 //│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Parameterized' -//│ ║ l.179: fun Parameterized() = 0 +//│ ║ l.177: fun Parameterized() = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── with class of the same name -//│ ║ l.178: class Parameterized() +//│ ║ l.176: class Parameterized() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ :e class ParameterizedValue(x) val ParameterizedValue = 0 //│ ╔══[COMPILATION ERROR] Illegal overloading of value 'ParameterizedValue' -//│ ║ l.189: val ParameterizedValue = 0 +//│ ║ l.187: val ParameterizedValue = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── with class of the same name -//│ ║ l.188: class ParameterizedValue(x) +//│ ║ l.186: class ParameterizedValue(x) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ParameterizedValue = 0 @@ -201,10 +199,10 @@ class Duplicate fun Duplicate() = 0 val Duplicate = 1 //│ ╔══[COMPILATION ERROR] Illegal overloading of value 'Duplicate' -//│ ║ l.202: val Duplicate = 1 +//│ ║ l.200: val Duplicate = 1 //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── with function of the same name -//│ ║ l.201: fun Duplicate() = 0 +//│ ║ l.199: fun Duplicate() = 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls index c99c25c511..08543f0fe1 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls @@ -159,11 +159,11 @@ get :sr -fun foo(x) = +private fun foo(x) = x.a + x.b foo(f) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun foo⁰::foo¹(x⁰) ‹flow:foo⁰› = +(x⁰.a⁴, x⁰.b⁰)‹app⁶›, foo⁰(f²)‹app⁷› +//│ @private fun foo⁰::foo¹(x⁰) ‹flow:foo⁰› = +(x⁰.a⁴, x⁰.b⁰)‹app⁶›, foo⁰(f²)‹app⁷› //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 3 @@ -272,14 +272,14 @@ new 42 // :de :sr :sjs -// class Foo(pred) { // TODO -class Foo(pred) with { +// private class Foo(pred) { // TODO +private class Foo(pred) with { // val r = pred.id(42) pred.id(42) } let f = new Foo(Predef) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun Foo⁶ +//│ @private fun Foo⁶ //│ class Foo⁷::Foo⁸(pred⁰) { //│ @private val pred¹::pred² ‹flow:pred⁰› = pred⁰^pred², //│ Foo⁸.this.pred¹.id⁰(42)‹app⁹› @@ -304,13 +304,13 @@ let f = new Foo(Predef) :sr -class Foo(pred) { +private class Foo(pred) { pred.id(1) pred.id(2) } let f = Foo(Predef) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun Foo⁹ +//│ @private fun Foo⁹ //│ class Foo¹⁰::Foo¹¹(pred³) { //│ @private val pred⁴::pred⁵ ‹flow:pred¹› = pred³^pred⁵, //│ Foo¹¹.this.pred⁴.id⁰(1)‹app¹⁰›, @@ -322,13 +322,13 @@ let f = Foo(Predef) //│ f = Foo(_) :sr -class Foo(pred) { +private class Foo(pred) { val r = pred.id(42) fun callId(x) = pred.id(x) } let f = new Foo(Predef) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun Foo¹² +//│ @private fun Foo¹² //│ class Foo¹³::Foo¹⁴(pred⁶) { //│ @private val pred⁷::pred⁸ ‹flow:pred²› = pred⁶^pred⁸, //│ val r⁰::r¹ ‹flow:r⁰› = Foo¹⁴.this.pred⁷^r¹.id⁰(42)‹app¹³›, diff --git a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls index 7b257f5ee8..9fbbc8d864 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls @@ -99,9 +99,23 @@ fun test(x) = if x is Foo(y) then y.b test(bz) -//│ ╔══[COMPILATION ERROR] This selection of member 'b' has no resolved target +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.100: Foo(y) then y.b +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'b' of a value with unknown shape. +//│ ╟── This constructor field may contain a value of unknown shape. +//│ ║ l.5: class Foo(val a) +//│ ║ ^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.98: fun test(x) = +//│ ║ ^ +//│ ╟── 'test' is exposed by this compilation unit. +//│ ║ l.98: fun test(x) = +//│ ║ ^^^^^^^^^^^^^ +//│ ║ l.99: if x is +//│ ║ ^^^^^^^^^ //│ ║ l.100: Foo(y) then y.b -//│ ╙── ^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -117,7 +131,7 @@ let x = if maybe then Bar else bz :e x.b //│ ╔══[COMPILATION ERROR] This selection of member 'b' is ambiguous, as it has multiple resolved targets -//│ ║ l.118: x.b +//│ ║ l.132: x.b //│ ║ ^^^ //│ ╟── target: function 'b' in overloaded function and class 'Baz' //│ ║ l.7: class Baz(c) { fun b = c } @@ -158,9 +172,19 @@ if Foo(Predef) is :todo val f = case Foo(y) then y.b -//│ ╔══[COMPILATION ERROR] This selection of member 'b' has no resolved target -//│ ║ l.160: Foo(y) then y.b -//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.174: Foo(y) then y.b +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'b' of a value with unknown shape. +//│ ╟── This constructor field may contain a value of unknown shape. +//│ ║ l.5: class Foo(val a) +//│ ║ ^ +//│ ╟── Parameter 'caseScrut' admits values of unknown shape. +//│ ╟── 'f' is exposed by this compilation unit. +//│ ║ l.173: val f = case +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.174: Foo(y) then y.b +//│ ╙── ^^^^^^^^^^^^^^^^^ //│ f = fun diff --git a/hkmc2/shared/src/test/mlscript/newres/Projections.mls b/hkmc2/shared/src/test/mlscript/newres/Projections.mls index 0b88dca567..19caf972cc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Projections.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Projections.mls @@ -51,16 +51,18 @@ fun missing(a) = a.Later#missing() //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.50: fun missing(a) = a.Later#missing() //│ ║ ^^^^^^^^^^^^^^^ -//│ ╙── Class 'Later' does not contain member 'missing' +//│ ╟── Class 'Later' does not contain member 'missing' +//│ ║ l.7: class Later(val x) { fun get() = x } +//│ ╙── ^ :e val NotClass = 1 fun invalid(a) = a.NotClass#get() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.58: fun invalid(a) = a.NotClass#get() +//│ ║ l.60: fun invalid(a) = a.NotClass#get() //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Integer literal cannot be used as a projection class. -//│ ║ l.57: val NotClass = 1 +//│ ║ l.59: val NotClass = 1 //│ ╙── ^ //│ NotClass = 1 @@ -72,23 +74,34 @@ open Left open Right fun ambiguous(a) = a.Item#get() //│ ╔══[COMPILATION ERROR] The projection class is ambiguous -//│ ║ l.73: fun ambiguous(a) = a.Item#get() +//│ ║ l.75: fun ambiguous(a) = a.Item#get() //│ ║ ^^^^^ //│ ╟── class: 'Item' -//│ ║ l.70: module Right { class Item extends Parent {} } +//│ ║ l.72: module Right { class Item extends Parent {} } //│ ║ ^^^^ //│ ╟── class: 'Item' -//│ ║ l.69: module Left { class Item extends Parent {} } +//│ ║ l.71: module Left { class Item extends Parent {} } //│ ╙── ^^^^ // Forwarded arguments carry shapes through the projected method's result. +// FIXME: The projected wrapper loses the activation of its exposed argument. +:fixme :expect 17 class Identity { fun apply(x) = x } class Answer { fun get() = 17 } let identity = new Identity val apply = Identity::apply apply(identity, new Answer).get() -//│ = 17 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.94: apply(identity, new Answer).get() +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'get' of a value with unknown shape. +//│ ╟── Rest parameter 'args' admits elements of unknown shape. +//│ ╟── 'apply' is exposed by this compilation unit. +//│ ║ l.93: val apply = Identity::apply +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '17', got: 'undefined' //│ apply = fun //│ identity = Identity @@ -123,10 +136,10 @@ open first open second fun sameClass(value) = value.Entry#get() //│ ╔══[COMPILATION ERROR] The projection class is ambiguous -//│ ║ l.124: fun sameClass(value) = value.Entry#get() +//│ ║ l.137: fun sameClass(value) = value.Entry#get() //│ ║ ^^^^^^ //│ ╟── class: 'Entry' -//│ ║ l.119: class Library { class Entry { fun get() = 1 } } +//│ ║ l.132: class Library { class Entry { fun get() = 1 } } //│ ╙── ^^^^^ //│ first = Library { Entry: class Entry } //│ second = Library { Entry: class Entry } @@ -134,17 +147,17 @@ fun sameClass(value) = value.Entry#get() :e fun missingClass(value) = value.Unknown#get() //│ ╔══[COMPILATION ERROR] Cannot resolve the projection class -//│ ║ l.135: fun missingClass(value) = value.Unknown#get() +//│ ║ l.148: fun missingClass(value) = value.Unknown#get() //│ ╙── ^^^^^^^^ :e val instance = new Parent fun instanceAsClass(value) = value.instance#get() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.142: fun instanceAsClass(value) = value.instance#get() +//│ ║ l.155: fun instanceAsClass(value) = value.instance#get() //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Parent' cannot be used as a projection class. -//│ ║ l.141: val instance = new Parent +//│ ║ l.154: val instance = new Parent //│ ╙── ^^^^^^^^^^ //│ instance = Parent @@ -181,10 +194,10 @@ fun invalidAlias(value) = value.AliasToNumber#get() val SourceNumber = 123 val AliasToNumber = SourceNumber //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.180: fun invalidAlias(value) = value.AliasToNumber#get() +//│ ║ l.193: fun invalidAlias(value) = value.AliasToNumber#get() //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Integer literal cannot be used as a projection class. -//│ ║ l.181: val SourceNumber = 123 +//│ ║ l.194: val SourceNumber = 123 //│ ╙── ^^^ //│ AliasToNumber = 123 //│ SourceNumber = 123 diff --git a/hkmc2/shared/src/test/mlscript/newres/Records.mls b/hkmc2/shared/src/test/mlscript/newres/Records.mls index 7274b4d30b..0199158bc5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Records.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Records.mls @@ -44,7 +44,7 @@ wrap({right: 8}).value.right // Reading the same field through a helper retains the receiver context too. :expect 9 -fun read(r) = r.value +private fun read(r) = r.value read(wrap({answer: 9})).answer //│ = 9 @@ -94,8 +94,9 @@ let key = "a" //│ ║ l.92: {a: {answer: 0}, (key): {other: 1}}.a.answer //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. +//│ ╟── This computed key can overwrite the selected member. //│ ║ l.92: {a: {answer: 0}, (key): {other: 1}}.a.answer -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target //│ ║ l.92: {a: {answer: 0}, (key): {other: 1}}.a.answer //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -150,10 +151,11 @@ let changing = mut {value: {before: 0}} set changing.value = {after: 21} changing.value.after //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.151: changing.value.after +//│ ║ l.152: changing.value.after //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'after' of a value with unknown shape. -//│ ║ l.149: let changing = mut {value: {before: 0}} +//│ ╟── The shape of this value is unknown. +//│ ║ l.150: let changing = mut {value: {before: 0}} //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ changing = {value: {after: 21}} @@ -166,16 +168,16 @@ fun nestedWrap(x) = {...wrap(x)} nestedWrap({first: 22}).value.first nestedWrap({second: 23}).value.second //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.166: nestedWrap({first: 22}).value.first +//│ ║ l.168: nestedWrap({first: 22}).value.first //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'first' -//│ ║ l.167: nestedWrap({second: 23}).value.second +//│ ║ l.169: nestedWrap({second: 23}).value.second //│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.167: nestedWrap({second: 23}).value.second +//│ ║ l.169: nestedWrap({second: 23}).value.second //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'second' -//│ ║ l.166: nestedWrap({first: 22}).value.first +//│ ║ l.168: nestedWrap({first: 22}).value.first //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -191,11 +193,12 @@ fun unknownOpen() = a unknownOpen() //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.191: a +//│ ║ l.193: a //│ ║ ^ //│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. -//│ ║ l.186: let unknownKeys = {(key): 0} -//│ ╙── ^ +//│ ╟── This computed key can overwrite the selected member. +//│ ║ l.188: let unknownKeys = {(key): 0} +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ unknownKeys = {a: 0} @@ -217,13 +220,13 @@ forwardRecord.answer fun choose(flag) = if flag then {answer: 1} else {answer: 2} choose(true).answer //│ ╔══[COMPILATION ERROR] This selection of member 'answer' is ambiguous, as it has multiple resolved targets -//│ ║ l.218: choose(true).answer +//│ ║ l.221: choose(true).answer //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── target: record field 'answer' -//│ ║ l.217: fun choose(flag) = if flag then {answer: 1} else {answer: 2} +//│ ║ l.220: fun choose(flag) = if flag then {answer: 1} else {answer: 2} //│ ║ ^^^^^^ //│ ╟── target: record field 'answer' -//│ ║ l.217: fun choose(flag) = if flag then {answer: 1} else {answer: 2} +//│ ║ l.220: fun choose(flag) = if flag then {answer: 1} else {answer: 2} //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -235,10 +238,11 @@ fun recursiveRecord(n) = else {...recursiveRecord(n - 1)} recursiveRecord(2).answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.236: recursiveRecord(2).answer +//│ ║ l.239: recursiveRecord(2).answer //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' across a computed key or unknown record spread. -//│ ║ l.235: else {...recursiveRecord(n - 1)} +//│ ╟── This spread has no known element shape. +//│ ║ l.238: else {...recursiveRecord(n - 1)} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -254,10 +258,11 @@ fun unknownValueOpen() = answer3 unknownValueOpen() //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.254: answer3 +//│ ║ l.258: answer3 //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'answer3' of a value with unknown shape. -//│ ║ l.249: let unknownValue = mut {value: {answer3: 0}} +//│ ╟── The shape of this value is unknown. +//│ ║ l.253: let unknownValue = mut {value: {answer3: 0}} //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -281,7 +286,7 @@ fun allMissingOpen() = nonexistent allMissingOpen() //│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: nonexistent -//│ ║ l.281: nonexistent +//│ ║ l.286: nonexistent //│ ╙── ^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -291,7 +296,7 @@ allMissingOpen() // Treating the second expression as already processed would skip its subscription // to xs, leaving b.value unresolved. :expect [29, 29] -fun repeatedRecords(xs) = +private fun repeatedRecords(xs) = let a = {...xs} let b = {...xs} [a.value, b.value] @@ -327,10 +332,11 @@ let mutableNested = mut {...stableNested} set mutableNested.value = {after: 34} [stableNested.value.before, mutableNested.value.after] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.328: [stableNested.value.before, mutableNested.value.after] +//│ ║ l.333: [stableNested.value.before, mutableNested.value.after] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'after' of a value with unknown shape. -//│ ║ l.325: let stableNested = {value: {before: 33}} +//│ ╟── The shape of this value is unknown. +//│ ║ l.330: let stableNested = {value: {before: 33}} //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ mutableNested = {value: {after: 34}} @@ -347,18 +353,18 @@ fun ambiguousRecordOpen() = shared ambiguousRecordOpen() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'shared' is ambiguous -//│ ║ l.347: shared +//│ ║ l.353: shared //│ ║ ^^^^^^ //│ ╟── candidate: record field 'shared' defined here -//│ ║ l.343: let firstRecord = {shared: 1} +//│ ║ l.349: let firstRecord = {shared: 1} //│ ║ ^^^^^^ //│ ╟── Opened here -//│ ║ l.345: open firstRecord +//│ ║ l.351: open firstRecord //│ ║ ^^^^^^^^^^^ //│ ╟── candidate: record field 'shared' defined here -//│ ║ l.344: let secondRecord = {shared: 2} +//│ ║ l.350: let secondRecord = {shared: 2} //│ ║ ^^^^^^ //│ ╟── Opened here -//│ ║ l.346: open secondRecord +//│ ║ l.352: open secondRecord //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls index 6e03199226..8a5516b7f0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls @@ -7,13 +7,13 @@ data class Env(bindings) with fun extend(name) = Env(0) :expect 0 -fun infer(env, x) = if x then env.lookup(x) else infer(env.extend(x), x) +private fun infer(env, x) = if x then env.lookup(x) else infer(env.extend(x), x) infer(Env(0), true) //│ = 0 // Exercise the recursive branch at runtime as well as during resolution. :expect 0 -fun advance(env, n) = if n == 0 then env.lookup(n) else advance(env.extend(n), n - 1) +private fun advance(env, n) = if n == 0 then env.lookup(n) else advance(env.extend(n), n - 1) advance(Env(0), 3) //│ = 0 @@ -23,20 +23,35 @@ data class NewEnv(bindings) with fun extend(name) = new NewEnv(0) :expect 0 -fun advanceNew(env, n) = if n == 0 then env.lookup(n) else advanceNew(env.extend(n), n - 1) +private fun advanceNew(env, n) = if n == 0 then env.lookup(n) else advanceNew(env.extend(n), n - 1) advanceNew(new NewEnv(0), 3) //│ = 0 // The instance capture matters even without a recursive source-level call. +// FIXME: Rebuilding the same class loses the original constructor activation. +:fixme :expect 42 class Box(val value) with fun copy() = Box(value) Box({answer: 42}).copy().value.answer -//│ = 42 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.36: Box({answer: 42}).copy().value.answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── Parameter 'value' admits values of unknown shape. +//│ ║ l.34: class Box(val value) with +//│ ║ ^^^^^ +//│ ╟── 'Box' is exposed by this compilation unit. +//│ ║ l.34: class Box(val value) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.35: fun copy() = Box(value) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' // Rebuilding a nested class must also retain its enclosing function's capture. :expect 43 -fun nested(enclosing) = +private fun nested(enclosing) = class Inner(val value) with fun copy() = new Inner(value) fun read() = enclosing.answer @@ -45,19 +60,37 @@ nested({answer: 43}) //│ = 43 // Captures of inherited wildcard opens cross instance scopes too. +// FIXME: Capturing a constructor alias loses its calling activation. +:fixme :expect 44 class OpenBox(val value) let makers = {create: OpenBox} -fun opened(value) = +private fun opened(value) = open makers class Wrapper with fun make() = create(value) (new Wrapper).make().value.answer opened({answer: 44}) -//│ = 44 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.72: (new Wrapper).make().value.answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── Parameter 'value' admits values of unknown shape. +//│ ║ l.66: class OpenBox(val value) +//│ ║ ^^^^^ +//│ ╟── This value is stored in this record field. +//│ ║ l.67: let makers = {create: OpenBox} +//│ ║ ^^^^^^ +//│ ╟── This value is exposed by this compilation unit. +//│ ║ l.67: let makers = {create: OpenBox} +//│ ╙── ^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '44', got: 'undefined' //│ makers = {create: fun OpenBox { class: class OpenBox }} // Remaining argument lists retain the context of an explicitly constructed class. +// FIXME: An exposed partial constructor shares its remaining argument flow with callers. +:fixme :expect [45, 46] fun partial(a) = class Pair(val first)(val second) @@ -65,7 +98,18 @@ fun partial(a) = let finish = partial({answer: 45}) let pair = finish({answer: 46}) [pair.first.answer, pair.second.answer] -//│ = [45, 46] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.100: [pair.first.answer, pair.second.answer] +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── Parameter 'second' admits values of unknown shape. +//│ ║ l.96: class Pair(val first)(val second) +//│ ║ ^^^^^^ +//│ ╟── This value is exposed by this compilation unit. +//│ ║ l.98: let finish = partial({answer: 45}) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[45, 46]', got: 'undefined' //│ finish = fun //│ pair = Pair({answer: 45}) @@ -73,6 +117,6 @@ let pair = finish({answer: 46}) :expect 1 class Chain(val value, val previous) with fun prepend(value) = Chain(value, this) -fun build(chain, n) = if n == 0 then chain else build(chain.prepend(n), n - 1) +private fun build(chain, n) = if n == 0 then chain else build(chain.prepend(n), n - 1) build(Chain(0, ()), 3).value //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 38f9ccbc95..5516b718bc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -198,3 +198,20 @@ fun foo(x) = +:e +fun foo[A](x: A) = x.a +foo({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.202: fun foo[A](x: A) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.202: fun foo[A](x: A) = x.a +//│ ║ ^ +//│ ╟── 'foo' is exposed by this compilation unit. +//│ ║ l.202: fun foo[A](x: A) = x.a +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + + + diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index 5f7d89936a..415a33848f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -19,8 +19,8 @@ delayed() class Box(val value) fun pair(x, y) = [x, ...[y]] fun second(a, b) = b -fun forward(head, ...tail) = second(...tail) -fun captured(x, y) = () => forward(0, ...pair(x, y)) +private fun forward(head, ...tail) = second(...tail) +private fun captured(x, y) = () => forward(0, ...pair(x, y)) let first = captured(Box(10), Box(11)) let next = captured(Box(20), Box(21)) [first().value, captured(Box(0), Box(12))().value, next().value, captured(Box(0), Box(22))().value] @@ -30,7 +30,7 @@ let next = captured(Box(20), Box(21)) // Residual fields from distinct spreads and direct arguments keep separate marks. :expect 32 -fun mixed(x, y) = forward(...[0, x], y) +private fun mixed(x, y) = forward(...[0, x], y) mixed(Box(31), Box(32)).value //│ = 32 @@ -42,13 +42,36 @@ empty(0) //│ = 40 // Each list of a curried function or constructor is checked independently. +// FIXME: Forwarding to a captured constructor loses the caller activation. +:fixme :expect [51, 52, 53, 54] class Pair(val left)(val right) fun curried(x)(y) = Pair(x)(y) let built = curried(...[Box(51)])(...[Box(52)]) let direct = new Pair(...[Box(53)])(...[Box(54)]) [built.left.value, built.right.value, direct.left.value, direct.right.value] -//│ = [51, 52, 53, 54] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.52: [built.left.value, built.right.value, direct.left.value, direct.right.value] +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.49: fun curried(x)(y) = Pair(x)(y) +//│ ║ ^ +//│ ╟── 'curried' is exposed by this compilation unit. +//│ ║ l.49: fun curried(x)(y) = Pair(x)(y) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.52: [built.left.value, built.right.value, direct.left.value, direct.right.value] +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'y' admits values of unknown shape. +//│ ║ l.49: fun curried(x)(y) = Pair(x)(y) +//│ ║ ^ +//│ ╟── 'curried' is exposed by this compilation unit. +//│ ║ l.49: fun curried(x)(y) = Pair(x)(y) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[51, 52, 53, 54]', got: 'undefined' //│ built = Pair(Box(51)) //│ direct = Pair(Box(53)) @@ -63,7 +86,7 @@ typed(0) fun tooShort() = four(...short()) fun short() = [1, ...[2]] //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.63: fun tooShort() = four(...short()) +//│ ║ l.86: fun tooShort() = four(...short()) //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got 2 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -72,7 +95,7 @@ fun short() = [1, ...[2]] :e four(...[1, 2], ...[3, 4], 5) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.73: four(...[1, 2], ...[3, 4], 5) +//│ ║ l.96: four(...[1, 2], ...[3, 4], 5) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got 5 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -125,7 +148,7 @@ fun varying(xs, ys) = four(...xs, ...ys) fun knownGood() = varying([1], [2, 3, 4]) fun laterBad() = varying([5], [6, 7]) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.124: fun varying(xs, ys) = four(...xs, ...ys) +//│ ║ l.147: fun varying(xs, ys) = four(...xs, ...ys) //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got 3 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -166,7 +189,7 @@ let arr = if maybe then [1, 2] else [1, 2, 3, 4] :e four(...arr) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.167: four(...arr) +//│ ║ l.190: four(...arr) //│ ║ ^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got 2 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -202,10 +225,19 @@ fun tailLast(a, b, c) = c tailLast(...afterOne(...new Array("ignored"), 1, 2, Box(94))).value //│ = 94 -// Unknown elements used as callees remain dynamic calls. -:expect 95 +// An opaque spread does not authorize a call through its unknown element. +:e fun invokeUnknown(f) = f() invokeUnknown(...new Array(() => 95)) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.230: fun invokeUnknown(f) = f() +//│ ║ ^^^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.231: invokeUnknown(...new Array(() => 95)) +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── This spread has no known element shape. +//│ ║ l.231: invokeUnknown(...new Array(() => 95)) +//│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ = 95 // Unknown elements must not silently resolve against another known candidate. @@ -214,10 +246,11 @@ fun inspectUnknown(x) = x.value inspectUnknown(Box(96)) inspectUnknown(...new Array(Box(96))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.213: fun inspectUnknown(x) = x.value +//│ ║ l.245: fun inspectUnknown(x) = x.value //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ║ l.215: inspectUnknown(...new Array(Box(96))) +//│ ╟── This spread has no known element shape. +//│ ║ l.247: inspectUnknown(...new Array(Box(96))) //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -225,24 +258,33 @@ inspectUnknown(...new Array(Box(96))) :e four(...new Array(), 1, 2, 3, 4, 5) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.226: four(...new Array(), 1, 2, 3, 4, 5) +//│ ║ l.259: four(...new Array(), 1, 2, 3, 4, 5) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got at least 5 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'four' expected 4 arguments but got 5 -// Successful rest calls still require enough arguments before a known suffix. -:expect 97 +// Rest arity may be valid while the callable argument remains unknown. +:e fun invokeRest(f, x, ...tail) = f() invokeRest(...new Array(() => 97), Box(0)) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.270: fun invokeRest(f, x, ...tail) = f() +//│ ║ ^^^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.271: invokeRest(...new Array(() => 97), Box(0)) +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── This spread has no known element shape. +//│ ║ l.271: invokeRest(...new Array(() => 97), Box(0)) +//│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ = 97 // When repeatedTuples receives [Box(98)], both a and b must acquire its element // shape so that both `.value` selections resolve. Although the tuple expressions // have equal syntax, resolving a does not notify the listeners attached to b. :expect [98, 98] -fun repeatedTuples(xs) = +private fun repeatedTuples(xs) = let a = [0, ...xs] let b = [0, ...xs] [second(...a).value, second(...b).value] diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls b/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls index 9ba17f2dfe..73bbaf9f3e 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls @@ -53,6 +53,8 @@ fun unused(value: Invalid) = value type Invalid = notAType val notAType = 1 //│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.53: type Invalid = notAType +//│ ║ ^^^^^^^^ //│ ╟── Value symbol 'notAType' cannot be used as a type //│ ║ l.54: val notAType = 1 //│ ╙── ^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls index 2ca3d9573d..9e9ddb9fc7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ValCtxSens.mls @@ -48,24 +48,38 @@ foo(B).u val alias = foo //│ alias = fun +// FIXME: Alias capture loses the activation of the exposed lambda. +:fixme :expect 2 alias(B).u -//│ = 2 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.54: alias(B).u +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'u' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.16: val foo = x => x +//│ ║ ^ +//│ ╟── 'alias' is exposed by this compilation unit. +//│ ║ l.48: val alias = foo +//│ ╙── ^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' :todo // The captured reference to foo is shared by both calls of alias. :sr alias(A).u +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.70: alias(A).u +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'u' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.16: val foo = x => x +//│ ║ ^ +//│ ╟── 'alias' is exposed by this compilation unit. +//│ ║ l.48: val alias = foo +//│ ╙── ^^^^^^^^^^^^^^^ //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ alias⁰(A⁰)‹app²›.u‹u⁰, u²› -//│ ╔══[COMPILATION ERROR] This selection of member 'u' is ambiguous, as it has multiple resolved targets -//│ ║ l.57: alias(A).u -//│ ║ ^^^^^^^^^^ -//│ ╟── target: value 'u' in module 'A' -//│ ║ l.6: val u = 1 -//│ ║ ^ -//│ ╟── target: value 'u' in module 'B' -//│ ║ l.9: val u = 2 -//│ ╙── ^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls index d5f9f973b6..860345fb29 100644 --- a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls +++ b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls @@ -90,6 +90,28 @@ module M with //│ | | | ~> member:Predef⁰^term:M⁰/p⁰ //│ | | ~> { val member:p = member:Predef⁰^term:M⁰/p⁰; } //│ ~> { Mod M ObjBody(Blk(List(TermDefinition(ImmutVal,member:p,term:M/p,List(),None,None,Some(Capture(MemberRef(member:Predef),term:M/p)),‹›,Modulefulness(None),List(),None)),Lit(UnitLit(false)))); } +//│ listenBMS: bms = module +//│ listenedBMS: bms = module +//│ fromBMS: bms = member:M¹, sh = module:M⁰, flow = exposed⁰, markss = List() +//│ PUSH module:M⁰ +//│ listenTerm: trm = undefined +//│ listen: trm = undefined +//│ introShape: intro = Lit(UnitLit(false)) +//│ listenBMS: bms = value +//│ listenedBMS: bms = value +//│ listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ +//│ listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ +//│ listen: trm = member:Predef⁰^term:M⁰/p⁰ +//│ listenTerm: trm = member:Predef⁰ +//│ listen: trm = member:Predef⁰ +//│ listenBMS: bms = module +//│ listenedBMS: bms = module +//│ fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() +//│ PUSH module:Predef¹ +//│ fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = exposed¹, markss = List() +//│ .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, exposed¹) +//│ = module:Predef¹ +//│ MATCH module:Predef¹ for term:M⁰/p⁰ % exposed¹ //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ module M⁰::M¹ { val p²::p³ ‹flow:p¹› = Predef⁰^p³ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -167,6 +189,14 @@ M.p.id //│ | | ~> member:M¹.p.id //│ | ~> member:M¹.p.id //│ ~> { member:M¹.p.id } +//│ listenTerm: trm = member:M¹.p.id +//│ listen: trm = member:M¹.p.id +//│ listenBMS: bms = function +//│ listenedBMS: bms = function +//│ fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List() +//│ .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) +//│ = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ PUSH MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ M⁰.p².id⁰ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls index bc68d708ee..9f2695df05 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls @@ -66,9 +66,16 @@ call() :e fun unknownReceiver(x) = x.missing -//│ ╔══[COMPILATION ERROR] This selection of member 'missing' has no resolved target +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.68: fun unknownReceiver(x) = x.missing +//│ ║ ^^^^^^^^^ +//│ ╟── Cannot resolve member 'missing' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.68: fun unknownReceiver(x) = x.missing +//│ ║ ^ +//│ ╟── 'unknownReceiver' is exposed by this compilation unit. //│ ║ l.68: fun unknownReceiver(x) = x.missing -//│ ╙── ^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Allowing multiple targets does not make class definitions assignable. :e @@ -77,7 +84,7 @@ module ClassRight { class Item } let classes = if hide(true) then ClassLeft else ClassRight set classes.Item = 0 //│ ╔══[COMPILATION ERROR] Assignment requires a term member -//│ ║ l.78: set classes.Item = 0 +//│ ║ l.85: set classes.Item = 0 //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ classes = class ClassLeft { Item: class Item } diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls index 48a2d50722..8b089e1106 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/WasmMethodResolution.mls @@ -34,4 +34,6 @@ class Add(val x) { fun add(y) = x + y } val add = Add::add add(Add(2), 4) //│ ═══[COMPILATION ERROR] WatBuilder::returningTerm for FunDefn(...) where `!sym.nameIsMeaningful` not implemented yet -//│ ═══[COMPILATION ERROR] Expected static function reference in Call(...) expression +//│ ╔══[COMPILATION ERROR] Expected static function reference in Call(...) expression +//│ ║ l.35: add(Add(2), 4) +//│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls index d02ae082c7..34e6839ba6 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls @@ -9,7 +9,7 @@ open Stack fun showList(xs) = "[" + xs Iter.fromStack() Iter.joined(", ") + "]" -fun foldLeft(f)(z) = +private fun foldLeft(f)(z) = fun aux(acc, xs) = if xs is Nil then acc diff --git a/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls b/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls index 1e15c28e91..c193cfb2ea 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls @@ -24,7 +24,7 @@ Cons(1, Nil) //│ ╙── ^ //│ = Cons(1, 2) -fun map(f, xs) = if xs is +private fun map(f, xs) = if xs is Nil then Nil x :: xs' then f(x) :: map(f, xs') diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls b/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls index 9576c6d311..7d78f1d857 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls @@ -126,7 +126,7 @@ add_5(Some(5), Some(9)) //│ = 14 -fun add_6(x, y) = +private fun add_6(x, y) = if [x, y] is [Some(xv), Some(yv)] then xv + yv [Some(xv), None] then xv @@ -149,7 +149,7 @@ add_6(Some(5), Some(9)) // Functions from now on have a predicate `p` that can be used to add some preconditions. -fun add_6(p, x, y) = +private fun add_6(p, x, y) = if x is Some(xv) and y is Some(yv) then xv + yv y is None and p(x) and x is Some(xv) then 42 @@ -179,7 +179,7 @@ add_6((x) => true, Some(5), Some(9)) //│ = 14 -fun add_7(p, x, y) = +private fun add_7(p, x, y) = if x is Some(xv) and y is Some(yv) then xv + yv // y is None and p(x) and x is Some(xv) then 42 @@ -210,7 +210,7 @@ add_7((x) => x > 0, Some(5), Some(9)) //│ = 14 -fun add_8(p, x, y) = +private fun add_8(p, x, y) = if x is Some(xv) and y is Some(yv) then xv + yv y is None and p(x) and x is Some(xv) then 42 diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls b/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls index 89330baef5..991ecd82ad 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls @@ -6,7 +6,7 @@ data class Cons[out T](head: T, tail: List[T]) extends List[T] object Nil extends List[Nothing] -fun map(f, xs) = if xs is +private fun map(f, xs) = if xs is Nil then Nil let xs' = map(f, xs) Cons(x, xs) then Cons(f(x), xs') diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/List.mls b/hkmc2/shared/src/test/mlscript/ucs/general/List.mls index 079a4b3bf1..1ca2141050 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/List.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/List.mls @@ -23,12 +23,12 @@ length of () //│ ═══[RUNTIME ERROR] Error: match error -fun map(f, xs) = if xs is +private fun map(f, xs) = if xs is Nil then Nil Cons(x, xs) then Cons(f(x), map(f, xs)) -fun findFirst(f, xs) = +private fun findFirst(f, xs) = if xs is Nil then None Cons(x, _) and f(x) then Some(x) @@ -44,7 +44,7 @@ findFirst of _ > 2, 1 :: 2 :: 4 :: 3 :: Nil //│ = Some(4) -fun findFirst(f, xs) = +private fun findFirst(f, xs) = if xs is Nil then None Cons(x, _) and @@ -61,7 +61,7 @@ findFirst of _ > 2, 1 :: 2 :: 4 :: 3 :: Nil //│ = Some(4) -fun findFirst(f, xs) = +private fun findFirst(f, xs) = if xs is Nil then None diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls index b56907f663..3952fb1c0e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls @@ -5,11 +5,11 @@ import "../../../mlscript-compile/Option.mls" open Option -fun map(f) = case +private fun map(f) = case Some(x) then Some(f(x)) None as n then n -fun map(f) = case +private fun map(f) = case Some(x as n) then Some of f(n) None as n then n diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls index 94f37913a0..4ada8ea800 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls @@ -8,26 +8,41 @@ open annotations :fixme fun trimStart(str) = if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str +//│ ╔══[COMPILATION ERROR] This pattern is not supported during shape resolution. +//│ ║ l.10: if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str +//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. //│ ║ l.10: if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^ -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'rest' +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'rest' +//│ ║ l.10: if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str +//│ ╙── ^^^^ :fixme fun trimStart(str) = if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ╔══[COMPILATION ERROR] This pattern is not supported during shape resolution. +//│ ║ l.23: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.18: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.23: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^ -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'rest' +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'rest' +//│ ║ l.23: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ╙── ^^^^ :fixme fun trimStart(str) = if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ╔══[COMPILATION ERROR] This pattern is not supported during shape resolution. +//│ ║ l.36: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.26: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ║ l.36: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^ -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'rest' +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'rest' +//│ ║ l.36: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ╙── ^^^^ // * Note: `@compile(...)` is parsed as a whole annotation @@ -36,16 +51,16 @@ fun trimStart(str) = fun trimStart(str) = if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.37: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.52: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.37: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.52: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.37: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.52: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: rest -//│ ║ l.37: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.52: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls index 7d4038b97a..373c0fbc54 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls @@ -39,6 +39,9 @@ keepSecret(SecretPair(2, 3)) :e fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result +//│ ╔══[COMPILATION ERROR] This pattern is not supported during shape resolution. +//│ ║ l.41: fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result +//│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] This pattern cannot be matched //│ ║ l.41: fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result //│ ║ ^ @@ -46,6 +49,8 @@ fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then resu //│ ║ l.30: class SecretPair(a: Int, val b: Int) //│ ║ ^ //│ ╟── because rebuilding the matched `SecretPair` value requires reading every constructor argument +//│ ║ l.41: fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result +//│ ║ ^^^^^^^^^^ //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible class Test(a, b, c, d, e, f) @@ -55,50 +60,50 @@ fun test(x) = if x is Test then "ok" :e fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `a` is not publicly accessible -//│ ║ l.51: class Test(a, b, c, d, e, f) +//│ ║ l.56: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `b` is not publicly accessible -//│ ║ l.51: class Test(a, b, c, d, e, f) +//│ ║ l.56: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `c` is not publicly accessible -//│ ║ l.51: class Test(a, b, c, d, e, f) +//│ ║ l.56: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `d` is not publicly accessible -//│ ║ l.51: class Test(a, b, c, d, e, f) +//│ ║ l.56: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `e` is not publicly accessible -//│ ║ l.51: class Test(a, b, c, d, e, f) +//│ ║ l.56: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.56: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `f` is not publicly accessible -//│ ║ l.51: class Test(a, b, c, d, e, f) +//│ ║ l.56: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible @@ -106,7 +111,7 @@ fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" :e fun test(x) = if x is Test(_, _, _) then "ok" //│ ╔══[COMPILATION ERROR] Resolution error in constructor -//│ ║ l.107: fun test(x) = if x is Test(_, _, _) then "ok" +//│ ║ l.112: fun test(x) = if x is Test(_, _, _) then "ok" //│ ║ ^^^^^^^^^^^^^ //│ ╙── Class definition 'Test' expected 6 pattern arguments, but got 3 @@ -115,10 +120,10 @@ fun test(x) = if x is Test(_, _, _, _, _, _) then "ok" :e fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.116: fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" +//│ ║ l.121: fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `d` is not publicly accessible -//│ ║ l.51: class Test(a, b, c, d, e, f) +//│ ║ l.56: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible @@ -141,16 +146,16 @@ object A :e fun foo(x) = if x is A() then 0 //│ ╔══[COMPILATION ERROR] `A` is an object. -//│ ║ l.139: object A +//│ ║ l.144: object A //│ ║ ^ //│ ╟── Its pattern cannot have an argument list. -//│ ║ l.142: fun foo(x) = if x is A() then 0 +//│ ║ l.147: fun foo(x) = if x is A() then 0 //│ ╙── ^^^ :e fun foo(x) = if x is A(y) then y //│ ╔══[COMPILATION ERROR] Resolution error in constructor -//│ ║ l.151: fun foo(x) = if x is A(y) then y +//│ ║ l.156: fun foo(x) = if x is A(y) then y //│ ║ ^^^^ //│ ╙── Module/object definition 'A' does not take pattern arguments. @@ -206,7 +211,7 @@ if p is Crazy(a, b, c, d, e, f, g) then :re if p is Crazy(a, b, c, d, e, f) then 42 //│ ╔══[COMPILATION ERROR] Expected seven extraction arguments, but found only six arguments. -//│ ║ l.207: if p is Crazy(a, b, c, d, e, f) then 42 +//│ ║ l.212: if p is Crazy(a, b, c, d, e, f) then 42 //│ ╙── ^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -214,7 +219,7 @@ if p is Crazy(a, b, c, d, e, f) then 42 :re if p is Crazy(all) then 42 //│ ╔══[COMPILATION ERROR] Expected seven extraction arguments, but found only one argument. -//│ ║ l.215: if p is Crazy(all) then 42 +//│ ║ l.220: if p is Crazy(all) then 42 //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls index 58fcadf6c2..59ba9d0b8a 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls @@ -86,7 +86,7 @@ fun foo(x) = x is fun foo(x) = x is Pair(a, b) | Int //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) //│ ║ l.86: fun foo(x) = x is Pair(a, b) | Int -//│ ╙── ^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ // Test how shorthand conditionals work together with lambda functions. // ==================================================================== diff --git a/hkmc2/shared/src/test/mlscript/ups/UpsBugsBacklog.mls b/hkmc2/shared/src/test/mlscript/ups/UpsBugsBacklog.mls index c2b30ff2c1..0fe2bc0547 100644 --- a/hkmc2/shared/src/test/mlscript/ups/UpsBugsBacklog.mls +++ b/hkmc2/shared/src/test/mlscript/ups/UpsBugsBacklog.mls @@ -47,7 +47,9 @@ if tokens is @compile Parse as stack do print(stack) :fixme // Should be a "name not found" if tokens is @compile Parse as stack do print(stack) -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'stack' +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'stack' +//│ ║ l.49: @compile Parse as stack do print(stack) +//│ ╙── ^^^^^ // What's actually intended: if tokens is diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls index cc4b68e464..67a3452581 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls @@ -221,7 +221,7 @@ fun sideGo(x) = if x is @compile SideGo(r) then r else "" //│ ║ l.214: pattern SideSum = Pair(Lit(a), Lit(b)) => Lit(a + b) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.215: pattern SideCtx(pattern H) = H | Pair(SideCtx(H), SideUnBox) -//│ ╙── ^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // An alternative that is not a class pattern, so there is no constructor to @@ -260,7 +260,7 @@ fun arityGo(x) = if x is @compile ArityGo(r) then r else "" //│ ║ l.245: pattern AritySum = Pair(Lit(a), Lit(b)) => Lit(a + b) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.246: pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) -//│ ╙── ^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // A rewriting alternative placed after one that descends. diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/MixedParameters.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/MixedParameters.mls index 3900ab9a3f..5163ff307a 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/MixedParameters.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/MixedParameters.mls @@ -42,7 +42,9 @@ if [0, 0] is P'(Bit, a, b) then "values: " + a + ", " + b // Order actually matters. But it seems that I didn't implement the error // reporting correctly. if [0, 0] is P(a, Bit, b) then "values: " + a + ", " + b -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'a' +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'a' +//│ ║ l.44: if [0, 0] is P(a, Bit, b) then "values: " + a + ", " + b +//│ ╙── ^ //│ ═══[RUNTIME ERROR] ReferenceError: a is not defined pattern S(a, pattern Q, b) = [Q as a, Q as b] @@ -55,7 +57,9 @@ f([0, 0]) :fixme // Order actually matters. fun f(x) = if x is S(Bit, a, b) then [a, b] -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'a' +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'a' +//│ ║ l.59: fun f(x) = if x is S(Bit, a, b) then [a, b] +//│ ╙── ^ :re f([0, 0]) @@ -82,7 +86,7 @@ if 0 is Q'(Bit) as y then y // Similarly, it cannot be written like this: if 0 is Q'(Bit, y) then y //│ ╔══[COMPILATION ERROR] Expected zero extraction arguments, but found one argument. -//│ ║ l.83: if 0 is Q'(Bit, y) then y +//│ ║ l.87: if 0 is Q'(Bit, y) then y //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -95,8 +99,8 @@ pattern CaptureBit(value) = Bit as value // discarded unless the user makes the discard explicit with `as _`. if 0 is CaptureBit(ZeroToOne) then "discarded implicitly" //│ ╔══[WARNING] This extraction argument's transformation result is discarded by pattern `CaptureBit`. -//│ ║ l.96: if 0 is CaptureBit(ZeroToOne) then "discarded implicitly" -//│ ║ ^^^^^^^^^ +//│ ║ l.100: if 0 is CaptureBit(ZeroToOne) then "discarded implicitly" +//│ ║ ^^^^^^^^^ //│ ╙── Write `... as _` to discard it explicitly. //│ = "discarded implicitly" diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls index 1c9453794a..1fdfa8fac8 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls @@ -8,11 +8,15 @@ pattern LessArity = Pair("meow") //│ ╔══[COMPILATION ERROR] Resolution error in constructor //│ ║ l.7: pattern LessArity = Pair("meow") //│ ║ ^^^^^^^^^^^^ -//│ ╙── Class definition 'Pair' expected 2 pattern arguments, but got 1 +//│ ╟── Class definition 'Pair' expected 2 pattern arguments, but got 1 +//│ ║ l.4: data class Pair[A, B](val first: A, val second: B) +//│ ╙── ^^^^^^^^^^^^^^^^ :e pattern MoreArity = Pair("meow", "woof", "moo") //│ ╔══[COMPILATION ERROR] Resolution error in constructor -//│ ║ l.14: pattern MoreArity = Pair("meow", "woof", "moo") +//│ ║ l.16: pattern MoreArity = Pair("meow", "woof", "moo") //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Class definition 'Pair' expected 2 pattern arguments, but got 3 +//│ ╟── Class definition 'Pair' expected 2 pattern arguments, but got 3 +//│ ║ l.4: data class Pair[A, B](val first: A, val second: B) +//│ ╙── ^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 0fe8b85502..60898647ce 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -76,7 +76,9 @@ wasm.i32.const(1, 2) // backend, so it would not test this. :ge fun constOf(x: Int) = wasm.ref.i31(wasm.i32.const(x)) -//│ ═══[COMPILATION ERROR] Wasm intrinsic 'i32.const' expects an integer literal immediate +//│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' expects an integer literal immediate +//│ ║ l.78: fun constOf(x: Int) = wasm.ref.i31(wasm.i32.const(x)) +//│ ╙── ^ // Negative literals are elaborated as a call to `Call(BuiltinSymbol("-"), 1)`. diff --git a/hkmc2/shared/src/test/mlscript/wasm/Casts.mls b/hkmc2/shared/src/test/mlscript/wasm/Casts.mls index fdecf45123..ef609a3f0b 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Casts.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Casts.mls @@ -177,7 +177,9 @@ unpack(toObj()) // where `Unknown` cannot be cast to a primitive and is rejected. :e fun test: Function = id -//│ ═══[COMPILATION ERROR] Returning function instances is not supported +//│ ╔══[COMPILATION ERROR] Returning function instances is not supported +//│ ║ l.179: fun test: Function = id +//│ ╙── ^^ :fixme fun test: Function = new Object @@ -218,7 +220,7 @@ fun test: Function = castToA(123) :e castToA(wasm.i32.const(123)) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int32' at an unrelated type 'Unknown' -//│ ║ l.219: castToA(wasm.i32.const(123)) +//│ ║ l.221: castToA(wasm.i32.const(123)) //│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ Wasm result: //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/wasm/NumericTypes.mls b/hkmc2/shared/src/test/mlscript/wasm/NumericTypes.mls index f5de42ff63..fe69899422 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/NumericTypes.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/NumericTypes.mls @@ -225,17 +225,19 @@ wasm.f64.const(1.0e400) // Immediates must be written as literals for floats as much as for integers. :ge fun constOf(x: Num): Float64 = wasm.f64.const(x) -//│ ═══[COMPILATION ERROR] Wasm intrinsic 'f64.const' expects a numeric literal immediate +//│ ╔══[COMPILATION ERROR] Wasm intrinsic 'f64.const' expects a numeric literal immediate +//│ ║ l.227: fun constOf(x: Num): Float64 = wasm.f64.const(x) +//│ ╙── ^ // `Float32` and `Float64` are as distinct as the two integer types are. :ge wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Float32' at an unrelated type 'Float64' -//│ ║ l.233: wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) +//│ ║ l.235: wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) //│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Float32' at an unrelated type 'Float64' -//│ ║ l.233: wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) +//│ ║ l.235: wasm.f64.add(wasm.f32.const(1.5), wasm.f32.const(2.25)) //│ ╙── ^^^^^^^^^^^^^^^^^^^^ //│ ═══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'f64.add' must be of type 'Float64' //│ ═══[COMPILATION ERROR] Operand #2 of wasm intrinsic 'f64.add' must be of type 'Float64' diff --git a/hkmc2/shared/src/test/mlscript/wasm/TailRecOptCasts.mls b/hkmc2/shared/src/test/mlscript/wasm/TailRecOptCasts.mls index f206cb43e8..db8ce21077 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/TailRecOptCasts.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/TailRecOptCasts.mls @@ -309,5 +309,7 @@ fun foo(n: Int31) = fun bar(m: Int32) = foo(wasm.ref.i31(m)) //│ ═══[COMPILATION ERROR] Cannot cast a Wasm value of type `i32` to `(ref null any)` in a different type hierarchy -//│ ═══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'ref.i31' must be of type 'Int32' +//│ ╔══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'ref.i31' must be of type 'Int32' +//│ ║ l.310: foo(wasm.ref.i31(m)) +//│ ╙── ^ //│ ═══[COMPILATION ERROR] Cannot cast a Wasm value of type `i32` to `(ref null any)` in a different type hierarchy diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index a338952ed2..e66fc99301 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -394,7 +394,7 @@ abstract class MLsDiffMaker extends DiffMaker: val elab = Elaborator(etl, wd, Ctx.empty) try val resBlk = new syntax.Tree.Block(res) - val (e, newCtx) = elab.importFrom(resBlk) + val (e, newCtx) = elab.importFrom(resBlk, resBlk.definedSymbols.iterator.map(_._2).toList) if file.toString === runtimeSourceFile.toString then summon[Elaborator.State].initRuntimeSymbolsFromBlock(e) val ctxWithImports = newCtx.withMembers(resBlk.definedSymbols) From b370c01d47f9604533f2d28bf26a1e96a1354b20 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 16:28:03 +0800 Subject: [PATCH 074/228] Limit worksheet exposure checks to strict resolution and enforce generic opacity --- doc/new-resolution-suite-migration.md | 24 ++- .../src/test/scala/hkmc2/CompilerTest.scala | 18 ++ .../scala/hkmc2/semantics/Elaborator.scala | 13 +- .../hkmc2/semantics/InterfaceExposure.scala | 15 -- .../scala/hkmc2/semantics/NewResolver.scala | 13 ++ .../shared/src/test/mlscript/basics/AppOp.mls | 2 +- .../src/test/mlscript/basics/LazySpreads.mls | 2 +- .../src/test/mlscript/basics/OfLambdaArgs.mls | 6 +- .../src/test/mlscript/codegen/Bindings.mls | 2 +- .../test/mlscript/codegen/BlockPrinter.mls | 3 +- .../src/test/mlscript/codegen/FunnyOpen.mls | 2 +- .../src/test/mlscript/codegen/Lambdas.mls | 2 +- .../src/test/mlscript/newres/Arrays.mls | 6 +- .../test/mlscript/newres/DeclaredTypes.mls | 43 +++-- .../mlscript/newres/InterfaceExposure.mls | 175 ++++++++++-------- .../src/test/mlscript/newres/ReslScratch.mls | 17 +- .../newres/loose/InterfaceExposure.mls | 67 +++++++ .../test/mlscript/newres/loose/Targets.mls | 13 +- .../mlscript/ucs/examples/Permutations.mls | 2 +- .../mlscript/ucs/future/SymbolicClass.mls | 2 +- .../test/mlscript/ucs/general/DualOptions.mls | 8 +- .../mlscript/ucs/general/InterleavedLet.mls | 2 +- .../src/test/mlscript/ucs/general/List.mls | 8 +- .../mlscript/ucs/patterns/AliasPattern.mls | 4 +- .../mlscript/ucs/patterns/Compilation.mls | 25 +-- .../test/mlscript/ucs/patterns/Parameters.mls | 43 ++--- 26 files changed, 318 insertions(+), 199 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 4058acabb9..df9a15fe77 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -167,16 +167,26 @@ provides it to a parameter independently of call-site inference. Type aliases ma also denote `dyn`. These rules apply in both strict and non-strict resolution. Dynamic instantiation uses the existing `new!` syntax. -Before sealing a file or diff-test block, `InterfaceExposure` checks the values -reachable through its exposed interface. It seeds unannotated parameters and -unconstrained generic parameters with `UnknownValueShape`, then follows returned -functions, record/tuple contents, and public instance members. Local examples do -not close an exposed interface: both `fun foo(x) = x.a` and -`fun foo[A](x: A) = x.a` are rejected even if this unit calls `foo({a: 1})`. -Private helpers can retain local inference unless their function values escape. +Before sealing a file, `InterfaceExposure` checks the values reachable through its +exposed interface. Diff-test/worksheet blocks run this check only under strict +resolution; non-strict blocks retain local inference without flooding their public +definitions. The pass seeds unannotated parameters with `UnknownValueShape`, then +follows returned functions, record/tuple contents, and public instance members. +Local examples do not close an exposed interface: `fun foo(x) = x.a` is rejected +in a strict block even if that block calls `foo({a: 1})`. Private helpers can retain +local inference unless their function values escape. Parameter annotations provide their declared shapes; callable result annotations constrain returned implementations through their declared domains. +Generic opacity is independent of interface exposure, visibility, and strict mode. +Each generic definition is checked under a fresh abstract activation when its type +parameters are declared. Thus `private fun foo[A](x: A) = x.a` is also rejected, +even without callers or when every local instantiation supplies a type with `a`. +The diagnostic identifies the type parameter, without an exposure hint. The +abstract activation cannot match a concrete caller's exit, so inferred and explicit +type arguments still substitute into results such as `identity[A](x: A): A = x`. +Coverage includes strict and non-strict `InterfaceExposure.mls` tests. + Discovery uses a queue and temporary observers of shape publishers. Each reached shape is processed once, with listeners discovering subsequent candidates; there is no repeated whole-graph scan. Observers are detached before the unit is sealed diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index 69fc13de85..d788468b3e 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -362,6 +362,24 @@ class CompilerTest extends AnyFunSuite: compiler.compileModule(Path("/Unsafe.mls")) assert(errors.exists(_.theMsg.contains("Resolution error"))) assert(errors.exists(_.allMsgs.exists(_._1.show.contains("Type parameter 'A'")))) + assert(!errors.exists(_.allMsgs.exists(_._1.show.contains("exposed by this compilation unit")))) + + test("non-strict files still check their exported interfaces"): + val fs = new InMemoryFileSystem(loadStandardLibrary()) + given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace = false + val errors = scala.collection.mutable.ArrayBuffer.empty[Diagnostic] + given Raise = errors += _ + fs.write("/Unsafe.mls", """#lang(0.3.x, strictResolution: false) + |module Unsafe with + | fun foo(x) = x.a + |Unsafe.foo({a: 1}) + |""".stripMargin) + val compiler = new MLsCompiler(_ => summon[Raise]) + compiler.compileModule(Path("/Unsafe.mls")) + assert(errors.exists(_.allMsgs.exists(_._1.show.contains("exposed by this compilation unit")))) test("compiler can report errors"): val (fs, compiler) = createCompiler() diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 551c728a34..883b755d62 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -2161,6 +2161,7 @@ extends Importer: val (tps, ctx) = typeParams(t) (S(tps), ctx) case N => (N, ctx) + if newResolution then tps.foreach(ps => registerTypeParameters(tsym, ps.map(_.sym))) // * Add parameters to context var newCtx = newCtx1 val pss = td.paramLists.map: ps => @@ -2278,6 +2279,7 @@ extends Importer: case N => Nil newCtx ++= tps.map(tp => tp.sym.name -> tp.sym) // TODO: correct ++? + if newResolution && (k isnt Als) then registerTypeParameters(td.symbol, tps.map(_.sym)) val isDataClass = annotations.exists: case Annot.Modifier(Keyword.`data`) => true @@ -2949,11 +2951,12 @@ extends Importer: val (res, ctx) = block(sts, hasResult = false, resultInterp = Trm) computeVariances(res) if newResolution then - val exports = res.stats.collect: - case d: Definition if InterfaceExposure.isPublic(d) => d.bsym - val values = res.stats.collect: - case DefineVar(sym: LocalVarSymbol, rhs) => Term.SimpleRef(sym)(new Ident(sym.nme).withLocOf(rhs)) - InterfaceExposure(this).check(exports, res.res :: values) + if config.language.strictResolution then + val exports = res.stats.collect: + case d: Definition if InterfaceExposure.isPublic(d) => d.bsym + val values = res.stats.collect: + case DefineVar(sym: LocalVarSymbol, rhs) => Term.SimpleRef(sym)(new Ident(sym.nme).withLocOf(rhs)) + InterfaceExposure(this).check(exports, res.res :: values) rstate.completeBlock(res) (res, ctx) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index f007d7d960..ce76fd9ac4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -84,13 +84,6 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) val tuple = TupleShape(ref, TupleShape.Unknown(ref, Nil, unknown) :: Nil)(resolver) resolver.constrainParameter(rest, tuple.enter(marks), marks) - private def typeParameters(params: Ls[VarSymbol], marks: Ls[Marks], path: Path)(using NewResolverState): Unit = - params.foreach: param => - if !rstate.hasExplicitTypeArgument(param, marks) then - val ref = SimpleRef(param)(param.id) - resolver.publishParameter(param, UnknownValueShape(ref)(ShapeProvenance( - (msg"Type parameter '${param.nme}' does not specify a member interface." -> param.toLoc) :: path.diagnosticNotes)).enter(marks)) - private def members(cls: ClassLikeDef, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = cls.body.blk.stats.foreach: case d: Definition if InterfaceExposure.isPublic(d) => member(d.bsym, marks, path.via(msg"Member '${d.bsym.nme}' is accessible here." -> d.toLoc)) @@ -114,14 +107,6 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) val (head, marks) = shape.applicationHead head match case ds: DefnShape if ds.defn.sym.getState is rstate.owner => - shape match - case Marked(_: DefnShape, _) => - typeParameters(ds.defn match - case td: TermDefinition => td.tparams.toList.flatten.map(_.sym) - case cls: ClassLikeDef => cls.tparams.map(_.sym) - case _ => Nil - , marks, path) - case _ => () parameters(shape.unappliedParams, path) ds.clsDef match case S(cls) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index d329878317..701880d83f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -312,6 +312,19 @@ class NewResolver: def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])])(using rs: NewResolverState): Unit = rs.report: ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) + def registerTypeParameters(owner: AnyDefinitionSymbol, params: Ls[VarSymbol])(using State, NewResolverState): Unit = + if params.nonEmpty then + // Check every generic body under an abstract activation, including private + // definitions and non-strict worksheets. Its fresh entry site cannot match + // a real caller's exit, so identity-like results still substitute the actual + // type argument without leaking this abstract candidate into the caller. + val site = FlowSymbol("generic interface") + params.foreach: param => + val ref = SimpleRef(param)(param.id) + val unknown = UnknownValueShape(ref)(ShapeProvenance( + msg"Type parameter '${param.nme}' does not specify a member interface." -> param.toLoc :: Nil)) + publishParameter(param, MarkedShape.enter(unknown, ResolutionBoundary(owner), S(site))) + /** Explicit and inferred type arguments use the same entry/exit paths as term * arguments. An explicit argument fixes that instantiation; its value argument * must not add a more precise implementation shape to the declared interface. diff --git a/hkmc2/shared/src/test/mlscript/basics/AppOp.mls b/hkmc2/shared/src/test/mlscript/basics/AppOp.mls index a3de36baf9..d0b974fafa 100644 --- a/hkmc2/shared/src/test/mlscript/basics/AppOp.mls +++ b/hkmc2/shared/src/test/mlscript/basics/AppOp.mls @@ -59,7 +59,7 @@ add@1@2 //│ = [[1], 2, 3] -private fun test(f) = f(42) +fun test(f) = f(42) // :dp test @ x => x + 1 diff --git a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls index 347e26cdac..1d6015100f 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls @@ -120,7 +120,7 @@ sum2(..arr) //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 2 -private fun map2(f) = case +fun map2(f) = case [] then [] [x, ..xs, y] then [f(x), ..map2(f)(xs), f(y)] diff --git a/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls b/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls index 68986da21c..f771e6303c 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OfLambdaArgs.mls @@ -3,7 +3,7 @@ fun id(x) = x -private fun foo(f, g) = f(g(1)) +fun foo(f, g) = f(g(1)) foo(id(x => 1), id(y => 2)) @@ -25,8 +25,8 @@ foo(id of x => 1, id of y => 2) //│ ║ l.17: foo(id of x => 1, id of y => 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'foo' expected 2 arguments, but got 1 -//│ ║ l.6: private fun foo(f, g) = f(g(1)) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.6: fun foo(f, g) = f(g(1)) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'id' expected 1 argument but got 2 diff --git a/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls b/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls index 014a81bee7..0c22fe05a5 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Bindings.mls @@ -43,7 +43,7 @@ let x = 1 //│ x = 1 fun incr(n) = n + 1 -private fun (|>) pipe(x, f) = f(x) +fun (|>) pipe(x, f) = f(x) let x = x |> incr //│ x = 2 diff --git a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls index 3398567997..39b592aca6 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls @@ -112,12 +112,11 @@ fun f(x) = fun incr(n) = n + 1 -private fun (|>) pipe(x, f) = f(x) +fun (|>) pipe(x, f) = f(x) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define incr⁰ as fun incr¹(n) { //│ return +⁰(n, 1) //│ }; -//│ @private //│ define pipe⁰ as fun pipe¹(x, f) { return f(x) }; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls b/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls index bfa95e4877..b76e96117a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunnyOpen.mls @@ -4,7 +4,7 @@ module Mod with fun printt(x) = console.log(x) - fun (|>>) pipee[A, B](x: A, f: A -> B): B = f(x) + fun (|>>) pipee(x, f) = f(x) :ctx open Mod diff --git a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls index c7c208c541..4d04191368 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls @@ -59,7 +59,7 @@ x => if x is (0 | 1) then 1 //│ = 1 -private fun foo(f) = f(0) > 0 +fun foo(f) = f(0) > 0 if (foo of x => x + 1) then 1 //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 610c639795..bad7391c34 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -68,11 +68,11 @@ fun singleton(x) = [x] //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -// Nominal argument inference sees the tuple's Array supertype. +// Nominal argument inference sees the tuple's Array supertype at the caller. :expect [4] class Item(val value: Int) -private fun values[A](xs: Array[A]) = xs.map((item, ...) => item.value) -values([Item(4)]) +fun values[A](xs: Array[A]) = xs.map((item, ...) => item) +values([Item(4)]).map((item, ...) => item.value) //│ = [4] // Rest callback parameters retain the remaining argument tuple. diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index d15f646fa8..3aba6866f5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -215,10 +215,19 @@ fun unused(value: Loop[Int]) = value.field //│ ║ l.209: fun unused(value: Loop[Int]) = value.field //│ ╙── ^^^^^^^^^ -// Type arguments provide callable shapes through the generic parameter. -:expect 42 +// A concrete instantiation cannot authorize calls through an opaque type parameter. +:e private fun invoke[T](value: T) = value() invoke[() -> Int](() => 42) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.220: private fun invoke[T](value: T) = value() +//│ ║ ^^^^^^^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.220: private fun invoke[T](value: T) = value() +//│ ║ ^ +//│ ╟── Type parameter 'T' does not specify a member interface. +//│ ║ l.220: private fun invoke[T](value: T) = value() +//│ ╙── ^ //│ = 42 // A class-qualified selection still respects an annotated receiver's field boundary. @@ -229,11 +238,11 @@ class Box(val item) fun read(box: Box) = box.Box#item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.229: fun read(box: Box) = box.Box#item.extra +//│ ║ l.238: fun read(box: Box) = box.Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.229: fun read(box: Box) = box.Box#item.extra +//│ ║ l.238: fun read(box: Box) = box.Box#item.extra //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -263,7 +272,7 @@ class Child extends Base with fun identity[T](x: T): T = x identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.264: identity[Base](new Child).extra +//│ ║ l.273: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -305,11 +314,11 @@ class Box[T](val value: T) (new Box[Base](new Child)).value.extra Box[Base](new Child).value.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.305: (new Box[Base](new Child)).value.extra +//│ ║ l.314: (new Box[Base](new Child)).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.306: Box[Base](new Child).value.extra +//│ ║ l.315: Box[Base](new Child).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -352,11 +361,11 @@ class Box with val item = new Item (new Box as Box).Box#item.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.353: (new Box as Box).Box#item.extra +//│ ║ l.362: (new Box as Box).Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.353: (new Box as Box).Box#item.extra +//│ ║ l.362: (new Box as Box).Box#item.extra //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -370,11 +379,11 @@ module Types with fun hidden(module types: Types) = types.Item.extra hidden(Types) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.370: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.379: fun hidden(module types: Types) = types.Item.extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.370: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.379: fun hidden(module types: Types) = types.Item.extra //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -393,7 +402,7 @@ class Child extends Base with fun apply[A](f: A -> A, x: A): A = f(x) apply[Base](arg => (arg.extra; arg), new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.394: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ l.403: apply[Base](arg => (arg.extra; arg), new Child) //│ ║ ^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -416,7 +425,7 @@ class Child extends Base with fun read(xs: [Base, Int]) = xs.0.extra read([new Child, 0]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.416: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ l.425: fun read(xs: [Base, Int]) = xs.0.extra //│ ║ ^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -437,11 +446,11 @@ let xs = mut [First(1)] set xs.0 = Second(2) [...xs].0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.438: [...xs].0.first +//│ ║ l.447: [...xs].0.first //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'first' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.436: let xs = mut [First(1)] +//│ ║ l.445: let xs = mut [First(1)] //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -450,10 +459,10 @@ set xs.0 = Second(2) :ge [1, 2].2 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.451: [1, 2].2 +//│ ║ l.460: [1, 2].2 //│ ║ ^^^^^^^^ //│ ╟── Tuple literal does not contain member '2' -//│ ║ l.451: [1, 2].2 +//│ ║ l.460: [1, 2].2 //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls index 0cd2a089e3..06b33f02dc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls @@ -26,12 +26,43 @@ foo({a: 1}) //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Type parameter 'A' does not specify a member interface. //│ ║ l.21: fun foo[A](x: A) = x.a -//│ ║ ^ -//│ ╟── 'foo' is exposed by this compilation unit. -//│ ║ l.21: fun foo[A](x: A) = x.a -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// The abstraction boundary applies to private functions too. +:e +private fun privateGeneric[A](x: A) = x.a +privateGeneric({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.34: private fun privateGeneric[A](x: A) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.34: private fun privateGeneric[A](x: A) = x.a +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// It also applies through captures and generic class members. +:e +private class GenericClass[A](value: A) with + fun read() = (() => value.a)() +(new GenericClass({a: 1})).read() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.48: fun read() = (() => value.a)() +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.47: private class GenericClass[A](value: A) with +//│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +// Concrete type arguments still substitute into generic results at call sites. +:expect [3, 4] +class IdentityItem(val a: Int) +private fun identity[A](x: A): A = x +[identity(IdentityItem(3)).a, identity[IdentityItem](IdentityItem(4)).a] +//│ = [3, 4] + :expect 1 private fun foo(x) = x.a foo({a: 1}) @@ -48,16 +79,16 @@ foo(new A(1)) fun apply(f) = f(1) apply(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.48: fun apply(f) = f(1) +//│ ║ l.79: fun apply(f) = f(1) //│ ║ ^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.48: fun apply(f) = f(1) +//│ ║ l.79: fun apply(f) = f(1) //│ ║ ^ //│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.48: fun apply(f) = f(1) +//│ ║ l.79: fun apply(f) = f(1) //│ ║ ^ //│ ╟── 'apply' is exposed by this compilation unit. -//│ ║ l.48: fun apply(f) = f(1) +//│ ║ l.79: fun apply(f) = f(1) //│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ = 1 @@ -71,36 +102,36 @@ apply(x => x) :e fun escaped() = x => x.a //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.72: fun escaped() = x => x.a -//│ ║ ^^^ +//│ ║ l.103: fun escaped() = x => x.a +//│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.72: fun escaped() = x => x.a -//│ ║ ^ +//│ ║ l.103: fun escaped() = x => x.a +//│ ║ ^ //│ ╟── This value is returned here. -//│ ║ l.72: fun escaped() = x => x.a -//│ ║ ^^^^^^^^ +//│ ║ l.103: fun escaped() = x => x.a +//│ ║ ^^^^^^^^ //│ ╟── 'escaped' is exposed by this compilation unit. -//│ ║ l.72: fun escaped() = x => x.a -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.103: fun escaped() = x => x.a +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ :e private fun hidden(x) = x.a val escaped = [hidden] hidden({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.88: private fun hidden(x) = x.a -//│ ║ ^^^ +//│ ║ l.119: private fun hidden(x) = x.a +//│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.88: private fun hidden(x) = x.a -//│ ║ ^ +//│ ║ l.119: private fun hidden(x) = x.a +//│ ║ ^ //│ ╟── This value is stored in this tuple. -//│ ║ l.89: val escaped = [hidden] -//│ ║ ^^^^^^^^ +//│ ║ l.120: val escaped = [hidden] +//│ ║ ^^^^^^^^ //│ ╟── 'escaped' is exposed by this compilation unit. -//│ ║ l.89: val escaped = [hidden] -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.120: val escaped = [hidden] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ escaped = [fun hidden] @@ -109,17 +140,17 @@ private fun hidden(x) = x.a val escaped = {f: hidden} hidden({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.108: private fun hidden(x) = x.a +//│ ║ l.139: private fun hidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.108: private fun hidden(x) = x.a +//│ ║ l.139: private fun hidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.109: val escaped = {f: hidden} +//│ ║ l.140: val escaped = {f: hidden} //│ ║ ^ //│ ╟── 'escaped' is exposed by this compilation unit. -//│ ║ l.109: val escaped = {f: hidden} +//│ ║ l.140: val escaped = {f: hidden} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ escaped = {f: fun hidden} @@ -144,20 +175,20 @@ private fun hiddenRecord(x) = x.a fun exportRecord() = {callback: hiddenRecord} hiddenRecord({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.143: private fun hiddenRecord(x) = x.a +//│ ║ l.174: private fun hiddenRecord(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.143: private fun hiddenRecord(x) = x.a +//│ ║ l.174: private fun hiddenRecord(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.144: fun exportRecord() = {callback: hiddenRecord} +//│ ║ l.175: fun exportRecord() = {callback: hiddenRecord} //│ ║ ^^^^^^^^ //│ ╟── This value is returned here. -//│ ║ l.144: fun exportRecord() = {callback: hiddenRecord} +//│ ║ l.175: fun exportRecord() = {callback: hiddenRecord} //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── 'exportRecord' is exposed by this compilation unit. -//│ ║ l.144: fun exportRecord() = {callback: hiddenRecord} +//│ ║ l.175: fun exportRecord() = {callback: hiddenRecord} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -169,16 +200,16 @@ class CopyBox(val value) with fun copy() = CopyBox(value) CopyBox({answer: 42}).copy().value.answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.170: CopyBox({answer: 42}).copy().value.answer +//│ ║ l.201: CopyBox({answer: 42}).copy().value.answer //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. //│ ╟── Parameter 'value' admits values of unknown shape. -//│ ║ l.168: class CopyBox(val value) with +//│ ║ l.199: class CopyBox(val value) with //│ ║ ^^^^^ //│ ╟── 'CopyBox' is exposed by this compilation unit. -//│ ║ l.168: class CopyBox(val value) with +//│ ║ l.199: class CopyBox(val value) with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.169: fun copy() = CopyBox(value) +//│ ║ l.200: fun copy() = CopyBox(value) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -191,17 +222,17 @@ oldHidden({a: 1}) :e val oldEscape = {callback: oldHidden} //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.187: private fun oldHidden(x) = x.a +//│ ║ l.218: private fun oldHidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.187: private fun oldHidden(x) = x.a +//│ ║ l.218: private fun oldHidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.192: val oldEscape = {callback: oldHidden} +//│ ║ l.223: val oldEscape = {callback: oldHidden} //│ ║ ^^^^^^^^ //│ ╟── 'oldEscape' is exposed by this compilation unit. -//│ ║ l.192: val oldEscape = {callback: oldHidden} +//│ ║ l.223: val oldEscape = {callback: oldHidden} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ oldEscape = {callback: fun oldHidden} @@ -210,18 +241,15 @@ val oldEscape = {callback: oldHidden} fun spreadRecord[A](x: A) = ({...x}).a spreadRecord({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.210: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ l.241: fun spreadRecord[A](x: A) = ({...x}).a //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. //│ ╟── This spread has no known element shape. -//│ ║ l.210: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ l.241: fun spreadRecord[A](x: A) = ({...x}).a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.210: fun spreadRecord[A](x: A) = ({...x}).a -//│ ║ ^ -//│ ╟── 'spreadRecord' is exposed by this compilation unit. -//│ ║ l.210: fun spreadRecord[A](x: A) = ({...x}).a -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.241: fun spreadRecord[A](x: A) = ({...x}).a +//│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // The same provenance survives indexing into an unresolved tuple spread. @@ -229,18 +257,15 @@ spreadRecord({a: 1}) fun spreadTuple[A](x: A) = [...x].0.a spreadTuple([{a: 1}]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.229: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ l.257: fun spreadTuple[A](x: A) = [...x].0.a //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This spread has no known element shape. -//│ ║ l.229: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ l.257: fun spreadTuple[A](x: A) = [...x].0.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.229: fun spreadTuple[A](x: A) = [...x].0.a -//│ ║ ^ -//│ ╟── 'spreadTuple' is exposed by this compilation unit. -//│ ║ l.229: fun spreadTuple[A](x: A) = [...x].0.a -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.257: fun spreadTuple[A](x: A) = [...x].0.a +//│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Explicit dynamic spreads still permit dynamic element operations. @@ -254,20 +279,20 @@ private fun recursiveHidden(x) = x.a fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) recursiveHidden({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.253: private fun recursiveHidden(x) = x.a +//│ ║ l.278: private fun recursiveHidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.253: private fun recursiveHidden(x) = x.a +//│ ║ l.278: private fun recursiveHidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.254: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ l.279: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) //│ ║ ^^^^^^^^ //│ ╟── This value is returned here. -//│ ║ l.254: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ l.279: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── 'recursiveExport' is exposed by this compilation unit. -//│ ║ l.254: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ l.279: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -278,23 +303,23 @@ class Shadowed { } val Shadowed = 0 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.277: fun bad(x) = x.a +//│ ║ l.302: fun bad(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.277: fun bad(x) = x.a +//│ ║ l.302: fun bad(x) = x.a //│ ║ ^ //│ ╟── Member 'bad' is accessible here. -//│ ║ l.277: fun bad(x) = x.a +//│ ║ l.302: fun bad(x) = x.a //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── 'Shadowed' is exposed by this compilation unit. -//│ ║ l.276: class Shadowed { +//│ ║ l.301: class Shadowed { //│ ║ ^^^^^^^^^^^^^^^^ -//│ ║ l.277: fun bad(x) = x.a +//│ ║ l.302: fun bad(x) = x.a //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.278: } +//│ ║ l.303: } //│ ║ ^ -//│ ║ l.279: val Shadowed = 0 +//│ ║ l.304: val Shadowed = 0 //│ ╙── ^^^^^^^^^^^^^^^^ //│ Shadowed = 0 @@ -304,17 +329,17 @@ class Holder(val value) fun inspect(x) = if x is Holder(y) then y.a else 0 inspect(Holder({a: 1})) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.304: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.329: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This constructor field may contain a value of unknown shape. -//│ ║ l.303: class Holder(val value) +//│ ║ l.328: class Holder(val value) //│ ║ ^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.304: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.329: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ║ ^ //│ ╟── 'inspect' is exposed by this compilation unit. -//│ ║ l.304: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.329: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -332,17 +357,17 @@ class GenericHolder[A](val value: A) fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 inspectGeneric(GenericHolder({a: 1})) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.332: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.357: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This constructor field may contain a value of unknown shape. -//│ ║ l.331: class GenericHolder[A](val value: A) +//│ ║ l.356: class GenericHolder[A](val value: A) //│ ║ ^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.332: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.357: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ║ ^ //│ ╟── 'inspectGeneric' is exposed by this compilation unit. -//│ ║ l.332: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.357: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 5516b718bc..ade331e142 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -207,10 +207,19 @@ foo({a: 1}) //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Type parameter 'A' does not specify a member interface. //│ ║ l.202: fun foo[A](x: A) = x.a -//│ ║ ^ -//│ ╟── 'foo' is exposed by this compilation unit. -//│ ║ l.202: fun foo[A](x: A) = x.a -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +private fun foo[A](x: A) = x.a +foo({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.214: private fun foo[A](x: A) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.214: private fun foo[A](x: A) = x.a +//│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls new file mode 100644 index 0000000000..c3df4353b8 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls @@ -0,0 +1,67 @@ +:. + +// Non-strict worksheet blocks retain local inference for public definitions. +:expect 1 +fun select(x) = x.a +select({a: 1}) +//│ = 1 + +:expect 2 +fun returned() = x => x.a +returned()({a: 2}) +//│ = 2 + +// Type parameters remain opaque independently of visibility and exposure checks. +:e +fun generic[A](x: A) = x.a +generic({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.16: fun generic[A](x: A) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.16: fun generic[A](x: A) = x.a +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +private fun generic[A](x: A) = x.a +generic({a: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.28: private fun generic[A](x: A) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.28: private fun generic[A](x: A) = x.a +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +class Item(val a: Int) +private fun generic[A](x: A) = x.a +generic[Item](Item(1)) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.41: private fun generic[A](x: A) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.41: private fun generic[A](x: A) = x.a +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Abstract checking must not contaminate results at concrete calling activations. +:expect [3, 4] +fun identity[A](x: A): A = x +[identity(Item(3)).a, identity[Item](Item(4)).a] +//│ = [3, 4] + +// A private generic body is checked even without local instantiations. +:e +private fun unused[A](x: A) = x.a +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.60: private fun unused[A](x: A) = x.a +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.60: private fun unused[A](x: A) = x.a +//│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls index 9f2695df05..bc68d708ee 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls @@ -66,16 +66,9 @@ call() :e fun unknownReceiver(x) = x.missing -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.68: fun unknownReceiver(x) = x.missing -//│ ║ ^^^^^^^^^ -//│ ╟── Cannot resolve member 'missing' of a value with unknown shape. -//│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.68: fun unknownReceiver(x) = x.missing -//│ ║ ^ -//│ ╟── 'unknownReceiver' is exposed by this compilation unit. +//│ ╔══[COMPILATION ERROR] This selection of member 'missing' has no resolved target //│ ║ l.68: fun unknownReceiver(x) = x.missing -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^ // Allowing multiple targets does not make class definitions assignable. :e @@ -84,7 +77,7 @@ module ClassRight { class Item } let classes = if hide(true) then ClassLeft else ClassRight set classes.Item = 0 //│ ╔══[COMPILATION ERROR] Assignment requires a term member -//│ ║ l.85: set classes.Item = 0 +//│ ║ l.78: set classes.Item = 0 //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ classes = class ClassLeft { Item: class Item } diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls index 34e6839ba6..d02ae082c7 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls @@ -9,7 +9,7 @@ open Stack fun showList(xs) = "[" + xs Iter.fromStack() Iter.joined(", ") + "]" -private fun foldLeft(f)(z) = +fun foldLeft(f)(z) = fun aux(acc, xs) = if xs is Nil then acc diff --git a/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls b/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls index c193cfb2ea..1e15c28e91 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/future/SymbolicClass.mls @@ -24,7 +24,7 @@ Cons(1, Nil) //│ ╙── ^ //│ = Cons(1, 2) -private fun map(f, xs) = if xs is +fun map(f, xs) = if xs is Nil then Nil x :: xs' then f(x) :: map(f, xs') diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls b/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls index 7d78f1d857..9576c6d311 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/DualOptions.mls @@ -126,7 +126,7 @@ add_5(Some(5), Some(9)) //│ = 14 -private fun add_6(x, y) = +fun add_6(x, y) = if [x, y] is [Some(xv), Some(yv)] then xv + yv [Some(xv), None] then xv @@ -149,7 +149,7 @@ add_6(Some(5), Some(9)) // Functions from now on have a predicate `p` that can be used to add some preconditions. -private fun add_6(p, x, y) = +fun add_6(p, x, y) = if x is Some(xv) and y is Some(yv) then xv + yv y is None and p(x) and x is Some(xv) then 42 @@ -179,7 +179,7 @@ add_6((x) => true, Some(5), Some(9)) //│ = 14 -private fun add_7(p, x, y) = +fun add_7(p, x, y) = if x is Some(xv) and y is Some(yv) then xv + yv // y is None and p(x) and x is Some(xv) then 42 @@ -210,7 +210,7 @@ add_7((x) => x > 0, Some(5), Some(9)) //│ = 14 -private fun add_8(p, x, y) = +fun add_8(p, x, y) = if x is Some(xv) and y is Some(yv) then xv + yv y is None and p(x) and x is Some(xv) then 42 diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls b/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls index 991ecd82ad..89330baef5 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/InterleavedLet.mls @@ -6,7 +6,7 @@ data class Cons[out T](head: T, tail: List[T]) extends List[T] object Nil extends List[Nothing] -private fun map(f, xs) = if xs is +fun map(f, xs) = if xs is Nil then Nil let xs' = map(f, xs) Cons(x, xs) then Cons(f(x), xs') diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/List.mls b/hkmc2/shared/src/test/mlscript/ucs/general/List.mls index 1ca2141050..079a4b3bf1 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/List.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/List.mls @@ -23,12 +23,12 @@ length of () //│ ═══[RUNTIME ERROR] Error: match error -private fun map(f, xs) = if xs is +fun map(f, xs) = if xs is Nil then Nil Cons(x, xs) then Cons(f(x), map(f, xs)) -private fun findFirst(f, xs) = +fun findFirst(f, xs) = if xs is Nil then None Cons(x, _) and f(x) then Some(x) @@ -44,7 +44,7 @@ findFirst of _ > 2, 1 :: 2 :: 4 :: 3 :: Nil //│ = Some(4) -private fun findFirst(f, xs) = +fun findFirst(f, xs) = if xs is Nil then None Cons(x, _) and @@ -61,7 +61,7 @@ findFirst of _ > 2, 1 :: 2 :: 4 :: 3 :: Nil //│ = Some(4) -private fun findFirst(f, xs) = +fun findFirst(f, xs) = if xs is Nil then None diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls index 3952fb1c0e..b56907f663 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls @@ -5,11 +5,11 @@ import "../../../mlscript-compile/Option.mls" open Option -private fun map(f) = case +fun map(f) = case Some(x) then Some(f(x)) None as n then n -private fun map(f) = case +fun map(f) = case Some(x as n) then Some of f(n) None as n then n diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls index 4ada8ea800..baa0159d19 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls @@ -8,9 +8,6 @@ open annotations :fixme fun trimStart(str) = if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str -//│ ╔══[COMPILATION ERROR] This pattern is not supported during shape resolution. -//│ ║ l.10: if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str -//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. //│ ║ l.10: if str is @compile { (" " | "\t") ~ rest } then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^ @@ -21,27 +18,21 @@ fun trimStart(str) = :fixme fun trimStart(str) = if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str -//│ ╔══[COMPILATION ERROR] This pattern is not supported during shape resolution. -//│ ║ l.23: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str -//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.23: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.20: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'rest' -//│ ║ l.23: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.20: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^ :fixme fun trimStart(str) = if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str -//│ ╔══[COMPILATION ERROR] This pattern is not supported during shape resolution. -//│ ║ l.36: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str -//│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.36: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ║ l.30: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'rest' -//│ ║ l.36: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ║ l.30: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str //│ ╙── ^^^^ @@ -51,16 +42,16 @@ fun trimStart(str) = fun trimStart(str) = if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.52: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.43: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.52: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.43: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.52: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.43: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: rest -//│ ║ l.52: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ║ l.43: if str is (@compile((" " | "\t") ~ rest)) then trimStart(rest) else str //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls index 373c0fbc54..cb150862a5 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls @@ -39,9 +39,6 @@ keepSecret(SecretPair(2, 3)) :e fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result -//│ ╔══[COMPILATION ERROR] This pattern is not supported during shape resolution. -//│ ║ l.41: fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result -//│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] This pattern cannot be matched //│ ║ l.41: fun rebuildSecret(x) = if x is (SecretPair(_, (q => q + 1))) as result then result //│ ║ ^ @@ -60,50 +57,50 @@ fun test(x) = if x is Test then "ok" :e fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.58: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `a` is not publicly accessible -//│ ║ l.56: class Test(a, b, c, d, e, f) +//│ ║ l.53: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.58: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `b` is not publicly accessible -//│ ║ l.56: class Test(a, b, c, d, e, f) +//│ ║ l.53: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.58: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `c` is not publicly accessible -//│ ║ l.56: class Test(a, b, c, d, e, f) +//│ ║ l.53: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.58: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `d` is not publicly accessible -//│ ║ l.56: class Test(a, b, c, d, e, f) +//│ ║ l.53: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.58: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `e` is not publicly accessible -//│ ║ l.56: class Test(a, b, c, d, e, f) +//│ ║ l.53: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.61: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" +//│ ║ l.58: fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `f` is not publicly accessible -//│ ║ l.56: class Test(a, b, c, d, e, f) +//│ ║ l.53: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible @@ -111,7 +108,7 @@ fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" :e fun test(x) = if x is Test(_, _, _) then "ok" //│ ╔══[COMPILATION ERROR] Resolution error in constructor -//│ ║ l.112: fun test(x) = if x is Test(_, _, _) then "ok" +//│ ║ l.109: fun test(x) = if x is Test(_, _, _) then "ok" //│ ║ ^^^^^^^^^^^^^ //│ ╙── Class definition 'Test' expected 6 pattern arguments, but got 3 @@ -120,10 +117,10 @@ fun test(x) = if x is Test(_, _, _, _, _, _) then "ok" :e fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.121: fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" +//│ ║ l.118: fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `d` is not publicly accessible -//│ ║ l.56: class Test(a, b, c, d, e, f) +//│ ║ l.53: class Test(a, b, c, d, e, f) //│ ║ ^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible @@ -146,16 +143,16 @@ object A :e fun foo(x) = if x is A() then 0 //│ ╔══[COMPILATION ERROR] `A` is an object. -//│ ║ l.144: object A +//│ ║ l.141: object A //│ ║ ^ //│ ╟── Its pattern cannot have an argument list. -//│ ║ l.147: fun foo(x) = if x is A() then 0 +//│ ║ l.144: fun foo(x) = if x is A() then 0 //│ ╙── ^^^ :e fun foo(x) = if x is A(y) then y //│ ╔══[COMPILATION ERROR] Resolution error in constructor -//│ ║ l.156: fun foo(x) = if x is A(y) then y +//│ ║ l.153: fun foo(x) = if x is A(y) then y //│ ║ ^^^^ //│ ╙── Module/object definition 'A' does not take pattern arguments. @@ -211,7 +208,7 @@ if p is Crazy(a, b, c, d, e, f, g) then :re if p is Crazy(a, b, c, d, e, f) then 42 //│ ╔══[COMPILATION ERROR] Expected seven extraction arguments, but found only six arguments. -//│ ║ l.212: if p is Crazy(a, b, c, d, e, f) then 42 +//│ ║ l.209: if p is Crazy(a, b, c, d, e, f) then 42 //│ ╙── ^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -219,7 +216,7 @@ if p is Crazy(a, b, c, d, e, f) then 42 :re if p is Crazy(all) then 42 //│ ╔══[COMPILATION ERROR] Expected seven extraction arguments, but found only one argument. -//│ ║ l.220: if p is Crazy(all) then 42 +//│ ║ l.217: if p is Crazy(all) then 42 //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error From afa8b6c164112f27f9506e2b0273c48477401481 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 24 Sep 2026 16:56:30 +0800 Subject: [PATCH 075/228] Minor --- hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 883b755d62..6ce28fecc4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -470,7 +470,7 @@ object Elaborator: def isImport: Bool = true final case class CaptElem(base: Elem, thru: DefinitionSymbol[?]) extends Elem: def ref(id: Ident)(using Elaborator.State, Ctx, Config, NewResolver, NewResolverState): Term = - Term.Capture(base.ref(new Ident(base.nme).withLocOf(id)), thru).withLocOf(id) + Term.Capture(base.ref(id), thru) def symbol = base.symbol def isImport: Bool = false def nme: Str = base.nme From fcf487079e6f633b97c06b581fd36a79a20ba2eb Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 17:15:31 +0800 Subject: [PATCH 076/228] Port compilation fixtures and expand resolution interfaces --- doc/new-resolution-suite-migration.md | 223 ++++++++++++++++-- .../main/scala/hkmc2/codegen/Lowering.scala | 14 +- .../scala/hkmc2/codegen/js/JSBuilder.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 10 +- .../hkmc2/semantics/InterfaceExposure.scala | 6 +- .../scala/hkmc2/semantics/NewResolver.scala | 85 +++++-- .../hkmc2/semantics/NewResolverState.scala | 2 + .../main/scala/hkmc2/semantics/Shape.scala | 8 +- .../src/test/mlscript-compile/Block.mls | 6 +- .../src/test/mlscript-compile/Example.mls | 3 +- .../mlscript-compile/FilePrivateFunctions.mls | 2 + .../test/mlscript-compile/FingerTreeList.mls | 4 +- .../shared/src/test/mlscript-compile/Iter.mls | 4 +- .../test/mlscript-compile/LegacyOption.mls | 33 +++ .../src/test/mlscript-compile/MutMap.mls | 4 +- .../NestedFunctionsNoLift.mls | 2 + .../src/test/mlscript-compile/NoFreeze.mls | 2 + .../src/test/mlscript-compile/Option.mls | 14 +- .../test/mlscript-compile/QuoteExample2.mls | 2 + .../src/test/mlscript-compile/Record.mls | 2 + .../src/test/mlscript-compile/Shape.mls | 6 +- .../src/test/mlscript-compile/StrOps.mls | 2 + .../shared/src/test/mlscript-compile/Term.mls | 2 +- .../apps/parsing-web-demo/main.mls | 4 +- .../apps/parsing/BasicExpr.mls | 4 +- .../mlscript-compile/apps/parsing/Expr.mls | 4 +- .../apps/parsing/Extension.mls | 4 +- .../apps/parsing/Keywords.mls | 8 +- .../mlscript-compile/apps/parsing/Lexer.mls | 4 +- .../apps/parsing/ParseRule.mls | 12 +- .../apps/parsing/ParseRuleVisualizer.mls | 6 +- .../mlscript-compile/apps/parsing/Parser.mls | 20 +- .../apps/parsing/PrattParsing.mls | 6 +- .../apps/parsing/RecursiveDescent.mls | 6 +- .../mlscript-compile/apps/parsing/Rules.mls | 6 +- .../mlscript-compile/apps/parsing/Test.mls | 4 +- .../mlscript-compile/apps/parsing/Token.mls | 4 +- .../apps/parsing/TokenHelpers.mls | 2 + .../mlscript-compile/apps/parsing/Tree.mls | 4 +- .../apps/parsing/TreeHelpers.mls | 4 +- .../test/mlscript-compile/nofib/banner.mls | 2 + .../src/test/mlscript-compile/nofib/boyer.mls | 2 + .../test/mlscript-compile/nofib/boyer2.mls | 2 + .../test/mlscript-compile/nofib/calendar.mls | 2 + .../test/mlscript-compile/nofib/clausify.mls | 2 + .../mlscript-compile/nofib/cryptarithm1.mls | 2 + .../src/test/mlscript-compile/nofib/gcd.mls | 2 + .../test/mlscript-compile/nofib/mandel.mls | 2 + .../test/mlscript-compile/nofib/mandel2.mls | 2 + .../test/mlscript-compile/nofib/puzzle.mls | 2 + .../src/test/mlscript-compile/nofib/rsa.mls | 4 +- .../test/mlscript-compile/nofib/sphere.mls | 2 + .../test/mlscript-compile/quotes/CSPBar.mls | 2 + .../test/mlscript-compile/quotes/CSPBaz.mls | 2 + .../test/mlscript-compile/quotes/CSPFoo.mls | 2 + .../test/mlscript-compile/quotes/CSPNest.mls | 2 + .../test/mlscript-compile/quotes/Cubic.mls | 2 + .../test/mlscript-compile/quotes/Gib12.mls | 2 + .../test/mlscript-compile/quotes/Opened.mls | 2 + .../test/mlscript-compile/quotes/QuoteFoo.mls | 2 + .../test/mlscript-compile/quotes/QuoteInc.mls | 2 + .../test/mlscript-compile/quotes/SafeDiv.mls | 2 + .../src/test/mlscript-compile/ups/DnfCnf.mls | 2 + .../ups/EvaluationContext.mls | 4 +- .../test/mlscript-compile/ups/EvenOddTree.mls | 2 + .../test/mlscript-compile/ups/TruthyFalsy.mls | 2 + .../test/mlscript/block-staging/PrintCode.mls | 4 +- .../test/mlscript/codegen/ImportExample.mls | 4 +- .../src/test/mlscript/codegen/ImportMLs.mls | 18 +- .../test/mlscript/codegen/OpenWildcard.mls | 34 +-- .../src/test/mlscript/decls/Prelude.mls | 92 ++++++-- .../test/mlscript/invalml/InvalMLCodeGen.mls | 4 +- .../src/test/mlscript/newres/Arrays.mls | 19 +- .../src/test/mlscript/newres/Basics.mls | 26 +- .../src/test/mlscript/newres/Functions.mls | 2 +- .../test/mlscript/newres/HostInterfaces.mls | 14 ++ .../mlscript/newres/MemberAssignments.mls | 22 ++ .../test/mlscript/newres/OptionImports.mls | 20 ++ .../test/mlscript/newres/OverloadedCalls.mls | 67 ++++++ .../test/mlscript/newres/PrimitiveMembers.mls | 57 +++++ .../src/test/mlscript/newres/WIPTest.mls | 12 - .../ups/examples/EvaluationContext.mls | 6 +- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 9 +- 84 files changed, 797 insertions(+), 215 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/HostInterfaces.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/MemberAssignments.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/OptionImports.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/OverloadedCalls.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/PrimitiveMembers.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index df9a15fe77..c1f3eb238f 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -67,12 +67,10 @@ per-file `:todo`, `:fixme`, `:ignore`, or resolution-mode exception hides a migr failure. Existing expected failures remain visible. The inventory below records trial observations, not newly accepted failures. -The five migrated application worksheets still import compilation fixtures in -their existing mode. This is caller-side migration, not a claim that all imported -application implementations use new resolution. The 20 application compilation -fixtures and four `mlscript-compile/ups` fixtures need a subsequent dependency-ordered -migration. Their shared consumers make a blanket flag change inappropriate for -this worksheet migration. +Compilation fixtures are now being migrated as well; the worksheet counts above +are independent of that work. Mixed-mode imports remain necessary. A fixture +must pass both standalone compilation and its existing consumers before its +language header is retained. See the compilation-fixture audit below. The annotated-receiver capture assertion in WASM `Basics` is fixed; a later unannotated `this.x` initializer still blocks that worksheet (see task 3). @@ -80,6 +78,192 @@ Method calls across REPL blocks remain assigned to the other branch. The constructor-pattern context-mixing regression now passes; the remaining pattern-transfer gaps are listed below. +## Compilation-fixture migration + +All 107 original fixtures under `hkmc2/shared/src/test/mlscript-compile/` were inventoried. +Four already used new resolution. Each of the other 103 was tried independently +with `#lang(0.3.x)`, retaining its dependencies' configuration; the remaining +candidates were retried after the fixes below. Combined compilation and runtime +consumer tests decide which ports are retained. Real files check their exposed +interfaces even in non-strict mode. + +| Compilation suite | Newly migrated | Total migrated | Deferred | Total | +| --- | ---: | ---: | ---: | ---: | +| Main (including quotes, UPS, and temporary legacy copy) | 21 | 25 | 23 | 48 | +| Applications | 3 | 3 | 17 | 20 | +| Nofib | 12 | 12 | 27 | 39 | +| WASM | 0 | 0 | 1 | 1 | +| Total | 36 | 40 | 68 | 108 | + +The added `LegacyOption.mls` is counted among the deferred fixtures: 67 original +fixtures remain unported, plus this temporary compatibility copy. + +The retained ports are: + +- Main: `Example`, `FilePrivateFunctions`, `NestedFunctionsNoLift`, `NoFreeze`, + `Option`, `QuoteExample2`, `Record`, and `StrOps`. +- All ten `quotes/` fixtures: `CSPBar`, `CSPBaz`, `CSPFoo`, `CSPNest`, `Cubic`, + `Gib12`, `Opened`, `QuoteFoo`, `QuoteInc`, and `SafeDiv`. +- UPS: `DnfCnf`, `EvenOddTree`, and `TruthyFalsy`. +- Applications: `parsing/PrattParsing`, `parsing/RecursiveDescent`, and + `parsing/TokenHelpers`. +- Nofib: `banner`, `boyer`, `boyer2`, `calendar`, `clausify`, `cryptarithm1`, + `gcd`, `mandel`, `mandel2`, `puzzle`, `rsa`, and `sphere`. + +Most ports need only the language header. `Example.funnySlash` now +declares its generic callable parameter interface, and `rsa.int_if_char` declares +its string parameter. `Example.Test(...)` remains a direct constructor call: +needing `new` there was an overload-resolution bug, not a language rule. The +prelude now declares callable `Error` overloads, so existing `Error(...)` calls +also remain unchanged. `Term.codegen` emits the new language header when it +regenerates quotation fixtures, preserving their migration during runtime tests. +`Option` now declares its generic parameter, callback, and return interfaces. +No visibility changes or failure suppressions were added to make fixtures pass. +DOM and filesystem entry points are explicitly `dyn` in the prelude for now; +fixture parameters were not made dynamic to bypass missing interfaces. + +### Fixes found while porting + +- Ordinary values select function/constructor overloads; selection receivers + select companion modules. This distinction survives nested captures and opens. + Function/module overloads are accepted under new resolution, and interface + exposure checks both overloads. `newres/OverloadedCalls.mls` exercises direct, + generic, stored-function, module-member, and captured uses. +- Discarding captures for constructor-pattern resolution now traverses every + nested capture. Previously, parser fixtures compiled but tested the generated + constructor function instead of its class, producing runtime match errors. +- The prelude declares `Array.at`, `fill`, and `splice`, common `Str` and `Num` + methods, and the basic Set, Map, and RegExp interfaces. Primitive literals use + those declared interfaces, including inheritance (`Int` extends `Num`). + `newres/Arrays.mls` and `PrimitiveMembers.mls` cover execution and missing-member + diagnostics. The previously expected failure for string `repeat` now succeeds. +- Calls to bodyless foreign method declarations use foreign-call normalization, + even when `declare` appears on their enclosing class rather than the method. + In particular, an out-of-range `Array.at` returns unit through the existing + JavaScript `undefined` normalization instead of failing an MLscript-call check. +- Foreign callable constructor declarations cover `Error`, `TypeError`, + `RangeError`, `Array`, and `String`. The JS backend uses their existing host + class values for `new` and patterns, without an MLscript `.class` wrapper. +- The prelude's own diff test now parses it as one compilation unit, matching + normal compiler loading so mutually referring declarations can cross blank + lines. `newres/HostInterfaces.mls` checks dynamic DOM and filesystem access + without requiring a DOM in the test VM. +- Assignment lowering accepts resolved member references and uses the existing + symbol assignment checks. `newres/MemberAssignments.mls` covers a mutable field + accessed by name inside a method. + +### Remaining work and design boundaries + +Some deferred fixtures need source interfaces rather than compiler changes: +public callback parameters need function types, and public receivers need a +nominal or structural interface. For example, `QuoteExample.bind` calls its +unannotated `k`, and many Nofib helpers expose unannotated comparators or printers. +Choosing which helpers should instead be private changes the exported API and +was not done automatically. Annotation boundaries must remain opaque. + +Other failures require compiler or prelude work: + +1. **Legacy consumers inspecting new-resolution return annotations.** `Option` + compiles after adding generic callback/parameter interfaces. In the attempted + port, `getOrElse` has result annotation `A` and `flatMap` has `Option[B]`. + The legacy consumer `apps/parsing/Parser.mls` calls these functions (for + example, `getOrElse` at line 101 and `flatMap` at line 167). Its + `Resolver.resolveType` calls `resolveSign` on the imported return annotation; + that reads `legacyResolvedSym` from new-resolution syntax and fails with + `Legacy symbol query on a new-resolution term`. The application run reports + this failure for `Parser`, `Test`, `ParseRuleVisualizer`, and + `parsing-web-demo/main`; the latter three also depend on `Parser`. + `Option.mls` is now ported; the affected dependency graph imports the temporary + `LegacyOption.mls`, preserving shared constructor identities. + Its comment requires deletion when those consumers are ported. + `apps/parsing/BasicExpr` and `Expr` compile with just the header but their + legacy worksheet consumers encounter the same boundary, so these two remain + in their original mode. A general interoperability bridge would need to consume + completed type information without re-resolving imported nodes or querying + erased types before erasure. +2. **Flow through patterns and mutation.** Tuple bindings, guarded/transforming + patterns, assignment results, and mutable container element types still leave + some selections without shapes. `Char.AnyChar` loses the string shape before + its `length` guard; CSV's mutable nested array loses its element interface. + Assuming initializer shapes survive mutation would be unsound. +3. **Remaining host interfaces.** `Array.reduce` needs its accumulator/callback + contracts; `Map.set` and `Reflect.set` need keyword-named declarations; + WebAssembly exports still need interfaces. DOM and filesystem entry points + now use `dyn`; `HTMLElement` retains its nominal superclass identity with + dynamic host members. Implicit generated + `toString` methods on user classes also need a declared resolution interface. +4. **Argument grouping and imported lowering forms.** Several parser APIs take + one record argument but currently see its named fields as multiple positional + arguments. `Parser` also encounters a synthesized selection during new + resolution. These require changes to argument interpretation and the import + boundary, respectively. +5. **Quotation coverage.** Quasiquote lowering still lacks some new type-selection + and wildcard-reference forms (`CSP`, `QuoteExample1`). + +### Deferred compilation inventory + +Paths below are relative to `mlscript-compile/`. Each row gives a concrete +remaining obstruction, not a claim that it is the only one. Deferred files keep +legacy resolution; imports that exchange options use `LegacyOption` consistently. + +| Main fixture | Observed obstruction | +| --- | --- | +| `Benchmark.mls` | Exposed `suite.run` and callback interfaces. Filesystem access is now dynamic. | +| `Block.mls` | `Str.replaceAll`, `Any.toString`, and pattern-value interfaces. | +| `CSP.mls` | Quasiquote type selections and wildcard opens. | +| `CachedHash.mls` | Interface for the generated `this.toString` method. | +| `Char.mls` | `AnyChar` loses the string shape before its `length` guard. | +| `FingerTreeList.mls` | `Array.reduce` and numeric projection on an array rather than a fixed tuple. | +| `Iter.mls` | Exposed callbacks and iterator `next` interfaces. | +| `LazyArray.mls` | `null.next` and missing pattern-field shapes through mutation. | +| `LazyFingerTree.mls` | Exposed `xs.length` and pattern-field interfaces. | +| `MutMap.mls` | Exposed `m.underlying` and keyword-named `Map.set`. | +| `ObjectBuffer.mls` | Exposed `cls.size` and constructor interfaces. | +| `LegacyOption.mls` | Temporary copy for legacy consumers; delete when those consumers are ported. | +| `Predef.mls` | Exposed generic callbacks and rest-argument `.call`. | +| `QuoteExample.mls` | Exposed callback `k` in `bind`. | +| `QuoteExample1.mls` | Quasiquote wildcard-open references. | +| `Rendering.mls` | Nullable string flow, callbacks, and pattern-field interfaces. | +| `Runtime.mls` | `Map.set` and nullable `contTrace` flow. | +| `Shape.mls` | Pattern-derived receivers for `join`, `every`, `length`, and `name`. | +| `Stack.mls` | Exposed `arr.length` and predicate callbacks. | +| `Term.mls` | `Map.set` and generic pattern/value interfaces. | +| `TreeTracer.mls` | Exposed `message.split` and pattern-reference forms. | +| `XML.mls` | Exposed `value.toValue`. | +| `ups/EvaluationContext.mls` | Exposed `target.freeVars` and context interfaces. | + +| Application fixture | Observed obstruction | +| --- | --- | +| `apps/parsing/BasicExpr.mls`, `Expr.mls` | Compile independently; legacy worksheet consumers inspect new-resolution signatures. | +| `apps/parsing/Accounting.mls` | Array callback arity, `reduce`, and receiver interfaces. | +| `apps/parsing/CSV.mls` | Mutable nested-array elements lack the shape required for `push`. | +| `apps/parsing/Extension.mls` | Named record fields counted as three arguments instead of one. | +| `apps/parsing/Keywords.mls` | Generic/abstract string interfaces and patterns. | +| `apps/parsing/Lexer.mls` | Exposed `options.noWhitespace` and pattern interpretations. | +| `apps/parsing/ParseRule.mls` | Exposed `keyword.name` and subsequent patterns. | +| `apps/parsing/ParseRuleVisualizer.mls` | Imported JS railroad API lacks member interfaces. | +| `apps/parsing/Parser.mls` | Synthetic selections from imported legacy forms reach new resolution. | +| `apps/parsing/Rules.mls` | Named-record argument grouping. | +| `apps/parsing/Test.mls` | `flags.has` and `tracer.reset` lose shapes through tuple/import flow. | +| `apps/parsing/Token.mls` | Named-record grouping and exposed `literal.length`. | +| `apps/parsing/Tree.mls` | Constructor-field shapes for `length` and `slice`. | +| `apps/parsing/TreeHelpers.mls` | Exposed `text.split` and generated `Tree.toString` interface. | +| `apps/parsing-web-demo/Examples.mls` | Named-record argument grouping in `MutMap.insert`. | +| `apps/parsing-web-demo/main.mls` | String callback interfaces, dynamic receiver flow through aliases/mutation, and tuple-bound `example.name`. DOM declarations themselves are now dynamic. | + +| Nofib fixture (under `nofib/`) | Observed obstruction | +| --- | --- | +| `NofibPrelude.mls`, `ansi.mls`, `atom.mls`, `awards.mls`, `constraints.mls`, `cse.mls`, `fish.mls`, `integer.mls`, `lambda.mls`, `lastpiece.mls`, `life.mls`, `mate.mls`, `minimax.mls`, `para.mls`, `power.mls`, `pretty.mls`, `primetest.mls`, `scc.mls`, `secretary.mls` | Exposed callback parameters need callable interfaces. | +| `eliza.mls`, `knights.mls`, `sorting.mls` | Unannotated string receivers and callbacks. | +| `circsim.mls` | Exposed record receivers such as `p.pid` and `p.compType`. | +| `cryptarithm2.mls` | Callable interface for an unannotated constructor field. | +| `cichelli.mls` | Generated `toString` interfaces and callbacks. | +| `treejoin.mls` | Generated `toString` interfaces and pattern flow. | +| `lcss.mls` | Function result has no interface for `toString`. | + +The remaining WASM fixture, `wasm/Wasm.mls`, needs interfaces for +`WebAssembly.Instance.exports` and its exposed `wasmInst.imports` receiver. + ## Straightforward fixes applied 1. Named record/tuple fields elaborate their values with the enclosing @@ -435,10 +619,11 @@ These changes also fix the sibling-subclass field-extraction regression in 5. **Complete type/interface and call validation.** Preserve opaque annotations and marked generic flow; finish generic validation, rest-parameter signatures, module checks, and the WASM migration. Keep cross-block method work coordinated externally. -6. **Migrate shared compilation fixtures from leaves upward.** Begin with the - four UPS fixtures; then application parsing data types, lexer, parser helpers, - and entry points. Rebuild `.mjs` dependencies before each worksheet batch. - Verify cache/import behavior across old and new consumers during transition. +6. **Continue the audited compilation-fixture migration.** Port `LegacyOption` + consumers and remove the temporary copy. Resolve the legacy-consumer signature + boundary for `BasicExpr` and `Expr`; then add + exposed interfaces and complete the pattern/host-interface gaps listed above. + Rebuild `.mjs` dependencies and run existing consumers for each batch. 7. **Remove the remaining legacy suite configurations.** Require no new failure suppressions, no lost negative diagnostics, reviewed goldens, and a successful `hkmc2AllTests/test`. Commit code and golden outputs together under the agent's @@ -446,19 +631,23 @@ These changes also fix the sibling-subclass field-extraction regression in ## Validation -- `ctest`: all 47 selected compilation tests pass. -- `catest`: all 20 application compilation fixtures pass in their existing mode. +- `ctest`: all 48 selected compilation tests pass. +- `catest`: all 20 application compilation fixtures pass with the retained mixed-mode ports. +- `cntest`: all 39 Nofib compilation fixtures pass. - `cwtest`: the WASM compilation fixture passes. -- `hkmc2AllTests/test`: all 951 tests pass, including 708 main tests, 18 application +- `hkmc2AllTests/test`: all 963 tests pass, including 715 main tests, 18 application tests, and 22 WASM tests. The main count includes directory configurations and new-resolution regressions; it is not a count of migrated worksheets. -- All retained ports have reviewed goldens. Deferred worksheets retain their - original source and output; no trial-generated application report changes remain. +- All retained ports have reviewed goldens. Legacy consumers that require the + compatibility option constructors import `LegacyOption`; reflection uses that + same copy as `Block.mls`. No trial-generated failures remain in golden outputs. ## Deferred-file inventory -The following are the first observed trial failures, which may be symptoms rather -than root causes. Each path is relative to `hkmc2/shared/src/test/mlscript/`. +The following are the first observed worksheet-trial failures, which may be +symptoms rather than root causes. This inventory predates the compilation-fixture +batch above; its overload and primitive-member fixes may unblock additional +worksheets, which have not been re-audited in this batch. Each path is relative to `hkmc2/shared/src/test/mlscript/`. The files themselves retain the pre-trial configuration and output. ### basics diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 43636eb5a3..b899dfe692 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1080,7 +1080,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case sym: sem.BlockMemberSymbol => sym.trmImplTree.fold(sym.clsTree.isDefined)(_.k is syntax.Fun) case sym: sem.TermSymbol => - (sym.k is syntax.Fun) && sym.defn.forall(!_.hasDeclareModifier.isDefined) + // Methods inside a declared class can have no local `declare` modifier. + // Only implementations (and generated constructors) promise the MLscript + // calling convention; foreign declarations need undefined normalization. + (sym.k is syntax.Fun) && sym.defn.exists(d => !d.hasDeclareModifier.isDefined && + (d.body.isDefined || sym.isInstanceOf[ClassCtorSymbol])) // Do not perform safety check on `MatchSuccess` and `MatchFailure`. case sym => (sym is State.matchSuccessClsSymbol) || (sym is State.matchFailureClsSymbol) @@ -1212,6 +1216,14 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case Ref(sym) => subTerm(rhs): r => assignSymbol(resolvedSelectionSymbol.getOrElse(sym), sym, r, k(unit), trm.toLoc) + case ref: MemberRef => + if ref.isErroneous then compError else ref.resolvedTargets.distinct match + case target :: Nil => + subTerm(rhs): r => + assignSymbol(target, ref.sym, r, k(unit), trm.toLoc) + case _ => fail: + ErrorReport(msg"Assignment requires one resolved member" -> ref.toLoc :: Nil, + source = Diagnostic.Source.Compilation) case ref: UnresolvedRef => openSelection(ref): (prefix, member, target) => target match 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 7437b6d4c6..816aa9d864 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -1257,7 +1257,9 @@ object JSBuilder: * symbol are not affected. */ def shouldBeLifted: Bool = val bsym = dsym.asBlkMember - ( + // Foreign callable constructors are already class values. Their declared + // function overload does not create an MLscript wrapper or `.class` slot. + !dsym.defn.exists(_.hasDeclareModifier.isDefined) && ( (dsym.asTrm orElse bsym.flatMap(_.asTrm)).isDefined || (dsym.asCls orElse bsym.flatMap(_.asCls)).flatMap(_.defn).exists(_.paramsOpt.isDefined) ) && diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 883b755d62..9a951b9e4f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -482,7 +482,7 @@ object Elaborator: * a runtime value merely because they use the same reference syntax. */ enum Interpretation: - case Trm, Clss, Ptrn, Tpe + case Trm, Receiver, Clss, Ptrn, Tpe enum Mode: case Full @@ -719,7 +719,7 @@ end Elaborator import Elaborator.* -import Elaborator.Interpretation.{Trm, Clss, Ptrn, Tpe} +import Elaborator.Interpretation.{Trm, Receiver, Clss, Ptrn, Tpe} class Elaborator(val tl: TraceLogger, val wd: io.Path, val prelude: Ctx) @@ -1211,6 +1211,7 @@ extends Importer: private def interpretRef(ref: Term, interp: Interpretation): Term = if newResolution then interp match case Trm => requireTerm(ref) + case Receiver => listenReceiver(ref)(_ => ()) case Tpe => ref.typeInterpretation = S(typeResolution(ref)) case _ => () ref @@ -1240,7 +1241,7 @@ extends Importer: app(lt, rt)(tree, N, sym) def elaborateSelection(tree: Sel): Term = - val preTrm = subterm(tree.prefix) + val preTrm = subterm(tree.prefix, Receiver) if newResolution then val res = new Term.NewSel(preTrm, tree.name, N)(FlowSymbol.sel(tree.name.name)).withLocOf(tree) // listenTerm(preTrm, shape => selShape2(shape, tree.name, res)) @@ -1913,7 +1914,8 @@ extends Importer: val bareClassOverload = newResolution && ((k is Fun) || k.isInstanceOf[Val]) && (defn match case td: TypeDef => (td.k is Cls) && td.paramLists.isEmpty case _ => false) - if !supportedOverloadings(k -> defn.k) && !bareClassOverload then raise: + val functionModuleOverload = newResolution && (k is Fun) && (defn.k is Mod) + if !supportedOverloadings(k -> defn.k) && !bareClassOverload && !functionModuleOverload then raise: ErrorReport: if notYetSupportedOverloadings(k -> defn.k) then msg"Not yet supported: overloading of ${k.desc} '$name'" -> mainDefn.toLoc diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index ce76fd9ac4..6fc527b618 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -57,7 +57,11 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) private def member(symbol: BlockMemberSymbol, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = if (symbol.getState is rstate.owner) && (symbol.asModOrObj.isDefined || symbol.asTrm.isDefined || symbol.asCls.isDefined) then val ref = source(symbol) - watch((symbol, marks))(resolver.fromBMS(symbol, ref.resSym, marks, _, ref, _ => ()))(emit(_, path)) + watch((symbol, marks))(resolver.fromBMS(symbol, ref.resSym, marks, _, ref, _ => (), false))(emit(_, path)) + // Both overloads are public: selecting a module member must not hide the + // function's inputs, and exposing a function must not hide module members. + if symbol.asModOrObj.isDefined && symbol.asTrm.isDefined then + watch((symbol, marks, true))(resolver.fromBMS(symbol, ref.resSym, marks, _, ref, _ => (), true))(emit(_, path)) // A term companion can hide the constructor in term position, but clients // still have access to the class through `new` and class projections. if symbol.asModOrObj.isDefined || symbol.asTrm.exists(!_.isInstanceOf[ClassCtorSymbol]) then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 701880d83f..21f6537bff 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -265,9 +265,9 @@ class NewResolver: }) private def listenDeclaredMember(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], flow: FlowSymbol, - source: Term, selected: ShapeListener[DefinitionSymbol[?]])(listener: Listener)(using NewResolverState): Unit = + source: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(listener: Listener)(using NewResolverState): Unit = member.onComplete: () => - member.asModOrObj.orElse(member.asTrm).orElse(member.asCls) match + valueTarget(member, receiver) match case S(symbol: TermSymbol) if !symbol.isInstanceOf[ClassCtorSymbol] => selected(symbol) val td = symbol.defn.get @@ -307,7 +307,7 @@ class NewResolver: case _ => // A nested nominal declaration denotes its statically selected symbol; // selecting it does not inspect an instance field or method body. - fromBMS(member, flow, Nil, listener, source, selected) + fromBMS(member, flow, Nil, listener, source, selected, receiver) def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])])(using rs: NewResolverState): Unit = rs.report: ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) @@ -705,7 +705,7 @@ class NewResolver: case S(cls) => classPattern(cls) case N => softAssert(false, "Completed pattern member has no definition") case N => - fromBMS(bms, FlowSymbol.pat()(using rstate.owner), sh.markss, valuePattern, lhs, _ => ()) + fromBMS(bms, FlowSymbol.pat()(using rstate.owner), sh.markss, valuePattern, lhs, _ => (), false) case sh: TermShape => valuePattern(sh) /** Propagate possible values to pattern bindings. Constructor tests filter by @@ -742,7 +742,7 @@ class NewResolver: val field = symShapes.getOrElseUpdate((sym, resSym, marks), SymShape(sym, resSym, marks)) matchShapePat(field, pat)(_ => ()) case MemberLookup.Declared(member, bindings, marks) => - listenDeclaredMember(member, bindings, resSym, ctor.target, _ => ()): field => + listenDeclaredMember(member, bindings, resSym, ctor.target, _ => (), false): field => field.exit(marks) match case value: TermShape => val field = (value.applicationHead._1, unknown) match @@ -784,7 +784,7 @@ class NewResolver: case _ => check(sh, N) shape match case sh: TermShape => narrow(sh) - case sh: SymShape => fromSymbol(sh, narrow, ctor.target, _ => ()) + case sh: SymShape => fromSymbol(sh, narrow, ctor.target, _ => (), false) ctor.subscribeToShapes(listenConstructor) case Pattern.Tuple(_, _) => () // Tuple binding shapes are not inferred yet. case _ => @@ -936,7 +936,7 @@ class NewResolver: def unresolvedRef(ref: UnresolvedRef)(using NewResolverState): Unit = ref.prefixes.foreach: prefix => - listenTerm(prefix): shape => + listenReceiver(prefix): shape => shape.getMember(ref.id.name) match case MemberLookup.Found(member, marks) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member.memberSymbol) => val candidate = prefix -> member.memberSymbol @@ -992,7 +992,7 @@ class NewResolver: case sh: SymShape => completedClass(sh)(cls => select(cls, ExitMark(ResolutionBoundary(cls.sym), S(sh.resSym), NoMarks) :: sh.markss), - () => fromSymbol(sh, value, trm, _ => ())) + () => fromSymbol(sh, value, trm, _ => (), false)) case sh: TermShape => value(sh) def newSel(sel: NewSel)(using NewResolverState): Unit = @@ -1025,7 +1025,7 @@ class NewResolver: // selection, but cannot choose a different member for that old syntax. case _ => () sel.cls match - case N => listenTerm(sel.prefix): shape => + case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc) case S(cls) => @@ -1157,20 +1157,20 @@ class NewResolver: listener(N) private def fromSymbol(shape: SymShape, listener: Listener, source: Term, - selected: ShapeListener[DefinitionSymbol[?]])(using NewResolverState): Unit = shape match + selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(using NewResolverState): Unit = shape match case declared: DeclaredSymShape => - listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, selected): value => + listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, selected, receiver): value => value.exit(shape.markss) match case value: TermShape => listener(value) case NoShape => () - case _ => fromBMS(shape.sym, shape.resSym, shape.markss, listener, source, selected) + case _ => fromBMS(shape.sym, shape.resSym, shape.markss, listener, source, selected, receiver) def fromBMS(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: Listener, - trm: Term, selected: ShapeListener[DefinitionSymbol[?]])(using NewResolverState) = + trm: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(using NewResolverState) = log(s"listenBMS: bms = ${bms.describe}") bms.onComplete: () => log(s"listenedBMS: bms = ${bms.describe}") - bms.asModOrObj orElse bms.asTrm orElse bms.asCls match + valueTarget(bms, receiver) match case S(sym: (ModuleOrObjectSymbol | TermSymbol | ClassSymbol)) => // Selection is independent of the selected value's shape. In particular, // an assignment needs its target even if the value has no inferred shape. @@ -1285,18 +1285,34 @@ class NewResolver: resolError(trm, msg"An annotation must have a known main symbol." -> N :: Nil) N + /** Values prefer the function/constructor overload; selection receivers prefer + * the module overload. Keep that choice through captures so nested functions + * and opened names use the same interpretation as direct references. + */ + private def valueTarget(member: BlockMemberSymbol, receiver: Bool): Opt[DefinitionSymbol[?]] = + if receiver then member.asModOrObj.orElse(member.asTrm).orElse(member.asCls) + else member.asTrm.orElse(member.asModOrObj).orElse(member.asCls) + def listenTerm(trm: Term)(listener: Listener)(using NewResolverState): Unit = log(s"listenTerm: trm = ${trm.showDbg}") - listen(trm): - case sh: TermShape => - listener(sh) + listenValue(trm, false)(listener) + + def listenReceiver(trm: Term)(listener: Listener)(using NewResolverState): Unit = + listenValue(trm, true)(listener) + + private def listenValue(trm: Term, receiver: Bool)(listener: Listener)(using NewResolverState): Unit = trm match + case Capture(base, thru) => + listenValue(base, receiver): shape => + listener(MarkedShape.enter(shape, ResolutionBoundary(thru), N)) + case _ => listen(trm): + case sh: TermShape => listener(sh) case ss: SymShape => fromSymbol(ss, listener, trm, sym => trm.withoutCaptures match case ref: NewResolvable => rstate.recordResolution(ref, ref.resolvedTargets.contains(sym))(ref.resolvedTargets ::= sym) case _ => () - ) + , receiver) /** Subscribe to spread operands once for each tuple or record AST node. Record * the node before calling start: a recursive spread can call listen on the same @@ -1329,7 +1345,7 @@ class NewResolver: case (callee: (DefnShape | CallableTypeShape), marks) => applyTypeArguments(callee, marks, args, trm) case _ => () shape match - case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => ()) + case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => (), false) case value: TermShape => instantiate(value) listener(shape) case Mut(underlying: Tup) => listenTerm(underlying): @@ -1402,11 +1418,30 @@ class NewResolver: case _ :: rest => expand(rest, reversed) expand(record.stats, Nil) case intro: IntroTerm => - val sh = introShapes.getOrElseUpdate(new Identity(intro), { - log(s"introShape: intro = $intro") - IntroShape(intro) - }) - listener(sh) + // Literals have the declared primitive interface, including inherited + // members. Retain their literal shape for pattern tests and provenance. + val primitive = intro match + case Lit(_: Tree.StrLit) => S(prelude.builtins.Str) + case Lit(_: Tree.IntLit) => S(prelude.builtins.Int) + case Lit(_: Tree.DecLit) => S(prelude.builtins.Num) + case Lit(_: Tree.BoolLit) => S(prelude.builtins.Bool) + case _ => N + def publish(parent: Opt[NominalTypeShape])(using NewResolverState): Unit = + listener(introShapes.getOrElseUpdate(new Identity(intro), { + log(s"introShape: intro = $intro") + IntroShape(intro, parent) + })) + primitive match + case N => publish(N) + case S(cls) => + val tpe = rstate.primitiveTypes.getOrElseUpdate(cls, { + val resolution = new TypeResolution(intro, messages => resolError(intro, messages)) + resolution.publish(TypeShape.Nominal(cls.defn.get)) + declaredType(resolution, Map.empty) + }) + listenTypeValues(tpe): + case nominal: NominalTypeShape => publish(S(nominal)) + case _ => softAssert(false, "Primitive class must expose a nominal interface") case Ref(sym) if sym is sym.getState.globalThisSymbol => listener(DynShape()) case SelfRef(sym) if sym is sym.getState.globalThisSymbol => listener(DynShape()) case ref @ Ref(loc: LocalSymbol) => @@ -1442,7 +1477,7 @@ class NewResolver: listener(sh) case Capture(base, thru) => if discardMarks then - listen(base)(listener) + listen(base, discardMarks = true)(listener) else listenTerm(base): sh => listener(MarkedShape.enter(sh, ResolutionBoundary(thru), N)) case ref @ Ref(sym: InnerSymbol) => // TODO: remove remaining occurrences of such refs diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index a57fe0e725..01b1801ace 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -187,6 +187,8 @@ final class NewResolverState private ( new Cache(source.map(_.typeInterpretations), identity) val typeValues: Cache[DeclaredType, TypeValues] = new Cache(source.map(_.typeValues), identity) + val primitiveTypes: Cache[ClassSymbol, DeclaredType] = + new Cache(source.map(_.primitiveTypes), identity) val abstractTypes: Cache[TypeResolution, DeclaredType] = new Cache(source.map(_.abstractTypes), identity) val signatureParameters: Cache[VarSymbol, DeclaredType] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index c8e31d3d68..c611e99859 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -438,7 +438,7 @@ class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTer defn match case defn: ModuleOrObjectDef => MemberLookup.inClass(defn, ext, name) - case defn: TermDefinition => ??? + case _: TermDefinition => MemberLookup.Missing case _ => MemberLookup.Missing def toLoc: Opt[Loc] = defn.sym.toLoc @@ -619,13 +619,15 @@ object RecordShape: type IntroTerm = Term.Lit | Term.UnitVal | Term.Lam //| Term.New -class IntroShape(val trm: IntroTerm) extends NonAppTermShape: +class IntroShape(val trm: IntroTerm, val primitive: Opt[NominalTypeShape]) extends NonAppTermShape: def describe: Str = trm.describe protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = trm match - case _: Term.Lit | _: Term.UnitVal => MemberLookup.Missing // TODO: methods on literals + case _: Term.Lit | _: Term.UnitVal => primitive.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)) case lam: Term.Lam => MemberLookup.Missing // TODO: methods on lambdas // case newTerm: Term.New => // Map.empty // TODO + override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = + primitive.exists(_.isInstanceOfClass(cls)) def toLoc: Opt[Loc] = trm.toLoc override def toString: Str = s"IntroShape(${trm})" diff --git a/hkmc2/shared/src/test/mlscript-compile/Block.mls b/hkmc2/shared/src/test/mlscript-compile/Block.mls index ef323a67d6..39b31fe967 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Block.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Block.mls @@ -1,15 +1,15 @@ import "./Predef.mls" -import "./Option.mls" +import "./LegacyOption.mls" import "./StrOps.mls" import "./Runtime.mls" open Predef open StrOps -open Option +open LegacyOption module Block with... -type Opt[A] = Option[A] +type Opt[A] = LegacyOption[A] // dependancies referenced in Block classes, referencing implementation in Term.mls diff --git a/hkmc2/shared/src/test/mlscript-compile/Example.mls b/hkmc2/shared/src/test/mlscript-compile/Example.mls index b1798ed94b..2aa1e4e726 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Example.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Example.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) import "./Predef.mls" open Predef @@ -12,7 +13,7 @@ module Example with ... fun pubFun = privFun -fun (/) funnySlash(f, arg) = f(arg) +fun (/) funnySlash[A, B](f: A -> B, arg: A) = f(arg) fun inc(x) = x + 1 diff --git a/hkmc2/shared/src/test/mlscript-compile/FilePrivateFunctions.mls b/hkmc2/shared/src/test/mlscript-compile/FilePrivateFunctions.mls index 4e51b387f9..1035316caa 100644 --- a/hkmc2/shared/src/test/mlscript-compile/FilePrivateFunctions.mls +++ b/hkmc2/shared/src/test/mlscript-compile/FilePrivateFunctions.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./Predef.mls" open annotations { noInline } diff --git a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls index b9c84587af..f3e4e921f9 100644 --- a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls +++ b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls @@ -1,7 +1,7 @@ -import "./Option.mls" +import "./LegacyOption.mls" import "./Iter.mls" -open Option +open LegacyOption open Iter open FingerTreeList diff --git a/hkmc2/shared/src/test/mlscript-compile/Iter.mls b/hkmc2/shared/src/test/mlscript-compile/Iter.mls index 78eb2ca8fd..bbc4b4943d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Iter.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Iter.mls @@ -1,9 +1,9 @@ #config(liftDefns: Some(LiftDefns)) -import "./Option.mls" +import "./LegacyOption.mls" import "./Stack.mls" -open Option { Some, None } +open LegacyOption { Some, None } open Stack { Nil, Cons } diff --git a/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls b/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls new file mode 100644 index 0000000000..a37ec27a66 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls @@ -0,0 +1,33 @@ +// Temporary copy for consumers that still inspect signatures with the legacy resolver. +// Delete this file when its consumers are ported to new resolution, and change +// their imports back to Option.mls. Consumers that exchange Some/None values +// must use the same copy so constructor identities agree. + + +type LegacyOption[A] = LegacyOption.{ Some(A) | None } + +module LegacyOption with ... + + +data class Some[T](value) +object None + +fun isDefined(x) = if x is + Some then true + None then false + +data class Both(fst, snd) + + +fun getOrElse(opt, default) = if opt is + Some(value) then value + None then default + +fun flatMap(opt, f) = if opt is + Some(value) then f(value) + None then None + +module unsafe with + fun get(opt) = if opt is + Some(value) then value + None then throw Error("None.get") diff --git a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls index 34ebbbf402..dad5cc1a75 100644 --- a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls +++ b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls @@ -1,8 +1,8 @@ -import "./Option.mls" +import "./LegacyOption.mls" import "./Predef.mls" open Predef -open Option { Some, None } +open LegacyOption { Some, None } module MutMap with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/NestedFunctionsNoLift.mls b/hkmc2/shared/src/test/mlscript-compile/NestedFunctionsNoLift.mls index a6c89eb4c0..b7baadfd99 100644 --- a/hkmc2/shared/src/test/mlscript-compile/NestedFunctionsNoLift.mls +++ b/hkmc2/shared/src/test/mlscript-compile/NestedFunctionsNoLift.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + #config(liftDefns: None) import "./Predef.mls" diff --git a/hkmc2/shared/src/test/mlscript-compile/NoFreeze.mls b/hkmc2/shared/src/test/mlscript-compile/NoFreeze.mls index 9e73654ed6..7a4b4ceaad 100644 --- a/hkmc2/shared/src/test/mlscript-compile/NoFreeze.mls +++ b/hkmc2/shared/src/test/mlscript-compile/NoFreeze.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + #config(noFreeze: true) #config(noModuleCheck: true) diff --git a/hkmc2/shared/src/test/mlscript-compile/Option.mls b/hkmc2/shared/src/test/mlscript-compile/Option.mls index e6a48900fc..1d2320f944 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Option.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Option.mls @@ -1,30 +1,32 @@ -type Option[A] = Option.{ Some(A) | None } +#lang(0.3.x) + +type Option[A] = Option.{ Some[A] | None } module Option with ... -data class Some[T](value) +data class Some[T](value: T) object None -fun isDefined(x) = if x is +fun isDefined[A](x: Option[A]): Bool = if x is Some then true None then false data class Both(fst, snd) -fun getOrElse(opt, default) = if opt is +fun getOrElse[A](opt: Option[A], default: A): A = if opt is Some(value) then value None then default -fun flatMap(opt, f) = if opt is +fun flatMap[A, B](opt: Option[A], f: A -> Option[B]): Option[B] = if opt is Some(value) then f(value) None then None module unsafe with - fun get(opt) = if opt is + fun get[A](opt: Option[A]): A = if opt is Some(value) then value None then throw Error("None.get") diff --git a/hkmc2/shared/src/test/mlscript-compile/QuoteExample2.mls b/hkmc2/shared/src/test/mlscript-compile/QuoteExample2.mls index 194eb24237..f95b092d17 100644 --- a/hkmc2/shared/src/test/mlscript-compile/QuoteExample2.mls +++ b/hkmc2/shared/src/test/mlscript-compile/QuoteExample2.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./Term.mls" import "./QuoteExample.mls" import "./QuoteExample1.mls" diff --git a/hkmc2/shared/src/test/mlscript-compile/Record.mls b/hkmc2/shared/src/test/mlscript-compile/Record.mls index c403501770..dc27aa273a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Record.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Record.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./Predef.mls" open Predef diff --git a/hkmc2/shared/src/test/mlscript-compile/Shape.mls b/hkmc2/shared/src/test/mlscript-compile/Shape.mls index d8a3dcca30..f24e53e3f0 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Shape.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Shape.mls @@ -1,8 +1,8 @@ import "./Block.mls" -import "./Option.mls" +import "./LegacyOption.mls" open Block { Literal, ClassSymbol, showSymbol, isPrimitiveType, isPrimitiveTypeOf } -open Option +open LegacyOption type Shape = Shape.Shape @@ -57,7 +57,7 @@ fun silh(p: Case): Shape = if p is Class(sym, Array(size).fill(Dyn)) Block.Tup(n) then Arr(Array(n).fill(Dyn)) -// TODO: use Option instead, since all of them return at most one shape +// TODO: use LegacyOption instead, since all of them return at most one shape fun filter(s: Shape, p: Case): Array[Shape] = if [s, p] is [Lit(l1), Block.Lit(l2)] and l1 == l2 then [s] diff --git a/hkmc2/shared/src/test/mlscript-compile/StrOps.mls b/hkmc2/shared/src/test/mlscript-compile/StrOps.mls index 2215b041ff..cc0e948a32 100644 --- a/hkmc2/shared/src/test/mlscript-compile/StrOps.mls +++ b/hkmc2/shared/src/test/mlscript-compile/StrOps.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + module StrOps with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/Term.mls b/hkmc2/shared/src/test/mlscript-compile/Term.mls index 6f71a4541c..b14424ebc6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Term.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Term.mls @@ -173,7 +173,7 @@ fun codegen(t, file) = if not fs.existsSync(file) do fs.writeFileSync(file, "", "utf8") let originData = fs.readFileSync(file, "utf8") - let newData = StrOps.concat of dependencies.join("\n"), "\n", code + let newData = StrOps.concat of "#lang(0.3.x)\n\n", dependencies.join("\n"), "\n", code if newData != originData do fs.writeFileSync(file, newData, "utf8") diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls index c3fa60c204..71897207ad 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls @@ -16,7 +16,7 @@ import "../parsing/Lexer.mls" import "../../StrOps.mls" import "../../Iter.mls" import "../../XML.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Runtime.mls" import "../../Predef.mls" import "../parsing/TreeHelpers.mls" @@ -28,7 +28,7 @@ import "./Examples.mls" open XML { elem } open Predef -open Option { Some, None } +open LegacyOption { Some, None } open Examples module Main with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls index df342e0e2e..a8eb7e9b24 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls @@ -1,9 +1,9 @@ import "../../StrOps.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Predef.mls" open StrOps { parenthesizedIf } -open Option { Some, None } +open LegacyOption { Some, None } open Predef { mkStr } type OptionT[A] = Some[A] | None diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls index 0b8bfeddca..711fbda317 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls @@ -1,9 +1,9 @@ import "../../StrOps.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Predef.mls" open StrOps { parenthesizedIf } -open Option { Some, None } +open LegacyOption { Some, None } type OptionT[A] = Some[A] | None diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls index 345201a51f..409677de87 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls @@ -4,14 +4,14 @@ import "./Token.mls" import "./ParseRule.mls" import "./Tree.mls" import "../../Stack.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Predef.mls" import "../../Iter.mls" import "../../MutMap.mls" open Predef open Stack -open Option { Some, None } +open LegacyOption { Some, None } open Token { LiteralKind } open ParseRule { Choice } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls index a13ea96d87..c6d92e7fdd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls @@ -1,10 +1,10 @@ -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Iter.mls" import "../../Predef.mls" import "../../MutMap.mls" open Predef -open Option { Some, None } +open LegacyOption { Some, None } module Keywords with ... @@ -108,7 +108,7 @@ let precMap = makePrecMap of "." // The largest precedence value of all operators. -val periodPrec = precMap |> MutMap.get(".") |> Option.unsafe.get +val periodPrec = precMap |> MutMap.get(".") |> LegacyOption.unsafe.get val _period = keyword of ".", Some(periodPrec), Some(periodPrec) @@ -117,7 +117,7 @@ val appPrec = maxOperatorPrec - 1 val prefixPrec = appPrec - 1 // Get the precedence of a single character operator. -fun charPrec(op) = precMap |> MutMap.get(op) |> Option.unsafe.get +fun charPrec(op) = precMap |> MutMap.get(op) |> LegacyOption.unsafe.get fun charPrecOpt(op) = precMap |> MutMap.get(op) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls index 769521dab5..b9184181c4 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls @@ -7,14 +7,14 @@ open Predef import "../../Char.mls" import "../../Stack.mls" import "../../StrOps.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Iter.mls" import "./Token.mls" open Stack open StrOps -open Option { Some, None} +open LegacyOption { Some, None} open Token { LiteralKind, LineLookupTable } type TokenType = Token.Token diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls index dc227a8eed..28f575a329 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls @@ -1,13 +1,13 @@ import "../../MutMap.mls" import "../../Iter.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Stack.mls" import "../../Predef.mls" import "./Keywords.mls" import "./Token.mls" import "./Tree.mls" -open Option { Some, None } +open LegacyOption { Some, None } open Predef { check, tuple } open Stack @@ -21,8 +21,8 @@ module Choice with data class Ref[A, B]( kind: Str, process: (Tree, B) -> A, - outerPrec: Option[Int], - innerPrec: Option[Int], + outerPrec: LegacyOption[Int], + innerPrec: LegacyOption[Int], rest: ParseRule[B] ) extends Choice[A] @@ -42,7 +42,7 @@ module Choice with init: ParseRule[B], optional: Bool, rest: ParseRule[C], - process: (Option[B], C) -> A + process: (LegacyOption[B], C) -> A ) extends Choice[A] let ensureChoices(xs, name) = xs @@ -130,7 +130,7 @@ module Choice with End(value) then End(op(value)) data class Lazy[out A](init: () -> A) with - mut val cached: Option[A] = None + mut val cached: LegacyOption[A] = None fun reset() = set cached = None fun get() = if cached is Some(v) then v diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls index e5293b9a8b..95af281b1e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls @@ -1,7 +1,7 @@ import "../../Predef.mls" import "../../Stack.mls" import "../../Iter.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../TreeTracer.mls" import "../../XML.mls" import "../../MutMap.mls" @@ -12,7 +12,7 @@ import "./Parser.mls" open Predef open Stack -open Option +open LegacyOption open ParseRule { Choice } open Rules { syntaxKinds } open TreeTracer @@ -88,7 +88,7 @@ fun render[T](rr, title: Str, rule: ParseRule.ParseRule[T]) = referencedKinds = new Set() diagrams.push of ...currentKinds Iter.mapping of kind => - let theRule = syntaxKinds |> MutMap.get(kind) |> Option.unsafe.get + let theRule = syntaxKinds |> MutMap.get(kind) |> LegacyOption.unsafe.get [kind, diagram of renderRule(theRule)] Iter.toArray() diagrams diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls index 1b9244f435..754cea2e1a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls @@ -1,5 +1,5 @@ import "../../Predef.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Stack.mls" import "../../TreeTracer.mls" import "../../Iter.mls" @@ -15,7 +15,7 @@ import "./Rules.mls" import "./ParseRule.mls" open Predef -open Option +open LegacyOption open Stack open ParseRule { Choice } open Choice { Ref } @@ -98,8 +98,8 @@ fun parse(tokens) = do tracer.print of "the current rule is " + rule.display rule.refChoice is Some(Ref(kind, process, outerPrec, innerPrec, rest)) and do tracer.print of "try to parse kind \"" + kind + "\" at " + TokenHelpers.preview(tokens), source.line - let outerPrec' = outerPrec Option.getOrElse(Keywords.maxKeywordPrec) - let innerPrec' = innerPrec Option.getOrElse(prec) + let outerPrec' = outerPrec LegacyOption.getOrElse(Keywords.maxKeywordPrec) + let innerPrec' = innerPrec LegacyOption.getOrElse(prec) outerPrec' > prec and let acc = parseKind(kind, prec) acc Tree.nonEmptyError() and parseRule(prec, rest) is @@ -164,7 +164,7 @@ fun parse(tokens) = Nil then Tree.error("unexpected end of input") fun exprCont(acc: Tree, prec: Int, options) = if tokens is - let infix = options.rule.refChoice Option.flatMap of case + let infix = options.rule.refChoice LegacyOption.flatMap of case Ref(kind, process, None, None, rest) and kind == options.kind then { process: process, rule: rest } else throw Error("Kind " + options.kind + " does not have infix rules") @@ -178,8 +178,8 @@ fun parse(tokens) = keyword.leftPrecOrMin > prec and rule.refChoice is Some(Ref(kind, process, outerPrec, innerPrec, rest)) and do tracer.print of "try to parse kind \"" + kind + "\" at " + TokenHelpers.preview(tokens), source.line - let outerPrec' = outerPrec Option.getOrElse(Keywords.maxOperatorPrec) - let innerPrec' = innerPrec Option.getOrElse(outerPrec') + let outerPrec' = outerPrec LegacyOption.getOrElse(Keywords.maxOperatorPrec) + let innerPrec' = innerPrec LegacyOption.getOrElse(outerPrec') outerPrec' > prec then consume let rhs = parseKind(kind, keyword.rightPrecOrMin) @@ -204,10 +204,10 @@ fun parse(tokens) = token :: _ and infix.rule.refChoice is Some(Ref(kind, process, outerPrec, innerPrec, rest)) and do tracer.print of "found reference to " + kind + " with outerPrec = " + outerPrec, source.line - let outerPrec' = outerPrec Option.getOrElse(Keywords.maxOperatorPrec) - let innerPrec' = innerPrec Option.getOrElse(outerPrec') + let outerPrec' = outerPrec LegacyOption.getOrElse(Keywords.maxOperatorPrec) + let innerPrec' = innerPrec LegacyOption.getOrElse(outerPrec') outerPrec' > prec and - parseKind(kind, innerPrec Option.getOrElse(outerPrec')) is + parseKind(kind, innerPrec LegacyOption.getOrElse(outerPrec')) is Tree.Empty then do tracer.print of "nothing was parsed", source.line acc diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/PrattParsing.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/PrattParsing.mls index aab7edb932..726b53fbe0 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/PrattParsing.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/PrattParsing.mls @@ -1,6 +1,8 @@ +#lang(0.3.x) + import "../../Predef.mls" import "../../Stack.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Iter.mls" import "../../StrOps.mls" import "./Lexer.mls" @@ -8,7 +10,7 @@ import "./Token.mls" import "./Expr.mls" open Stack -open Option { Some, None } +open LegacyOption { Some, None } open Token { Round, LiteralKind } type Stack[A] = Cons[A] | Nil diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/RecursiveDescent.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/RecursiveDescent.mls index b3b89009a8..b60e6d167b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/RecursiveDescent.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/RecursiveDescent.mls @@ -1,13 +1,15 @@ +#lang(0.3.x) + import "../../Predef.mls" import "../../Stack.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Iter.mls" import "./Token.mls" import "./BasicExpr.mls" open Predef { mkStr } open Stack -open Option { Some, None } +open LegacyOption { Some, None } open Token { Round, LiteralKind } open BasicExpr { Expr } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls index 53bdec6664..5611cb852a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls @@ -1,5 +1,5 @@ import "../../Predef.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Stack.mls" import "../../MutMap.mls" import "../../Iter.mls" @@ -9,7 +9,7 @@ import "./Tree.mls" import "./ParseRule.mls" open Predef -open Option +open LegacyOption open Stack open ParseRule { Choice, rule } open Token { Curly, Square, Round } @@ -24,7 +24,7 @@ val syntaxKinds = MutMap.empty val extendedKinds = new Set() -fun getRuleByKind(kind: Str) = syntaxKinds |> MutMap.get(kind) |> Option.unsafe.get +fun getRuleByKind(kind: Str) = syntaxKinds |> MutMap.get(kind) |> LegacyOption.unsafe.get fun define(name: Str)(...choices) = syntaxKinds |> MutMap.updateWith(name) of case None then Some(rule(name, ...choices)) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls index e52fa72200..a3fe4513b5 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls @@ -6,13 +6,13 @@ import "./TokenHelpers.mls" import "./TreeHelpers.mls" import "./Parser.mls" import "../../Iter.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Predef.mls" import "../../Stack.mls" open Token { LiteralKind } open Parser { parse, tracer } -open Option { Some, None } +open LegacyOption { Some, None } open Predef { print } open Stack open TreeHelpers { showAsTree } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls index e67cb45c0f..be19feac71 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls @@ -1,7 +1,7 @@ -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Predef.mls" -open Option { Some, None } +open LegacyOption { Some, None } open Predef { mkStr } module Token with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TokenHelpers.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TokenHelpers.mls index 17b4839288..14c7127ef4 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TokenHelpers.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TokenHelpers.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./Token.mls" import "../../Stack.mls" import "../../Predef.mls" diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls index 5d63267497..7d0727e5ec 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls @@ -1,14 +1,14 @@ import "../../Iter.mls" import "../../Predef.mls" import "../../Stack.mls" -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../StrOps.mls" import "./Keywords.mls" import "./Token.mls" open Predef { fold, mkStr } open Stack -open Option { Some, None } +open LegacyOption { Some, None } open Token { LiteralKind, BracketKind } open Keywords { opPrec, INT_MAX } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls index c0ae10f278..87a71f4f19 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls @@ -1,4 +1,4 @@ -import "../../Option.mls" +import "../../LegacyOption.mls" import "../../Predef.mls" import "../../Stack.mls" import "./Tree.mls" @@ -8,7 +8,7 @@ import "./Token.mls" open Predef open Stack open Token { LiteralKind } -open Option { Some, None } +open LegacyOption { Some, None } module TreeHelpers with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/banner.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/banner.mls index b3f02e72a6..3f806dc9cd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/banner.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/banner.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/boyer.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/boyer.mls index 780b1f7776..f0909ca8e2 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/boyer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/boyer.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/boyer2.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/boyer2.mls index f82474b5f1..8443d63134 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/boyer2.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/boyer2.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/calendar.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/calendar.mls index f4f3c881f7..1dd0a445cb 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/calendar.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/calendar.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/clausify.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/clausify.mls index c049c89741..a555bac873 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/clausify.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/clausify.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/cryptarithm1.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/cryptarithm1.mls index d033310a6b..501f70ff40 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/cryptarithm1.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/cryptarithm1.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/gcd.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/gcd.mls index b3a02cd918..eeb5a50da8 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/gcd.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/gcd.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/mandel.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/mandel.mls index fdcfc337b2..98a7119950 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/mandel.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/mandel.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/mandel2.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/mandel2.mls index dc5ee1620c..20e40eb503 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/mandel2.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/mandel2.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/puzzle.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/puzzle.mls index d173a9b294..56904309f5 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/puzzle.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/puzzle.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/rsa.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/rsa.mls index e6b71c04f0..51a79a32de 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/rsa.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/rsa.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -16,7 +18,7 @@ fun z_div(x, y) = x / y fun z_mod(x, y) = x % y fun z_equal(x, y) = x === y fun z_sqr(x) = x * x -fun int_if_char(c) = c.codePointAt(0) +fun int_if_char(c: Str) = c.codePointAt(0) val const0 = z_of_int(0) //│ const0 = 0 diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/sphere.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/sphere.mls index aac6cd0e92..1f8962abda 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/sphere.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/sphere.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/CSPBar.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/CSPBar.mls index 52ec4999ca..13c0fe2a76 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/CSPBar.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/CSPBar.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./../Example.mls" module CSPBar with ... fun res = diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/CSPBaz.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/CSPBaz.mls index e44c6b8bba..bc6d648133 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/CSPBaz.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/CSPBaz.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./CSPNest.mls" module CSPBaz with ... fun res = diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/CSPFoo.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/CSPFoo.mls index 3cb2a29866..6219a922a7 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/CSPFoo.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/CSPFoo.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./../CSP.mls" module CSPFoo with ... fun res = diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/CSPNest.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/CSPNest.mls index 3897489e9d..d412c0fead 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/CSPNest.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/CSPNest.mls @@ -1,2 +1,4 @@ +#lang(0.3.x) + module CSPNest with ... fun nest_f() = 42 diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/Cubic.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/Cubic.mls index 1b7055c23e..1dd0d0e970 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/Cubic.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/Cubic.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + module Cubic with ... fun res = diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/Gib12.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/Gib12.mls index d191fdb538..aa7fe5c9b5 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/Gib12.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/Gib12.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + module Gib12 with ... fun res = diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/Opened.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/Opened.mls index 45eb6634f7..f01cb51a3f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/Opened.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/Opened.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./../Predef.mls" module Opened with ... fun res = diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/QuoteFoo.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/QuoteFoo.mls index f0e447e0e4..e50ae3bb2c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/QuoteFoo.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/QuoteFoo.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + module QuoteFoo with ... fun res = diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/QuoteInc.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/QuoteInc.mls index 461729c341..3aca81129e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/QuoteInc.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/QuoteInc.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + module QuoteInc with ... fun res = diff --git a/hkmc2/shared/src/test/mlscript-compile/quotes/SafeDiv.mls b/hkmc2/shared/src/test/mlscript-compile/quotes/SafeDiv.mls index 37a40a58b6..eb33687708 100644 --- a/hkmc2/shared/src/test/mlscript-compile/quotes/SafeDiv.mls +++ b/hkmc2/shared/src/test/mlscript-compile/quotes/SafeDiv.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + module SafeDiv with ... fun res = diff --git a/hkmc2/shared/src/test/mlscript-compile/ups/DnfCnf.mls b/hkmc2/shared/src/test/mlscript-compile/ups/DnfCnf.mls index ba15362351..af671c45f8 100644 --- a/hkmc2/shared/src/test/mlscript-compile/ups/DnfCnf.mls +++ b/hkmc2/shared/src/test/mlscript-compile/ups/DnfCnf.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + open annotations module DnfCnf with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls b/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls index 6519116f08..1cbd2d1763 100644 --- a/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls +++ b/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls @@ -1,6 +1,6 @@ -import "../Option.mls" +import "../LegacyOption.mls" -open Option { Some, None } +open LegacyOption { Some, None } open annotations module EvaluationContext with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/ups/EvenOddTree.mls b/hkmc2/shared/src/test/mlscript-compile/ups/EvenOddTree.mls index 29571df9ec..445a439a3d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/ups/EvenOddTree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/ups/EvenOddTree.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + open annotations module EvenOddTree with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/ups/TruthyFalsy.mls b/hkmc2/shared/src/test/mlscript-compile/ups/TruthyFalsy.mls index 12fa7723f4..86f3a979fb 100644 --- a/hkmc2/shared/src/test/mlscript-compile/ups/TruthyFalsy.mls +++ b/hkmc2/shared/src/test/mlscript-compile/ups/TruthyFalsy.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + open annotations module TruthyFalsy with ... diff --git a/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls b/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls index 621c69fca0..bab6ea584f 100644 --- a/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls +++ b/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls @@ -2,10 +2,10 @@ :js import "../../mlscript-compile/Block.mls" -import "../../mlscript-compile/Option.mls" +import "../../mlscript-compile/LegacyOption.mls" open Block -open Option +open LegacyOption printCode(FunDefn(Symbol("f"), [[Symbol("x")]], Return(ValueLit(1)))) //│ > fun f(x) = 1 diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls index 9f79666925..54cc53a85a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls @@ -25,7 +25,7 @@ Example.privFun :re Example.assertFail() -//│ ═══[RUNTIME ERROR] Error: Assertion failed (mlscript-compile/Example.mls:27) +//│ ═══[RUNTIME ERROR] Error: Assertion failed (mlscript-compile/Example.mls:28) let n = 42 @@ -78,7 +78,7 @@ test / inc / 123 :re test / inc / 123 * 0.5 -//│ ═══[RUNTIME ERROR] TypeError: inlinedVal1 is not a function +//│ ═══[RUNTIME ERROR] TypeError: f2 is not a function let c = Curried(1) diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls index a08f45f197..221cb9e7d2 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls @@ -21,11 +21,11 @@ open Option :sjs None isDefined() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let x1; -//│ x1 = Option.None; -//│ if (x1 instanceof Option.Some.class) { +//│ let x2; +//│ x2 = Option.None; +//│ if (x2 instanceof Option.Some.class) { //│ return true -//│ } else if (x1 instanceof Option.None.class) { +//│ } else if (x2 instanceof Option.None.class) { //│ return false //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -87,7 +87,7 @@ Option.oops //│ ║ ^^^^^^^^^^^ //│ ╟── Module 'Option' does not contain member 'oops' //│ ║ in ../../mlscript-compile/Option.mls -//│ ║ l.4: module Option with ... +//│ ║ l.6: module Option with ... //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -98,7 +98,7 @@ open Option { oops } //│ ║ ^^^^ //│ ╟── Module 'Option' does not contain member 'oops' //│ ║ in ../../mlscript-compile/Option.mls -//│ ║ l.4: module Option with ... +//│ ║ l.6: module Option with ... //│ ╙── ^^^^^^ :e @@ -109,7 +109,7 @@ oops //│ ║ ^^^^ //│ ╟── Module 'Option' does not contain member 'oops' //│ ║ in ../../mlscript-compile/Option.mls -//│ ║ l.4: module Option with ... +//│ ║ l.6: module Option with ... //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -121,8 +121,8 @@ Option.None.oops //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Object 'None' does not contain member 'oops' //│ ║ in ../../mlscript-compile/Option.mls -//│ ║ l.8: object None -//│ ╙── ^^^^ +//│ ║ l.10: object None +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls b/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls index 7db99c837b..c981479d75 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls @@ -1,14 +1,14 @@ :js -import "../../mlscript-compile/Option.mls" +import "../../mlscript-compile/LegacyOption.mls" module OtherModule with val None = 123 :sjs -open Option +open LegacyOption //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -17,10 +17,10 @@ open Option None isDefined() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let x; -//│ x = Option.None; -//│ if (x instanceof Option.Some.class) { +//│ x = LegacyOption.None; +//│ if (x instanceof LegacyOption.Some.class) { //│ return true -//│ } else if (x instanceof Option.None.class) { +//│ } else if (x instanceof LegacyOption.None.class) { //│ return false //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -30,7 +30,7 @@ None isDefined() Some(1) isDefined() //│ = true -None === Option.None +None === LegacyOption.None //│ = true @@ -38,14 +38,14 @@ fun none() = None open OtherModule -None === Option.None +None === LegacyOption.None //│ = false -none() === Option.None +none() === LegacyOption.None //│ = true -open Option +open LegacyOption fun none() = None console.log(None) open OtherModule @@ -59,24 +59,24 @@ none() // * `open` statements are hygienic and are not affected by redefinitions :sjs -val Option = "Oops" +val LegacyOption = "Oops" //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Option1; Option1 = "Oops"; +//│ let LegacyOption1; LegacyOption1 = "Oops"; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ Option = "Oops" +//│ LegacyOption = "Oops" :sjs Some(123) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return Option.Some(123) +//│ return LegacyOption.Some(123) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = Some(123) -module Option with +module LegacyOption with val None = 123 :sjs -open Option +open LegacyOption //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -84,14 +84,14 @@ open Option :sjs Some //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return Option.Some +//│ return LegacyOption.Some //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun Some { class: class Some } :sjs None //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return Option3.None +//│ return LegacyOption3.None //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index a665aec534..5ce764cf88 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -35,24 +35,44 @@ declare module BigInt declare class Function declare module Function declare class String +declare fun String(...args): Str declare module String with fun fromCharCode fromCodePoint raw -declare class RegExp +declare class RegExp with + fun exec(input: Str): Array[Str] | null + fun test(input: Str): Bool declare module RegExp -declare class Set[V] +declare class Set[V] with + val size: Int + fun add(value: V): Set[V] + fun has(value: V): Bool + fun delete(value: V): Bool + fun clear(): undefined + fun union(other: Set[V]): Set[V] + fun difference(other: Set[V]): Set[V] declare module Set -declare class Map[K, V] +declare class Map[K, V] with + val size: Int + fun get(key: K): V | undefined + fun has(key: K): Bool + fun delete(key: K): Bool + fun clear(): undefined + // `set` needs support for keyword-named declarations, like Reflect.set below. declare module Map declare class WeakSet[V] declare module WeakSet declare class WeakMap[K, V] declare module WeakMap -declare class Error//(info) // TODO: handle JS classes that can be instantiated without `new` specially in codegen. -declare class TypeError//(info) // TODO: handle JS classes that can be instantiated without `new` specially in codegen. -declare class RangeError//(info) // TODO: handle JS classes that can be instantiated without `new` specially in codegen. +declare class Error with + val message: Str +declare fun Error(...args): Error +declare class TypeError extends Error +declare fun TypeError(...args): TypeError +declare class RangeError extends Error +declare fun RangeError(...args): RangeError declare class Date declare module Date @@ -90,28 +110,42 @@ declare val Promise // MLscript-specific types declare class Bool -declare class Num +declare class Num with + fun toString(...radix): Str + fun toFixed(...digits): Str declare class Int extends Num -declare class Str +declare class Str with + val length: Int + fun toString(): Str + fun charAt(index: Int): Str + fun charCodeAt(index: Int): Num + fun codePointAt(index: Int): Int | undefined + fun indexOf(value: Str, ...from): Int + fun lastIndexOf(value: Str, ...from): Int + fun includes(value: Str, ...from): Bool + fun startsWith(value: Str, ...from): Bool + fun endsWith(value: Str, ...until): Bool + fun slice(...bounds): Str + fun substring(...bounds): Str + fun split(...separatorAndLimit): Array[Str] + fun repeat(count: Int): Str + fun toUpperCase(): Str + fun toLowerCase(): Str + fun trim(): Str declare class Class -// The `Array` class/function is a footgun: -// > Array() -// [] -// > Array(1) -// [ <1 empty item> ] -// > Array(1, 2) -// [ 1, 2 ] -// We used to declare it as a class taking exactly one argument here to avoid that footgun: -// declare class Array[T](val length: Int): Array[T] -// but this made instance checks wrongly use `Array.class`; -// TODO: handle Array and other JS classes that can be instantiated without `new` specially in codegen. +// The foreign Array constructor is also callable. A single numeric argument +// denotes a length, so its rest arguments must not infer an element type. +declare fun Array[T](...args): Array[T] declare class Array[T] with val length: Int fun join(...separators): Str fun reverse(): Array[T] fun sort(...compare): Array[T] fun slice(...bounds): Array[T] + fun at(index: Int): T | undefined + fun fill(value: T, ...bounds): Array[T] + fun splice(start: Int, ...items): Array[T] fun push(...items): Int fun unshift(...items): Int fun pop(): T | undefined @@ -230,7 +264,8 @@ declare module console with trace declare val process // TODO make `module` -declare val fs // TODO make `module` +// Host APIs remain explicitly dynamic until their interfaces are modeled. +declare val fs: dyn declare val Infinity @@ -398,6 +433,17 @@ declare module runtime with // HTML DOM API definitions. // Move them to a separate Prelude file when there are enough of them. -declare val document -declare val customElements -declare class HTMLElement +declare val document: dyn +declare val customElements: dyn +// Preserve the nominal superclass while its host members remain dynamic. +declare class HTMLElement with + val textContent: dyn + val innerHTML: dyn + val shadowRoot: dyn + val appendChild: dyn + val attachShadow: dyn + val addEventListener: dyn + val querySelector: dyn + val classList: dyn + +//│ ———————————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls b/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls index f5a3a22735..e371ef0fce 100644 --- a/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls +++ b/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls @@ -192,7 +192,7 @@ fun nott = case //│ case false: //│ return true; //│ } -//│ throw globalThis.Object.freeze(new globalThis.Error.class("match error")) +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ }); //│ return lambda2 //│ }; @@ -208,7 +208,7 @@ nott of false //│ } else if (false === false) { //│ return true //│ } -//│ throw globalThis.Object.freeze(new globalThis.Error.class("match error")); +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = true //│ Type: Bool diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index bad7391c34..ed7a89ab21 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -84,7 +84,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.119: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.153: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:85 @@ -108,3 +108,20 @@ xs.map((x, ...) => x.original) //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Replacement(2)] + +// at supports negative indices; foreign undefined results normalize to unit. +:expect [3, 1, ()] +let xs = [1, 2, 3] +[xs.at(-1), xs.at(0), xs.at(3)] +//│ = [3, 1, ()] +//│ xs = [1, 2, 3] + +:expect ["7,7,3", "7", "7,9,3"] +let xs = mut [1, 2, 3] +let filled = xs.fill(7, 0, 2).join() +let removed = xs.splice(1, 1, 9).join() +[filled, removed, xs.join()] +//│ = ["7,7,3", "7", "7,9,3"] +//│ filled = "7,7,3" +//│ removed = "7" +//│ xs = [7, 9, 3] diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls index 32632357f2..60c028167c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -29,7 +29,6 @@ Predef.id //│ | Elab term Sel(Ident(Predef),Ident(id)) //│ | | Elab subterm Sel(Ident(Predef),Ident(id)) //│ | | | Elab subterm Ident(Predef) -//│ | | | | listenTerm: trm = member:Predef⁰ //│ | | | | listen: trm = member:Predef⁰ //│ | | | | listenBMS: bms = module //│ | | | | listenedBMS: bms = module @@ -37,7 +36,6 @@ Predef.id //│ | | | | PUSH module:Predef¹ //│ | | | ~> member:Predef⁰ //│ | | | newSel? sel = member:Predef⁰.id -//│ | | | listenTerm: trm = member:Predef⁰ //│ | | | listen: trm = member:Predef⁰ //│ | | | listenBMS: bms = module //│ | | | listenedBMS: bms = module @@ -88,7 +86,7 @@ Predef.id(1) :e Predef.idd(1) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.89: Predef.idd(1) +//│ ║ l.87: Predef.idd(1) //│ ║ ^^^^^^^^^^ //│ ╟── Module 'Predef' does not contain member 'idd' //│ ║ in ../../mlscript-compile/Predef.mls @@ -141,32 +139,26 @@ p.id :e (1).id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.142: (1).id +//│ ║ l.140: (1).id //│ ║ ^^^^^^ //│ ╟── Integer literal does not contain member 'id' -//│ ║ l.142: (1).id +//│ ║ l.140: (1).id //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme // parse 1.id //│ ╔══[LEXICAL ERROR] Expected at least one digit after the decimal point -//│ ║ l.152: 1.id +//│ ║ l.150: 1.id //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (identifier). -//│ ║ l.152: 1.id +//│ ║ l.150: 1.id //│ ╙── ^^ //│ = 1 -:fixme +:expect "ha ha " "ha ".repeat(2) -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.162: "ha ".repeat(2) -//│ ║ ^^^^^^^^^^^^ -//│ ╟── String literal does not contain member 'repeat' -//│ ║ l.162: "ha ".repeat(2) -//│ ╙── ^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = "ha ha " print(1) @@ -177,9 +169,9 @@ print(1) :e ().id //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.178: ().id +//│ ║ l.170: ().id //│ ║ ^^^^^ //│ ╟── Unit value does not contain member 'id' -//│ ║ l.178: ().id +//│ ║ l.170: ().id //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 600e14eb31..2a19e5aa80 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -57,9 +57,9 @@ callid1(Predef) //│ | | Elab subterm App(Sel(Ident(p),Ident(id)),Tup(List(IntLit(1)))) //│ | | | Elab subterm Sel(Ident(p),Ident(id)) //│ | | | | Elab subterm Ident(p) +//│ | | | | | listen: trm = p⁰ //│ | | | | ~> p⁰ //│ | | | | newSel? sel = p⁰.id -//│ | | | | listenTerm: trm = p⁰ //│ | | | | listen: trm = p⁰ //│ | | | | listenTerm: trm = p⁰.id //│ | | | | listen: trm = p⁰.id diff --git a/hkmc2/shared/src/test/mlscript/newres/HostInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/HostInterfaces.mls new file mode 100644 index 0000000000..3a3322a5b0 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/HostInterfaces.mls @@ -0,0 +1,14 @@ +:. + +// These host declarations are checked without requiring a DOM in the test VM. +:!js +fun readFile(path: Str) = fs.readFileSync(path).toString() +fun query(selector: Str) = document.querySelector(selector).classList.contains("active") +fun register(name: Str, ctor: dyn) = customElements.define(name, ctor) +class Element extends HTMLElement with + fun render(text: Str) = { + set this.textContent = text + this.appendChild(document.createElement("span")) + this.attachShadow({mode: "open"}) + set this.shadowRoot.innerHTML = text + } diff --git a/hkmc2/shared/src/test/mlscript/newres/MemberAssignments.mls b/hkmc2/shared/src/test/mlscript/newres/MemberAssignments.mls new file mode 100644 index 0000000000..17bd031c7d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/MemberAssignments.mls @@ -0,0 +1,22 @@ +:. + +class Counter with + let count = 0 + fun increment() = { + set count = count + 1 + count + } + +:expect [1, 2] +let counter = new mut Counter +[counter.increment(), counter.increment()] +//│ = [1, 2] +//│ counter = Counter + +:e +module Fixed with + fun value() = 0 + fun change() = set value = 1 +//│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Function' +//│ ║ l.19: fun change() = set value = 1 +//│ ╙── ^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/OptionImports.mls b/hkmc2/shared/src/test/mlscript/newres/OptionImports.mls new file mode 100644 index 0000000000..a747c351da --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/OptionImports.mls @@ -0,0 +1,20 @@ +:. + +import "../../mlscript-compile/Option.mls" +open Option { Some, None } + +:expect [true, false, 42, 7, 42, 43, 7] +[Option.isDefined(Some(42)), Option.isDefined(None), + Option.getOrElse(Some(42), 7), Option.getOrElse(None, 7), + Option.unsafe.get(Some(42)), + Option.getOrElse(Option.flatMap(Some(42), x => Some(x + 1)), 7), + Option.getOrElse(Option.flatMap(None, x => Some(x + 1)), 7)] +//│ = [true, false, 42, 7, 42, 43, 7] + +:expect "AB" +Option.getOrElse[Str](Some("ab"), "missing").toUpperCase() +//│ = "AB" + +:re +Option.unsafe.get(None) +//│ ═══[RUNTIME ERROR] Error: None.get diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedCalls.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedCalls.mls new file mode 100644 index 0000000000..8314a40c78 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedCalls.mls @@ -0,0 +1,67 @@ +:. + +// Calls and stored function values select the function overload. Selections +// retain the companion module, including inside nested function captures. +fun Compute[A](x: A) = x +module Compute with + fun next(x: Int) = Compute(x + 1) + +:expect [2, 3, 4] +let f = Compute +[Compute(2), f(3), Compute.next(3)] +//│ = [2, 3, 4] +//│ f = class Compute + +class Item(val value: Int) +module Item with + fun next(x: Item) = Item(x.value + 1) + +:expect [2, 3] +[Item(2).value, Item.next(Item(2)).value] +//│ = [2, 3] + +module Library with + fun Convert[A](x: A) = x + module Convert with + fun twice(x: Int) = Convert(x + x) + +:expect [4, 6] +open Library +[Convert(4), Convert.twice(3)] +//│ = [4, 6] + +:expect 5 +fun nested() = { + fun inner() = Item.next(Item(4)).value + inner() +} +nested() +//│ = 5 + +:expect [7, 8] +[Compute[Int](7), Library.Convert[Int](8)] +//│ = [7, 8] + +// Taking a function value does not also take its companion module. +:e +fun plain(x: Int) = x +plain.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.48: plain.missing +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Function 'plain' does not contain member 'missing' +//│ ║ l.47: fun plain(x: Int) = x +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Captured constructor patterns retain the class target through every scope. +:expect 9 +fun inspect(x) = { + fun nested() = { + fun deeper() = if x is Item(v) then v else 0 + deeper() + } + nested() +} +inspect(Item(9)) +//│ = 9 diff --git a/hkmc2/shared/src/test/mlscript/newres/PrimitiveMembers.mls b/hkmc2/shared/src/test/mlscript/newres/PrimitiveMembers.mls new file mode 100644 index 0000000000..f0e89980d7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/PrimitiveMembers.mls @@ -0,0 +1,57 @@ +:. + +:expect [3, "abab", "A", 98, "1.00", "23"] +["abc".length, "ab".repeat(2), "abc".charAt(0).toUpperCase(), + "abc".charCodeAt(1), (1).toFixed(2), (23).toString()] +//│ = [3, "abab", "A", 98, "1.00", "23"] + +:expect ["AB", "1.5", "a-b"] +fun upper(s: Str) = s.toUpperCase() +fun decimal(n: Num) = n.toFixed(1) +[upper("ab"), decimal(1.5), "a b".split(" ").join("-")] +//│ = ["AB", "1.5", "a-b"] + +:e +"abc".nonexistent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.15: "abc".nonexistent +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── String literal does not contain member 'nonexistent' +//│ ║ l.15: "abc".nonexistent +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:expect [true, 2, false, 1] +let values = new mut Set +values.add("a") +values.add("b") +let present = values.has("a") +let size = values.size +values.delete("a") +[present, size, values.has("a"), values.size] +//│ = [true, 2, false, 1] +//│ present = true +//│ size = 2 +//│ values = Set(1) {"b"} + +:expect [true, "ab", null, false] +let re = new RegExp("a+") +let groups = new RegExp("ab").exec("abc") +[re.test("aa"), (if groups is Array as array then array.at(0) else "missing"), + re.exec("c"), (new Map).has("missing")] +//│ = [true, "ab", null, false] +//│ groups = ["ab"] +//│ re = /a+/ + +:expect [true, true, true, "oops"] +let error = Error("oops") +[error is Error, TypeError("bad") is TypeError, + RangeError("bounds") is RangeError, error.message] +//│ = [true, true, true, "oops"] +//│ error = Error: oops + +:expect [true, 3, "42", true] +let xs = Array(3) +[xs is Array, xs.length, String(42), (new Error("new")) is Error] +//│ = [true, 3, "42", true] +//│ xs = [<3 empty items>] diff --git a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls index 860345fb29..3e98241a22 100644 --- a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls +++ b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls @@ -79,8 +79,6 @@ module M with //│ | | | Elab term Ident(Predef) //│ | | | | Elab subterm Ident(Predef) //│ | | | | | listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ -//│ | | | | | listen: trm = member:Predef⁰^term:M⁰/p⁰ -//│ | | | | | listenTerm: trm = member:Predef⁰ //│ | | | | | listen: trm = member:Predef⁰ //│ | | | | | listenBMS: bms = module //│ | | | | | listenedBMS: bms = module @@ -101,8 +99,6 @@ module M with //│ listenedBMS: bms = value //│ listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ //│ listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ -//│ listen: trm = member:Predef⁰^term:M⁰/p⁰ -//│ listenTerm: trm = member:Predef⁰ //│ listen: trm = member:Predef⁰ //│ listenBMS: bms = module //│ listenedBMS: bms = module @@ -124,7 +120,6 @@ M.p.id //│ | | Elab subterm Sel(Sel(Ident(M),Ident(p)),Ident(id)) //│ | | | Elab subterm Sel(Ident(M),Ident(p)) //│ | | | | Elab subterm Ident(M) -//│ | | | | | listenTerm: trm = member:M¹ //│ | | | | | listen: trm = member:M¹ //│ | | | | | listenBMS: bms = module //│ | | | | | listenedBMS: bms = module @@ -132,7 +127,6 @@ M.p.id //│ | | | | | PUSH module:M⁰ //│ | | | | ~> member:M¹ //│ | | | | newSel? sel = member:M¹.p -//│ | | | | listenTerm: trm = member:M¹ //│ | | | | listen: trm = member:M¹ //│ | | | | listenBMS: bms = module //│ | | | | listenedBMS: bms = module @@ -140,14 +134,11 @@ M.p.id //│ | | | | PUSH module:M⁰ //│ | | | | newSel: sel = member:M¹.p, shape = module:M⁰ //│ | | | | newSel member: bms = member:p¹, mss = List() -//│ | | | | listenTerm: trm = member:M¹.p //│ | | | | listen: trm = member:M¹.p //│ | | | | listenBMS: bms = value //│ | | | | listenedBMS: bms = value //│ | | | | listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ //│ | | | | listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ -//│ | | | | listen: trm = member:Predef⁰^term:M⁰/p⁰ -//│ | | | | listenTerm: trm = member:Predef⁰ //│ | | | | listen: trm = member:Predef⁰ //│ | | | | listenBMS: bms = module //│ | | | | listenedBMS: bms = module @@ -159,14 +150,11 @@ M.p.id //│ | | | | MATCH module:Predef¹ for term:M⁰/p⁰ % ⋅p⁰ //│ | | | ~> member:M¹.p //│ | | | newSel? sel = member:M¹.p.id -//│ | | | listenTerm: trm = member:M¹.p //│ | | | listen: trm = member:M¹.p //│ | | | listenBMS: bms = value //│ | | | listenedBMS: bms = value //│ | | | listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ //│ | | | listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ -//│ | | | listen: trm = member:Predef⁰^term:M⁰/p⁰ -//│ | | | listenTerm: trm = member:Predef⁰ //│ | | | listen: trm = member:Predef⁰ //│ | | | listenBMS: bms = module //│ | | | listenedBMS: bms = module diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls b/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls index 57e4af7a1b..8acc483ace 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls @@ -4,11 +4,11 @@ // Author: Luyu Cheng -import "../../../mlscript-compile/Option.mls" +import "../../../mlscript-compile/LegacyOption.mls" import "../../../mlscript-compile/MutMap.mls" open annotations -open Option { Some, None } +open LegacyOption { Some, None } data class Term with @@ -71,7 +71,7 @@ Var("a").freshVar("b") fun (<~>) alphaEquivalent(lhs, rhs) = let varMap = MutMap.empty - pattern Var' = Var(n) => varMap |> MutMap.get(n) |. Option.getOrElse(n) + pattern Var' = Var(n) => varMap |> MutMap.get(n) |. LegacyOption.getOrElse(n) fun go(lhs, rhs) = if lhs is Var(n) and rhs is Var' as n' then n === n' App(l, r) and r is App(l', r') then go(l, l') and go(r, r') diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 60898647ce..f56afc5715 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.272: fun +//│ ║ l.307: fun //│ ║ ^^^ -//│ ║ l.273: const(c: Int): Int32 +//│ ║ l.308: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index e66fc99301..75d893d3b7 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -20,7 +20,9 @@ abstract class MLsDiffMaker extends DiffMaker: val runtimeSourceFile: io.Path = predefFile.up / "Runtime.mls" // * Contains MLscript runtime sources val termFile: io.Path = predefFile.up / "Term.mjs" // * Contains MLscript runtime term definitions val blockFile: io.Path = predefFile.up / "Block.mjs" // * Contains MLscript runtime block definitions - val optionFile: io.Path = predefFile.up / "Option.mjs" // * Contains MLscipt runtime option definition + // Reflection must use the same option constructors as Block.mls. Switch both + // back to Option when Block and its consumers no longer need LegacyOption. + val optionFile: io.Path = predefFile.up / "LegacyOption.mjs" val wd = file.up @@ -311,6 +313,11 @@ abstract class MLsDiffMaker extends DiffMaker: val preludeArtifact = cctx.getPrelude(preludeFile) curCtx = preludeArtifact.ctx prelude = preludeArtifact.ctx + else + // The compiler loads the prelude as one compilation unit. Its mutually + // referring host signatures must see declarations across blank lines here + // too; worksheet block boundaries are not part of the prelude's semantics. + consumeEmptyLines.setCurrentValue(()) super.run() From 48c129c3392befb29ff6c23bada07e31e7dfb37f Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 17:20:21 +0800 Subject: [PATCH 077/228] Explain unknown shapes from unannotated constructor fields --- .../scala/hkmc2/semantics/NewResolver.scala | 10 +- .../test/mlscript/newres/DeclaredTypes.mls | 111 +++++++++++++----- 2 files changed, 88 insertions(+), 33 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 701880d83f..cc5eac4830 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -303,7 +303,15 @@ class NewResolver: td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), ps.restParam.nonEmpty)), result, Nil)) else result match case S(tpe) => listenTypeValues(tpe)(publish) - case N => publish(UnknownValueShape.at(source)) + case N => + // Keep the missing constructor annotation on the unknown value so + // later selections and calls can explain why argument flow is hidden. + val unknown = td.tsym.decl match + case S(param: Param) => UnknownValueShape(source)(ShapeProvenance( + (msg"Field '${member.nme}' is accessed through an annotated class type, so constructor arguments do not determine its shape." -> source.toLoc) :: + (msg"Constructor parameter '${param.sym.nme}' has no type annotation." -> param.toLoc) :: Nil)) + case _ => UnknownValueShape.at(source) + publish(unknown) case _ => // A nested nominal declaration denotes its statically selected symbol; // selecting it does not inspect an instance field or method body. diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 3aba6866f5..98090f5f75 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -66,7 +66,7 @@ box.extra //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Child { extra: 42 } -// An unannotated field cannot reveal its initializer through an annotated receiver. +// An annotated receiver does not expose constructor-argument shapes for unannotated fields. :ge class Item with val extra = 42 @@ -77,9 +77,12 @@ read(Box(new Item)) //│ ║ l.74: fun read(box: Box) = box.item.extra //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ╟── The shape of this value is unknown. +//│ ╟── Field 'item' is accessed through an annotated class type, so constructor arguments do not determine its shape. //│ ║ l.74: fun read(box: Box) = box.item.extra -//│ ╙── ^^^^^^^^ +//│ ║ ^^^^^^^^ +//│ ╟── Constructor parameter 'item' has no type annotation. +//│ ║ l.73: class Box(val item) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // An unannotated method result cannot reveal the method body either. @@ -91,11 +94,11 @@ class Box with fun read(box: Box) = box.item().extra read(new Box) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.91: fun read(box: Box) = box.item().extra +//│ ║ l.94: fun read(box: Box) = box.item().extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.91: fun read(box: Box) = box.item().extra +//│ ║ l.94: fun read(box: Box) = box.item().extra //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -141,10 +144,10 @@ read(() => Item(42)) :ge fun unused(factory: () -> Int) = factory(1) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.142: fun unused(factory: () -> Int) = factory(1) +//│ ║ l.145: fun unused(factory: () -> Int) = factory(1) //│ ║ ^^^^^^^^^^ //│ ╟── Function with a declared signature expected 0 arguments, but got 1 -//│ ║ l.142: fun unused(factory: () -> Int) = factory(1) +//│ ║ l.145: fun unused(factory: () -> Int) = factory(1) //│ ╙── ^^^^^^^^^ // A callable annotation also hides extra members of the concrete result. @@ -155,7 +158,7 @@ class Child extends Base with fun read(factory: () -> Base) = factory().extra read(() => new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.155: fun read(factory: () -> Base) = factory().extra +//│ ║ l.158: fun read(factory: () -> Base) = factory().extra //│ ║ ^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -169,7 +172,7 @@ fun hidden: (Base) -> Int fun hidden(base) = base.extra hidden(new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.169: fun hidden(base) = base.extra +//│ ║ l.172: fun hidden(base) = base.extra //│ ║ ^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -183,7 +186,7 @@ fun make: () -> Base fun make() = new Child make().extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.184: make().extra +//│ ║ l.187: make().extra //│ ║ ^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -208,11 +211,11 @@ class Box[T] type Loop[T] = Loop[Box[T]] fun unused(value: Loop[Int]) = value.field //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.209: fun unused(value: Loop[Int]) = value.field +//│ ║ l.212: fun unused(value: Loop[Int]) = value.field //│ ║ ^^^^^^^^^^^ //│ ╟── Cannot resolve member 'field' of a value with unknown shape. //│ ╟── This abstract type does not specify a member interface. -//│ ║ l.209: fun unused(value: Loop[Int]) = value.field +//│ ║ l.212: fun unused(value: Loop[Int]) = value.field //│ ╙── ^^^^^^^^^ // A concrete instantiation cannot authorize calls through an opaque type parameter. @@ -220,13 +223,13 @@ fun unused(value: Loop[Int]) = value.field private fun invoke[T](value: T) = value() invoke[() -> Int](() => 42) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.220: private fun invoke[T](value: T) = value() +//│ ║ l.223: private fun invoke[T](value: T) = value() //│ ║ ^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.220: private fun invoke[T](value: T) = value() +//│ ║ l.223: private fun invoke[T](value: T) = value() //│ ║ ^ //│ ╟── Type parameter 'T' does not specify a member interface. -//│ ║ l.220: private fun invoke[T](value: T) = value() +//│ ║ l.223: private fun invoke[T](value: T) = value() //│ ╙── ^ //│ = 42 @@ -238,12 +241,15 @@ class Box(val item) fun read(box: Box) = box.Box#item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.238: fun read(box: Box) = box.Box#item.extra +//│ ║ l.241: fun read(box: Box) = box.Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ╟── The shape of this value is unknown. -//│ ║ l.238: fun read(box: Box) = box.Box#item.extra -//│ ╙── ^^^^^^^^^^^^ +//│ ╟── Field 'item' is accessed through an annotated class type, so constructor arguments do not determine its shape. +//│ ║ l.241: fun read(box: Box) = box.Box#item.extra +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Constructor parameter 'item' has no type annotation. +//│ ║ l.240: class Box(val item) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // A signature alias supplies the interface to both the body and callers. @@ -272,7 +278,7 @@ class Child extends Base with fun identity[T](x: T): T = x identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.273: identity[Base](new Child).extra +//│ ║ l.279: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -314,11 +320,11 @@ class Box[T](val value: T) (new Box[Base](new Child)).value.extra Box[Base](new Child).value.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.314: (new Box[Base](new Child)).value.extra +//│ ║ l.320: (new Box[Base](new Child)).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.315: Box[Base](new Child).value.extra +//│ ║ l.321: Box[Base](new Child).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -361,11 +367,11 @@ class Box with val item = new Item (new Box as Box).Box#item.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.362: (new Box as Box).Box#item.extra +//│ ║ l.368: (new Box as Box).Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.362: (new Box as Box).Box#item.extra +//│ ║ l.368: (new Box as Box).Box#item.extra //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -379,11 +385,11 @@ module Types with fun hidden(module types: Types) = types.Item.extra hidden(Types) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.379: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.385: fun hidden(module types: Types) = types.Item.extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.379: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.385: fun hidden(module types: Types) = types.Item.extra //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -402,7 +408,7 @@ class Child extends Base with fun apply[A](f: A -> A, x: A): A = f(x) apply[Base](arg => (arg.extra; arg), new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.403: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ l.409: apply[Base](arg => (arg.extra; arg), new Child) //│ ║ ^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -425,7 +431,7 @@ class Child extends Base with fun read(xs: [Base, Int]) = xs.0.extra read([new Child, 0]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.425: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ l.431: fun read(xs: [Base, Int]) = xs.0.extra //│ ║ ^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -446,11 +452,11 @@ let xs = mut [First(1)] set xs.0 = Second(2) [...xs].0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.447: [...xs].0.first +//│ ║ l.453: [...xs].0.first //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'first' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.445: let xs = mut [First(1)] +//│ ║ l.451: let xs = mut [First(1)] //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -459,10 +465,10 @@ set xs.0 = Second(2) :ge [1, 2].2 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.460: [1, 2].2 +//│ ║ l.466: [1, 2].2 //│ ║ ^^^^^^^^ //│ ╟── Tuple literal does not contain member '2' -//│ ║ l.460: [1, 2].2 +//│ ║ l.466: [1, 2].2 //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -486,3 +492,44 @@ set xs.1 = 42 xs.1 //│ = 42 //│ xs = [1, 42] + +// An inherited field retains its missing-annotation diagnostic after value forwarding. +:ge +class Item with + val extra = 42 +class Parent(val item) +class Child(input) extends Parent(input) +type Annotated = Child +private fun identity(value) = value +fun read(box: Annotated) = identity(box.item).extra +read(Child(new Item)) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.504: fun read(box: Annotated) = identity(box.item).extra +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'extra' of a value with unknown shape. +//│ ╟── Field 'item' is accessed through an annotated class type, so constructor arguments do not determine its shape. +//│ ║ l.504: fun read(box: Annotated) = identity(box.item).extra +//│ ║ ^^^^^^^^ +//│ ╟── Constructor parameter 'item' has no type annotation. +//│ ║ l.500: class Parent(val item) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Calling an unannotated field reports the same declared-interface restriction. +:ge +class Box(val callback) +fun invoke(box: Box) = box.callback() +invoke(Box(() => 42)) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.521: fun invoke(box: Box) = box.callback() +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.521: fun invoke(box: Box) = box.callback() +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Field 'callback' is accessed through an annotated class type, so constructor arguments do not determine its shape. +//│ ║ l.521: fun invoke(box: Box) = box.callback() +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Constructor parameter 'callback' has no type annotation. +//│ ║ l.520: class Box(val callback) +//│ ╙── ^^^^^^^^ +//│ = 42 From f96ec7e4d9303592e42670e4508ee34ddc22929b Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 17:33:45 +0800 Subject: [PATCH 078/228] Identify type annotations in unknown-shape diagnostics --- .../scala/hkmc2/semantics/NewResolver.scala | 50 +++-- .../main/scala/hkmc2/semantics/Shape.scala | 21 +- .../test/mlscript/newres/DeclaredTypes.mls | 196 ++++++++++++++---- .../mlscript/newres/InterfaceExposure.mls | 133 ++++++------ .../src/test/mlscript/newres/ReslScratch.mls | 10 +- .../newres/loose/InterfaceExposure.mls | 24 ++- 6 files changed, 306 insertions(+), 128 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index cc5eac4830..dd6cb8be60 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -187,12 +187,12 @@ class NewResolver: case TypeShape.Nominal(defn) => val bindings = bind(defn.tparams) defn.ext match - case N => publish(NominalTypeShape(defn, bindings, N)) + case N => publish(NominalTypeShape(defn, bindings, N)(S(tpe.resolution.source))) case S(parent) => // Only the parent's declared type is relevant here. Evaluating // its constructor arguments would reintroduce implementation flow. listenTypeValues(declaredType(typeResolution(parent.cls), bindings)): ext => - publish(NominalTypeShape(defn, bindings, S(ext))) + publish(NominalTypeShape(defn, bindings, S(ext))(S(tpe.resolution.source))) case TypeShape.Alias(symbol, rhs) => // Revisiting the same alias reference without reaching an outer // nominal/function shape supplies no additional interface. Track @@ -208,7 +208,18 @@ class NewResolver: case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match case S(bound) => follow(bound, Nil, aliases, publish) case N => - listenTypeArgument(host)(publish) + listenTypeArgument(host): value => + // Report the use of an abstract parameter in a signature, not + // just its declaration. This survives substitution through + // generic members and nested tuple or function annotations. + val annotated = value match + case Marked(unknown: UnknownValueShape, marks) => + UnknownValueShape(unknown.source)(unknown.provenance.via( + msg"This type annotation supplies the value's shape." -> tpe.resolution.source.toLoc)).exit(marks) + case _ => value + annotated match + case value: TermShape => publish(value) + case NoShape => () case TypeShape.Captured(base, thru) => follow(declaredType(base, current.bindings), args, aliases, shape => publish(shape match @@ -265,7 +276,7 @@ class NewResolver: }) private def listenDeclaredMember(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], flow: FlowSymbol, - source: Term, selected: ShapeListener[DefinitionSymbol[?]])(listener: Listener)(using NewResolverState): Unit = + source: Term, annotation: Opt[Term], selected: ShapeListener[DefinitionSymbol[?]])(listener: Listener)(using NewResolverState): Unit = member.onComplete: () => member.asModOrObj.orElse(member.asTrm).orElse(member.asCls) match case S(symbol: TermSymbol) if !symbol.isInstanceOf[ClassCtorSymbol] => @@ -308,8 +319,9 @@ class NewResolver: // later selections and calls can explain why argument flow is hidden. val unknown = td.tsym.decl match case S(param: Param) => UnknownValueShape(source)(ShapeProvenance( - (msg"Field '${member.nme}' is accessed through an annotated class type, so constructor arguments do not determine its shape." -> source.toLoc) :: - (msg"Constructor parameter '${param.sym.nme}' has no type annotation." -> param.toLoc) :: Nil)) + (msg"Constructor parameter '${param.sym.nme}' has no type annotation." -> param.toLoc) :: + annotation.toList.map(sign => + msg"This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape." -> sign.toLoc))) case _ => UnknownValueShape.at(source) publish(unknown) case _ => @@ -522,7 +534,7 @@ class NewResolver: val cls = prelude.builtins.Array.defn.get softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") val elements = new TypeResolution(tuple.source, messages => resolError(tuple.source, messages)) - val parent = NominalTypeShape(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), N) + val parent = NominalTypeShape(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), N)(N) tupleArrayParents(new Identity(tuple)) = parent tuple.segments.foreach: case field: TupleShape.Fixed => listenTupleField(field): value => @@ -749,8 +761,8 @@ class NewResolver: case MemberLookup.Found(sym: BlockMemberSymbol, marks) => val field = symShapes.getOrElseUpdate((sym, resSym, marks), SymShape(sym, resSym, marks)) matchShapePat(field, pat)(_ => ()) - case MemberLookup.Declared(member, bindings, marks) => - listenDeclaredMember(member, bindings, resSym, ctor.target, _ => ()): field => + case MemberLookup.Declared(member, bindings, marks, annotation) => + listenDeclaredMember(member, bindings, resSym, ctor.target, annotation, _ => ()): field => field.exit(marks) match case value: TermShape => val field = (value.applicationHead._1, unknown) match @@ -922,9 +934,11 @@ class NewResolver: publish(UnknownValueShape.at(field.rhs)) private def publishDeclared(host: NewResolvable & ShapeHost, member: BlockMemberSymbol, flow: FlowSymbol, - bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks])(using NewResolverState): Unit = + bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term])(using NewResolverState): Unit = + // Keep one diagnostic witness per semantic candidate, as for unknown values. + // A different annotation location must not create additional inference flow. val shape = declaredSymShapes.getOrElseUpdate((member, flow, marks, bindings), - DeclaredSymShape(member, flow, marks, bindings)) + DeclaredSymShape(member, flow, marks, bindings, annotation)) if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) private def publishDynamic(host: NewResolvable & ShapeHost, marks: Ls[Marks])(using NewResolverState): Unit = @@ -950,10 +964,10 @@ class NewResolver: val candidate = prefix -> member.memberSymbol rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) publishMember(ref, member, ref.resSym, marks) - case MemberLookup.Declared(member, bindings, marks) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member) => + case MemberLookup.Declared(member, bindings, marks, annotation) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member) => val candidate = prefix -> member rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) - publishDeclared(ref, member, ref.resSym, bindings, marks) + publishDeclared(ref, member, ref.resSym, bindings, marks, annotation) case MemberLookup.Indexed(_, _) if rstate.canResolve(ref) => resolError(ref, msg"Tuple elements must be selected by index." -> ref.toLoc :: Nil) case MemberLookup.Dynamic(marks) if rstate.canResolve(ref) || ref.dynamicPrefixes.contains(prefix) => @@ -1010,9 +1024,9 @@ class NewResolver: log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") rstate.recordResolution(sel, sel.resolvedMembers.contains(bms.memberSymbol))(sel.resolvedMembers ::= bms.memberSymbol) publishMember(sel, bms, sel.resSym, marks) - case MemberLookup.Declared(member, bindings, marks) if rstate.canResolve(sel) || sel.resolvedMembers.contains(member) => + case MemberLookup.Declared(member, bindings, marks, annotation) if rstate.canResolve(sel) || sel.resolvedMembers.contains(member) => rstate.recordResolution(sel, sel.resolvedMembers.contains(member))(sel.resolvedMembers ::= member) - publishDeclared(sel, member, sel.resSym, bindings, marks) + publishDeclared(sel, member, sel.resSym, bindings, marks, annotation) case MemberLookup.Indexed(field, marks) if rstate.canResolve(sel) || sel.tupleIndex == sel.id.name.toIntOption => val index = sel.id.name.toIntOption softAssert(index.exists(_ >= 0), "Tuple lookup must identify a nonnegative index") @@ -1055,12 +1069,12 @@ class NewResolver: val selected = receiver match case Marked(nominal: NominalTypeShape, context) => nominal.ancestor(cd) match - case S(view) => view.getMember(sel.id.name).withMarks(context :: Nil) + case S(view) => view.getMember(sel.id.name).withAnnotation(nominal.annotation).withMarks(context :: Nil) case N => // An explicit projection can name a narrower class, but // it cannot recover that class's implementation arguments. val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) - MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil) + MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation) case _ => info member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc) case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc)) @@ -1167,7 +1181,7 @@ class NewResolver: private def fromSymbol(shape: SymShape, listener: Listener, source: Term, selected: ShapeListener[DefinitionSymbol[?]])(using NewResolverState): Unit = shape match case declared: DeclaredSymShape => - listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, selected): value => + listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, declared.annotation, selected): value => value.exit(shape.markss) match case value: TermShape => listener(value) case NoShape => () diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index c8e31d3d68..26c7c6325f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -261,7 +261,7 @@ enum MemberLookup: case Found(member: BlockMemberSymbol | RecordMember, marks: Ls[Marks]) // Declared members expose signatures only. Their marks transport dependent // type arguments; they never authorize reading an implementation's value flow. - case Declared(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks]) + case Declared(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term]) case Indexed(field: TupleShape.Fixed, marks: Ls[Marks]) case Dynamic(marks: Ls[Marks]) case Missing @@ -269,11 +269,16 @@ enum MemberLookup: def withMarks(marks: Ls[Marks]): MemberLookup = this match case Found(member, inner) => Found(member, inner ::: marks) - case Declared(member, bindings, inner) => Declared(member, bindings, inner ::: marks) + case Declared(member, bindings, inner, annotation) => Declared(member, bindings, inner ::: marks, annotation) case Indexed(field, inner) => Indexed(field, inner ::: marks) case Dynamic(inner) => Dynamic(inner ::: marks) case _ => this + /** The receiver's annotation remains the diagnostic origin when lookup visits a parent. */ + def withAnnotation(annotation: Opt[Term]): MemberLookup = this match + case Declared(member, bindings, marks, _) => Declared(member, bindings, marks, annotation) + case _ => this + object MemberLookup: enum Uncertainty: case ValueShape, RecordOverwrite @@ -339,7 +344,7 @@ class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: L * type, and constructor interpretations of the same overload set. */ final class DeclaredSymShape(symbol: BlockMemberSymbol, site: FlowSymbol, marks: Ls[Marks], - val bindings: Map[VarSymbol, DeclaredType]) extends SymShape(symbol, site, marks) + val bindings: Map[VarSymbol, DeclaredType], val annotation: Opt[Term]) extends SymShape(symbol, site, marks) /* class ThisShape(val defn: Definition) extends NonAppTermShape: @@ -359,9 +364,11 @@ class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppT /** A nominal annotation exposes only declarations, including inherited declarations. * In particular, selecting an unannotated field does not inspect its initializer. + * The annotation is a diagnostic witness, excluded from shape equality; synthesized + * interfaces such as a tuple's Array parent have no written annotation. */ final case class NominalTypeShape(defn: ClassLikeDef, bindings: Map[VarSymbol, DeclaredType], - parent: Opt[TermShape]) extends NonAppTermShape: + parent: Opt[TermShape])(val annotation: Opt[Term]) extends NonAppTermShape: def describe: Str = s"value of type '${defn.sym.nme}'" def toLoc: Opt[Loc] = defn.toLoc override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = @@ -373,8 +380,8 @@ final case class NominalTypeShape(defn: ClassLikeDef, bindings: Map[VarSymbol, D case _ => N protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = defn.body.members.get(name) match - case S(member) => MemberLookup.Declared(member, bindings, Nil) - case N => parent.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)) + case S(member) => MemberLookup.Declared(member, bindings, Nil, annotation) + case N => parent.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)).withAnnotation(annotation) /** Parameter types and arity exposed by one list in a declared calling interface. */ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool) @@ -600,7 +607,7 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] case Missing => loop(rest) case Found(member: RecordMember, inner) => Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil) - case Found(_: BlockMemberSymbol, _) | Declared(_, _, _) | Indexed(_, _) => + case Found(_: BlockMemberSymbol, _) | Declared(_, _, _, _) | Indexed(_, _) => // Record spreads recursively look up RecordShapes, which only create RecordMembers. lastWords("Record lookup returned a nominal member") loop(elements.reverse) diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 98090f5f75..fc18ac51bf 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -77,12 +77,12 @@ read(Box(new Item)) //│ ║ l.74: fun read(box: Box) = box.item.extra //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ╟── Field 'item' is accessed through an annotated class type, so constructor arguments do not determine its shape. -//│ ║ l.74: fun read(box: Box) = box.item.extra -//│ ║ ^^^^^^^^ //│ ╟── Constructor parameter 'item' has no type annotation. //│ ║ l.73: class Box(val item) -//│ ╙── ^^^^ +//│ ║ ^^^^ +//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ║ l.74: fun read(box: Box) = box.item.extra +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // An unannotated method result cannot reveal the method body either. @@ -228,6 +228,9 @@ invoke[() -> Int](() => 42) //│ ╟── Value of unknown shape cannot be called like a function. //│ ║ l.223: private fun invoke[T](value: T) = value() //│ ║ ^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.223: private fun invoke[T](value: T) = value() +//│ ║ ^ //│ ╟── Type parameter 'T' does not specify a member interface. //│ ║ l.223: private fun invoke[T](value: T) = value() //│ ╙── ^ @@ -241,15 +244,15 @@ class Box(val item) fun read(box: Box) = box.Box#item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.241: fun read(box: Box) = box.Box#item.extra +//│ ║ l.244: fun read(box: Box) = box.Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ╟── Field 'item' is accessed through an annotated class type, so constructor arguments do not determine its shape. -//│ ║ l.241: fun read(box: Box) = box.Box#item.extra -//│ ║ ^^^^^^^^^^^^ //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.240: class Box(val item) -//│ ╙── ^^^^ +//│ ║ l.243: class Box(val item) +//│ ║ ^^^^ +//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ║ l.244: fun read(box: Box) = box.Box#item.extra +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // A signature alias supplies the interface to both the body and callers. @@ -278,7 +281,7 @@ class Child extends Base with fun identity[T](x: T): T = x identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.279: identity[Base](new Child).extra +//│ ║ l.282: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -320,11 +323,11 @@ class Box[T](val value: T) (new Box[Base](new Child)).value.extra Box[Base](new Child).value.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.320: (new Box[Base](new Child)).value.extra +//│ ║ l.323: (new Box[Base](new Child)).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.321: Box[Base](new Child).value.extra +//│ ║ l.324: Box[Base](new Child).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -367,11 +370,11 @@ class Box with val item = new Item (new Box as Box).Box#item.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.368: (new Box as Box).Box#item.extra +//│ ║ l.371: (new Box as Box).Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.368: (new Box as Box).Box#item.extra +//│ ║ l.371: (new Box as Box).Box#item.extra //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -385,11 +388,11 @@ module Types with fun hidden(module types: Types) = types.Item.extra hidden(Types) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.385: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.388: fun hidden(module types: Types) = types.Item.extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.385: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.388: fun hidden(module types: Types) = types.Item.extra //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -408,7 +411,7 @@ class Child extends Base with fun apply[A](f: A -> A, x: A): A = f(x) apply[Base](arg => (arg.extra; arg), new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.409: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ l.412: apply[Base](arg => (arg.extra; arg), new Child) //│ ║ ^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -431,7 +434,7 @@ class Child extends Base with fun read(xs: [Base, Int]) = xs.0.extra read([new Child, 0]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.431: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ l.434: fun read(xs: [Base, Int]) = xs.0.extra //│ ║ ^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -452,11 +455,11 @@ let xs = mut [First(1)] set xs.0 = Second(2) [...xs].0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.453: [...xs].0.first +//│ ║ l.456: [...xs].0.first //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'first' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.451: let xs = mut [First(1)] +//│ ║ l.454: let xs = mut [First(1)] //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -465,10 +468,10 @@ set xs.0 = Second(2) :ge [1, 2].2 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.466: [1, 2].2 +//│ ║ l.469: [1, 2].2 //│ ║ ^^^^^^^^ //│ ╟── Tuple literal does not contain member '2' -//│ ║ l.466: [1, 2].2 +//│ ║ l.469: [1, 2].2 //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -504,15 +507,15 @@ private fun identity(value) = value fun read(box: Annotated) = identity(box.item).extra read(Child(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.504: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.507: fun read(box: Annotated) = identity(box.item).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. -//│ ╟── Field 'item' is accessed through an annotated class type, so constructor arguments do not determine its shape. -//│ ║ l.504: fun read(box: Annotated) = identity(box.item).extra -//│ ║ ^^^^^^^^ //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.500: class Parent(val item) -//│ ╙── ^^^^ +//│ ║ l.503: class Parent(val item) +//│ ║ ^^^^ +//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ║ l.507: fun read(box: Annotated) = identity(box.item).extra +//│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Calling an unannotated field reports the same declared-interface restriction. @@ -521,15 +524,136 @@ class Box(val callback) fun invoke(box: Box) = box.callback() invoke(Box(() => 42)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.521: fun invoke(box: Box) = box.callback() +//│ ║ l.524: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.521: fun invoke(box: Box) = box.callback() -//│ ║ ^^^^^^^^^^^^ -//│ ╟── Field 'callback' is accessed through an annotated class type, so constructor arguments do not determine its shape. -//│ ║ l.521: fun invoke(box: Box) = box.callback() +//│ ║ l.524: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^ //│ ╟── Constructor parameter 'callback' has no type annotation. -//│ ║ l.520: class Box(val callback) -//│ ╙── ^^^^^^^^ +//│ ║ l.523: class Box(val callback) +//│ ║ ^^^^^^^^ +//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ║ l.524: fun invoke(box: Box) = box.callback() +//│ ╙── ^^^ +//│ = 42 + +// Abstract tuple elements retain the use of the type parameter in the receiver annotation. +:ge +class Box[X](val value: [X, X]) +fun foo[A](x: Box[A]) = x.value.0.oops +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.543: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'oops' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.543: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ ^ +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.543: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ╙── ^ + +// Concrete callers cannot hide the annotation responsible for an abstract tuple element. +:ge +class Item with + val oops = 42 +class Box[X](val value: [X, X]) +private fun foo[A](x: Box[A]) = x.value.0.oops +foo[Item](Box[Item]([new Item, new Item])) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.560: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'oops' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.560: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ ^ +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.560: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Alias substitution and a function result still lead back to the written type argument. +:ge +class Box[X](val value: [X, X]) +type Wrapped[T] = Box[T] +fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.578: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'oops' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.578: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ ^ +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.578: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ╙── ^ + +// Tuple annotations do not need a nominal receiver to produce an abstract element. +:ge +fun foo[A](x: [A, A]) = x.0.oops +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.592: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ ^^^^^^^^ +//│ ╟── Cannot resolve member 'oops' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.592: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ ^ +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.592: fun foo[A](x: [A, A]) = x.0.oops +//│ ╙── ^ + +// An unannotated field can be reached through a function result annotation and an alias. +:ge +class Box(val value) +type Wrapped = Box +fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.608: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'oops' of a value with unknown shape. +//│ ╟── Constructor parameter 'value' has no type annotation. +//│ ║ l.606: class Box(val value) +//│ ║ ^^^^^ +//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ║ l.608: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ╙── ^^^^^^^ + +// Explicit ascription supplies the annotation even when its operand is a known value. +:ge +class Item with + val oops = 42 +class Box(val value) +(Box(new Item) as Box).value.oops +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.625: (Box(new Item) as Box).value.oops +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'oops' of a value with unknown shape. +//│ ╟── Constructor parameter 'value' has no type annotation. +//│ ║ l.624: class Box(val value) +//│ ║ ^^^^^ +//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ║ l.625: (Box(new Item) as Box).value.oops +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// An abstract function result remains unknown after a nested tuple projection. +:ge +fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.640: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'oops' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.640: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ ^ +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.640: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ╙── ^ + +// A concrete annotation supplies the nested member interface without losing tuple precision. +:expect 42 +class Item with + val oops = 42 +class Box[X](val value: [X, X]) +fun foo(x: Box[Item]) = x.value.0.oops +foo(Box[Item]([new Item, new Item])) //│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls index 06b33f02dc..5fe7d2b2f5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls @@ -24,6 +24,9 @@ foo({a: 1}) //│ ║ l.21: fun foo[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.21: fun foo[A](x: A) = x.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. //│ ║ l.21: fun foo[A](x: A) = x.a //│ ╙── ^ @@ -34,11 +37,14 @@ foo({a: 1}) private fun privateGeneric[A](x: A) = x.a privateGeneric({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.34: private fun privateGeneric[A](x: A) = x.a +//│ ║ l.37: private fun privateGeneric[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.37: private fun privateGeneric[A](x: A) = x.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.34: private fun privateGeneric[A](x: A) = x.a +//│ ║ l.37: private fun privateGeneric[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -48,11 +54,14 @@ private class GenericClass[A](value: A) with fun read() = (() => value.a)() (new GenericClass({a: 1})).read() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.48: fun read() = (() => value.a)() +//│ ║ l.54: fun read() = (() => value.a)() //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.53: private class GenericClass[A](value: A) with +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.47: private class GenericClass[A](value: A) with +//│ ║ l.53: private class GenericClass[A](value: A) with //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -79,16 +88,16 @@ foo(new A(1)) fun apply(f) = f(1) apply(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.79: fun apply(f) = f(1) +//│ ║ l.88: fun apply(f) = f(1) //│ ║ ^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.79: fun apply(f) = f(1) +//│ ║ l.88: fun apply(f) = f(1) //│ ║ ^ //│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.79: fun apply(f) = f(1) +//│ ║ l.88: fun apply(f) = f(1) //│ ║ ^ //│ ╟── 'apply' is exposed by this compilation unit. -//│ ║ l.79: fun apply(f) = f(1) +//│ ║ l.88: fun apply(f) = f(1) //│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ = 1 @@ -102,17 +111,17 @@ apply(x => x) :e fun escaped() = x => x.a //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.103: fun escaped() = x => x.a +//│ ║ l.112: fun escaped() = x => x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.103: fun escaped() = x => x.a +//│ ║ l.112: fun escaped() = x => x.a //│ ║ ^ //│ ╟── This value is returned here. -//│ ║ l.103: fun escaped() = x => x.a +//│ ║ l.112: fun escaped() = x => x.a //│ ║ ^^^^^^^^ //│ ╟── 'escaped' is exposed by this compilation unit. -//│ ║ l.103: fun escaped() = x => x.a +//│ ║ l.112: fun escaped() = x => x.a //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ :e @@ -120,17 +129,17 @@ private fun hidden(x) = x.a val escaped = [hidden] hidden({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.119: private fun hidden(x) = x.a +//│ ║ l.128: private fun hidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.119: private fun hidden(x) = x.a +//│ ║ l.128: private fun hidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this tuple. -//│ ║ l.120: val escaped = [hidden] +//│ ║ l.129: val escaped = [hidden] //│ ║ ^^^^^^^^ //│ ╟── 'escaped' is exposed by this compilation unit. -//│ ║ l.120: val escaped = [hidden] +//│ ║ l.129: val escaped = [hidden] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ escaped = [fun hidden] @@ -140,17 +149,17 @@ private fun hidden(x) = x.a val escaped = {f: hidden} hidden({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.139: private fun hidden(x) = x.a +//│ ║ l.148: private fun hidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.139: private fun hidden(x) = x.a +//│ ║ l.148: private fun hidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.140: val escaped = {f: hidden} +//│ ║ l.149: val escaped = {f: hidden} //│ ║ ^ //│ ╟── 'escaped' is exposed by this compilation unit. -//│ ║ l.140: val escaped = {f: hidden} +//│ ║ l.149: val escaped = {f: hidden} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ escaped = {f: fun hidden} @@ -175,20 +184,20 @@ private fun hiddenRecord(x) = x.a fun exportRecord() = {callback: hiddenRecord} hiddenRecord({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.174: private fun hiddenRecord(x) = x.a +//│ ║ l.183: private fun hiddenRecord(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.174: private fun hiddenRecord(x) = x.a +//│ ║ l.183: private fun hiddenRecord(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.175: fun exportRecord() = {callback: hiddenRecord} +//│ ║ l.184: fun exportRecord() = {callback: hiddenRecord} //│ ║ ^^^^^^^^ //│ ╟── This value is returned here. -//│ ║ l.175: fun exportRecord() = {callback: hiddenRecord} +//│ ║ l.184: fun exportRecord() = {callback: hiddenRecord} //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── 'exportRecord' is exposed by this compilation unit. -//│ ║ l.175: fun exportRecord() = {callback: hiddenRecord} +//│ ║ l.184: fun exportRecord() = {callback: hiddenRecord} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -200,16 +209,16 @@ class CopyBox(val value) with fun copy() = CopyBox(value) CopyBox({answer: 42}).copy().value.answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.201: CopyBox({answer: 42}).copy().value.answer +//│ ║ l.210: CopyBox({answer: 42}).copy().value.answer //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. //│ ╟── Parameter 'value' admits values of unknown shape. -//│ ║ l.199: class CopyBox(val value) with +//│ ║ l.208: class CopyBox(val value) with //│ ║ ^^^^^ //│ ╟── 'CopyBox' is exposed by this compilation unit. -//│ ║ l.199: class CopyBox(val value) with +//│ ║ l.208: class CopyBox(val value) with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.200: fun copy() = CopyBox(value) +//│ ║ l.209: fun copy() = CopyBox(value) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -222,17 +231,17 @@ oldHidden({a: 1}) :e val oldEscape = {callback: oldHidden} //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.218: private fun oldHidden(x) = x.a +//│ ║ l.227: private fun oldHidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.218: private fun oldHidden(x) = x.a +//│ ║ l.227: private fun oldHidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.223: val oldEscape = {callback: oldHidden} +//│ ║ l.232: val oldEscape = {callback: oldHidden} //│ ║ ^^^^^^^^ //│ ╟── 'oldEscape' is exposed by this compilation unit. -//│ ║ l.223: val oldEscape = {callback: oldHidden} +//│ ║ l.232: val oldEscape = {callback: oldHidden} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ oldEscape = {callback: fun oldHidden} @@ -241,14 +250,17 @@ val oldEscape = {callback: oldHidden} fun spreadRecord[A](x: A) = ({...x}).a spreadRecord({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.241: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ l.250: fun spreadRecord[A](x: A) = ({...x}).a //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. //│ ╟── This spread has no known element shape. -//│ ║ l.241: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ l.250: fun spreadRecord[A](x: A) = ({...x}).a //│ ║ ^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.250: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.241: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ l.250: fun spreadRecord[A](x: A) = ({...x}).a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -257,14 +269,17 @@ spreadRecord({a: 1}) fun spreadTuple[A](x: A) = [...x].0.a spreadTuple([{a: 1}]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.257: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ l.269: fun spreadTuple[A](x: A) = [...x].0.a //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This spread has no known element shape. -//│ ║ l.257: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ l.269: fun spreadTuple[A](x: A) = [...x].0.a //│ ║ ^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.269: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.257: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ l.269: fun spreadTuple[A](x: A) = [...x].0.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -279,20 +294,20 @@ private fun recursiveHidden(x) = x.a fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) recursiveHidden({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.278: private fun recursiveHidden(x) = x.a +//│ ║ l.293: private fun recursiveHidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.278: private fun recursiveHidden(x) = x.a +//│ ║ l.293: private fun recursiveHidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.279: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ l.294: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) //│ ║ ^^^^^^^^ //│ ╟── This value is returned here. -//│ ║ l.279: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ l.294: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── 'recursiveExport' is exposed by this compilation unit. -//│ ║ l.279: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ l.294: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -303,23 +318,23 @@ class Shadowed { } val Shadowed = 0 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.302: fun bad(x) = x.a +//│ ║ l.317: fun bad(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.302: fun bad(x) = x.a +//│ ║ l.317: fun bad(x) = x.a //│ ║ ^ //│ ╟── Member 'bad' is accessible here. -//│ ║ l.302: fun bad(x) = x.a +//│ ║ l.317: fun bad(x) = x.a //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── 'Shadowed' is exposed by this compilation unit. -//│ ║ l.301: class Shadowed { +//│ ║ l.316: class Shadowed { //│ ║ ^^^^^^^^^^^^^^^^ -//│ ║ l.302: fun bad(x) = x.a +//│ ║ l.317: fun bad(x) = x.a //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.303: } +//│ ║ l.318: } //│ ║ ^ -//│ ║ l.304: val Shadowed = 0 +//│ ║ l.319: val Shadowed = 0 //│ ╙── ^^^^^^^^^^^^^^^^ //│ Shadowed = 0 @@ -329,17 +344,17 @@ class Holder(val value) fun inspect(x) = if x is Holder(y) then y.a else 0 inspect(Holder({a: 1})) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.329: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.344: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This constructor field may contain a value of unknown shape. -//│ ║ l.328: class Holder(val value) +//│ ║ l.343: class Holder(val value) //│ ║ ^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.329: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.344: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ║ ^ //│ ╟── 'inspect' is exposed by this compilation unit. -//│ ║ l.329: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.344: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -357,17 +372,17 @@ class GenericHolder[A](val value: A) fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 inspectGeneric(GenericHolder({a: 1})) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.357: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.372: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This constructor field may contain a value of unknown shape. -//│ ║ l.356: class GenericHolder[A](val value: A) +//│ ║ l.371: class GenericHolder[A](val value: A) //│ ║ ^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.357: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.372: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ║ ^ //│ ╟── 'inspectGeneric' is exposed by this compilation unit. -//│ ║ l.357: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.372: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index ade331e142..09865d7813 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -205,6 +205,9 @@ foo({a: 1}) //│ ║ l.202: fun foo[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.202: fun foo[A](x: A) = x.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. //│ ║ l.202: fun foo[A](x: A) = x.a //│ ╙── ^ @@ -214,11 +217,14 @@ foo({a: 1}) private fun foo[A](x: A) = x.a foo({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.214: private fun foo[A](x: A) = x.a +//│ ║ l.217: private fun foo[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.217: private fun foo[A](x: A) = x.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.214: private fun foo[A](x: A) = x.a +//│ ║ l.217: private fun foo[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls index c3df4353b8..9eebcd1f72 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls @@ -19,6 +19,9 @@ generic({a: 1}) //│ ║ l.16: fun generic[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.16: fun generic[A](x: A) = x.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. //│ ║ l.16: fun generic[A](x: A) = x.a //│ ╙── ^ @@ -28,11 +31,14 @@ generic({a: 1}) private fun generic[A](x: A) = x.a generic({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.28: private fun generic[A](x: A) = x.a +//│ ║ l.31: private fun generic[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.31: private fun generic[A](x: A) = x.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.28: private fun generic[A](x: A) = x.a +//│ ║ l.31: private fun generic[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -41,11 +47,14 @@ class Item(val a: Int) private fun generic[A](x: A) = x.a generic[Item](Item(1)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.41: private fun generic[A](x: A) = x.a +//│ ║ l.47: private fun generic[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.47: private fun generic[A](x: A) = x.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.41: private fun generic[A](x: A) = x.a +//│ ║ l.47: private fun generic[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -59,9 +68,12 @@ fun identity[A](x: A): A = x :e private fun unused[A](x: A) = x.a //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.60: private fun unused[A](x: A) = x.a +//│ ║ l.69: private fun unused[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.69: private fun unused[A](x: A) = x.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.60: private fun unused[A](x: A) = x.a +//│ ║ l.69: private fun unused[A](x: A) = x.a //│ ╙── ^ From 11dc8ea7a31da5f53d63de9f745a6f9d3a640734 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 17:36:47 +0800 Subject: [PATCH 079/228] Resolve named tuple fields as records and support structural interfaces --- doc/new-resolution-suite-migration.md | 45 +++++---- .../scala/hkmc2/semantics/Elaborator.scala | 8 +- .../hkmc2/semantics/InterfaceExposure.scala | 2 +- .../scala/hkmc2/semantics/NewResolver.scala | 67 +++++++++++++- .../hkmc2/semantics/NewResolverState.scala | 4 + .../main/scala/hkmc2/semantics/Resolver.scala | 5 +- .../main/scala/hkmc2/semantics/Shape.scala | 13 +++ .../src/main/scala/hkmc2/semantics/Term.scala | 10 +- .../scala/hkmc2/semantics/TypeShape.scala | 2 + .../mlscript-compile/NamedFieldLibrary.mls | 9 ++ .../apps/parsing-web-demo/Examples.mls | 2 + .../mlscript-compile/apps/parsing/Rules.mls | 2 +- .../mlscript/newres/ImportedNamedFields.mls | 19 ++++ .../src/test/mlscript/newres/NamedFields.mls | 92 +++++++++++++++++++ .../test/mlscript/newres/RecordInterfaces.mls | 65 +++++++++++++ 15 files changed, 315 insertions(+), 30 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript-compile/NamedFieldLibrary.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/ImportedNamedFields.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/NamedFields.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index c1f3eb238f..3cd7cb78cb 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -89,14 +89,16 @@ interfaces even in non-strict mode. | Compilation suite | Newly migrated | Total migrated | Deferred | Total | | --- | ---: | ---: | ---: | ---: | -| Main (including quotes, UPS, and temporary legacy copy) | 21 | 25 | 23 | 48 | -| Applications | 3 | 3 | 17 | 20 | +| Main (including quotes, UPS, and regression/compatibility fixtures) | 21 | 26 | 23 | 49 | +| Applications | 4 | 4 | 16 | 20 | | Nofib | 12 | 12 | 27 | 39 | | WASM | 0 | 0 | 1 | 1 | -| Total | 36 | 40 | 68 | 108 | +| Total | 37 | 42 | 67 | 109 | -The added `LegacyOption.mls` is counted among the deferred fixtures: 67 original -fixtures remain unported, plus this temporary compatibility copy. +The added `LegacyOption.mls` is counted among the deferred fixtures: 66 original +fixtures remain unported, plus this temporary compatibility copy. The additional +`NamedFieldLibrary.mls` regression fixture uses new resolution and is counted in +the migrated total, but is not a port of one of the 107 original fixtures. The retained ports are: @@ -106,7 +108,7 @@ The retained ports are: `Gib12`, `Opened`, `QuoteFoo`, `QuoteInc`, and `SafeDiv`. - UPS: `DnfCnf`, `EvenOddTree`, and `TruthyFalsy`. - Applications: `parsing/PrattParsing`, `parsing/RecursiveDescent`, and - `parsing/TokenHelpers`. + `parsing/TokenHelpers`, plus `parsing-web-demo/Examples` after the named-field fix. - Nofib: `banner`, `boyer`, `boyer2`, `calendar`, `clausify`, `cryptarithm1`, `gcd`, `mandel`, `mandel2`, `puzzle`, `rsa`, and `sphere`. @@ -192,11 +194,13 @@ Other failures require compiler or prelude work: now use `dyn`; `HTMLElement` retains its nominal superclass identity with dynamic host members. Implicit generated `toString` methods on user classes also need a declared resolution interface. -4. **Argument grouping and imported lowering forms.** Several parser APIs take - one record argument but currently see its named fields as multiple positional - arguments. `Parser` also encounters a synthesized selection during new - resolution. These require changes to argument interpretation and the import - boundary, respectively. +4. **Imported lowering forms.** `Parser` encounters a synthesized selection + during new resolution, requiring import-boundary work. Named-field argument + grouping is fixed: tuple shapes now match lowering's positional values followed + by one record. `Extension`, `Rules`, `Token`, and web-demo `Examples` were + retried; their named-field arity errors are gone, and `Examples` is now ported. + `Rules.listLike` declares its structural field interface; `makeBracketRule` + still needs one, and some imported receivers still have no resolved target. 5. **Quotation coverage.** Quasiquote lowering still lacks some new type-selection and wildcard-reference forms (`CSP`, `QuoteExample1`). @@ -237,18 +241,17 @@ legacy resolution; imports that exchange options use `LegacyOption` consistently | `apps/parsing/BasicExpr.mls`, `Expr.mls` | Compile independently; legacy worksheet consumers inspect new-resolution signatures. | | `apps/parsing/Accounting.mls` | Array callback arity, `reduce`, and receiver interfaces. | | `apps/parsing/CSV.mls` | Mutable nested-array elements lack the shape required for `push`. | -| `apps/parsing/Extension.mls` | Named record fields counted as three arguments instead of one. | +| `apps/parsing/Extension.mls` | Receivers for `display`, `add`, and `extendChoices` have no resolved target. | | `apps/parsing/Keywords.mls` | Generic/abstract string interfaces and patterns. | | `apps/parsing/Lexer.mls` | Exposed `options.noWhitespace` and pattern interpretations. | | `apps/parsing/ParseRule.mls` | Exposed `keyword.name` and subsequent patterns. | | `apps/parsing/ParseRuleVisualizer.mls` | Imported JS railroad API lacks member interfaces. | | `apps/parsing/Parser.mls` | Synthetic selections from imported legacy forms reach new resolution. | -| `apps/parsing/Rules.mls` | Named-record argument grouping. | +| `apps/parsing/Rules.mls` | `makeBracketRule` needs a structural parameter annotation; an `extendChoices` receiver has no resolved target. `listLike` and named-field grouping are fixed. | | `apps/parsing/Test.mls` | `flags.has` and `tracer.reset` lose shapes through tuple/import flow. | -| `apps/parsing/Token.mls` | Named-record grouping and exposed `literal.length`. | +| `apps/parsing/Token.mls` | Exposed string receivers need annotations; constructor-field shapes are missing in `display`. | | `apps/parsing/Tree.mls` | Constructor-field shapes for `length` and `slice`. | | `apps/parsing/TreeHelpers.mls` | Exposed `text.split` and generated `Tree.toString` interface. | -| `apps/parsing-web-demo/Examples.mls` | Named-record argument grouping in `MutMap.insert`. | | `apps/parsing-web-demo/main.mls` | String callback interfaces, dynamic receiver flow through aliases/mutation, and tuple-bound `example.name`. DOM declarations themselves are now dynamic. | | Nofib fixture (under `nofib/`) | Observed obstruction | @@ -268,7 +271,13 @@ The remaining WASM fixture, `wasm/Wasm.mls`, needs interfaces for 1. Named record/tuple fields elaborate their values with the enclosing interpretation. `value: expression` inside a term tuple is not a type - annotation. Existing `basics/NamedArgs.mls` exercises this distinction. + annotation. Tuple shapes collect these values into one trailing record, matching + lowering while retaining source evaluation order, last-write-wins lookup, + computed-key uncertainty, and property identities across imports. Structural + record annotations expose only their declared fields and support generic + substitution and callback interfaces. `newres/NamedFields.mls`, + `ImportedNamedFields.mls`, and `RecordInterfaces.mls` cover these behaviors; + `basics/NamedArgs.mls` also exercises the existing runtime convention. 2. Compiler-generated `runtime.assertFail` calls use a synthetic selection, like other runtime helpers. This is not a fallback for unresolved source selections. `basics/Assert.mls` and `DisruptiveComments.mls` retain runtime assertion tests. @@ -631,11 +640,11 @@ These changes also fix the sibling-subclass field-extraction regression in ## Validation -- `ctest`: all 48 selected compilation tests pass. +- `ctest`: all 49 selected compilation tests pass. - `catest`: all 20 application compilation fixtures pass with the retained mixed-mode ports. - `cntest`: all 39 Nofib compilation fixtures pass. - `cwtest`: the WASM compilation fixture passes. -- `hkmc2AllTests/test`: all 963 tests pass, including 715 main tests, 18 application +- `hkmc2AllTests/test`: all 967 tests pass, including 718 main tests, 18 application tests, and 22 WASM tests. The main count includes directory configurations and new-resolution regressions; it is not a count of migrated worksheets. - All retained ports have reviewed goldens. Legacy consumers that require the diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 9a951b9e4f..b33dded2e0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -2063,6 +2063,10 @@ extends Importer: InfixApp(_, Keywrd(Keyword.`=>`), _) (base, term(rrhs, fieldInterpretation)) val newAcc = rlhs match + case id: Ident if fieldInterpretation == Tpe => + // Record type fields declare interfaces, not local value bindings. + // Keep their types directly so lookup never follows initializer flow. + RcdField.signature(Term.Lit(StrLit(id.name)).withLocOf(id), rhs_t) :: acc case id: Ident => val sym = new VarSymbol(id, erasedType = N) newCtx += id.name -> sym @@ -2072,7 +2076,9 @@ extends Importer: :: acc case lit: Literal => reportUnusedAnnotations - RcdField(Term.Lit(lit).withLocOf(lit), rhs_t) :: acc + val field = Term.Lit(lit).withLocOf(lit) + (if fieldInterpretation == Tpe then RcdField.signature(field, rhs_t) + else RcdField(field, rhs_t)) :: acc case Bra(Round, inner) => reportUnusedAnnotations RcdField(term(inner, Trm), rhs_t) :: acc diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index 6fc527b618..3ffa5a1731 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -146,7 +146,7 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) emit(shape.exit(marks), path) // Nominal annotations expose their declared interface, not an initializer's // inferred implementation. Unknown and dynamic values have no static graph. - case _: (NominalTypeShape | OpaqueTypeShape | UnknownValueShape | DynShape | ErrShape) => () + case _: (NominalTypeShape | RecordTypeShape | OpaqueTypeShape | UnknownValueShape | DynShape | ErrShape) => () def check(exports: Ls[BlockMemberSymbol], values: Ls[Term]): Unit = try diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 21f6537bff..a66101cf21 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -117,7 +117,17 @@ class NewResolver: result.publish(TypeShape.Parameter(sym, sym.inferenceHost)) case Tup(fields) if fields.forall(_.isInstanceOf[Fld]) => result.publish(TypeShape.Tuple(fields.collect { case Fld(_, sign, _) => typeResolution(sign) })) - case _: WildcardTy | _: Neg | _: Rcd | _: Tup | _: Lit | Missing | Error() => + case record: Rcd => + val fields = record.stats.collect { case field: RcdField => field } + if fields.length != record.stats.length || fields.exists(field => field.field match + case Lit(_: Tree.StrLit) => false + case _ => true) + then + result.fail(msg"A record type requires statically named fields." -> record.toLoc :: Nil) + result.publish(TypeShape.Abstract) + else result.publish(TypeShape.Record(record, + fields.reverse.distinctBy(_.sym.nme).reverse.map(field => field -> typeResolution(field.rhs)))) + case _: WildcardTy | _: Neg | _: Tup | _: Lit | Missing | Error() => result.publish(TypeShape.Abstract) case _ => listen(term, discardMarks = true): case shape: SymShape => shape.sym.onComplete: () => @@ -227,6 +237,7 @@ class NewResolver: case TypeShape.Tuple(fields) => publish(TupleShape(current.resolution.source, fields.map(field => TupleShape.TypedField(declaredType(field, current.bindings), Nil)))(this)) + case TypeShape.Record(source, fields) => publish(RecordTypeShape(source, fields, current.bindings)) case TypeShape.Union(left, right) => next(left); next(right) case TypeShape.Intersection(left, right) => next(left); next(right) case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) @@ -392,6 +403,9 @@ class NewResolver: case NoShape => () case _ => () case _ => () + case TypeShape.Record(_, fields) => value.enter(captures) match + case actual: TermShape => constrainRecord(fields, tpe.bindings, actual, marks) + case NoShape => () case TypeShape.Function(_, _) => value.enter(captures) match case actual: TermShape => constrainFunction(declaredType(tpe.resolution, tpe.bindings), actual, marks) @@ -420,6 +434,30 @@ class NewResolver: // nominal argument, or arrow installs another edge with its own cycle guard. follow(tpe, Nil, Nil, Set.empty) + /** Structural constraints follow declared fields only. This supplies generic + * argument inference and callback checking without refining the annotation + * with additional properties from an actual record. + */ + private def constrainRecord(fields: Ls[(RcdField, TypeResolution)], bindings: Map[VarSymbol, DeclaredType], + actual: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = + fields.foreach: (field, sign) => + val expected = declaredType(sign, bindings) + val flow = FlowSymbol.memSym(field.sym)(using rstate.owner) + def receive(value: TermShape)(using NewResolverState): Unit = inferTypeArguments(expected, value, marks) + actual.getMember(field.sym.nme) match + case MemberLookup.Found(RecordMember(member, true), _) => receive(UnknownValueShape.at(member.rhs)) + case MemberLookup.Found(member, inner) => + fromBMS(member.memberSymbol, flow, inner, receive, field.rhs, _ => (), false) + case MemberLookup.Declared(member, bound, inner) => + listenDeclaredMember(member, bound, flow, field.rhs, _ => (), false): value => + value.exit(inner) match + case value: TermShape => receive(value) + case NoShape => () + case MemberLookup.Dynamic(inner) => DynShape().exit(inner) match + case value: TermShape => receive(value) + case NoShape => () + case _ => () + /** Function constraints reverse the parameter flow and preserve result flow. * Read an implementation only while checking it against its declared interface; * calls through that interface continue to expose only the declared result. @@ -468,6 +506,8 @@ class NewResolver: case NoShape => () case _ => matchLists(ret, tail) case Nil => () + case Marked(record: RecordTypeShape, _) => + constrainRecord(record.fields, record.bindings, actual, marks) case _ => () actual match case Marked(givenType: CallableTypeShape, actualContext) => @@ -1359,16 +1399,33 @@ class NewResolver: case shape => listener(shape) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => listenAggregate(tuple, listener): publish => + val fields = tuple.fields.collect { case Fld(_, key, S(value)) => (key, value) } + val named = if fields.isEmpty then Nil else + // Reuse the defining graph's property symbols when an imported tuple + // is observed directly, as well as when its copied listeners fire. + val graph = tuple.originalData.owner + assert(graph != null, "A tuple producer must have an inference owner") + val record = rstate.inGraph(graph.nn).namedTupleRecords.getOrElseUpdate(new Identity(tuple), { + val record: Rcd = Rcd(false, fields.map((key, value) => RcdField(key, value)(using rstate.owner))) + record.withLocOf(tuple) + record + }) + TupleShape.ValueField(RecordShape(record, record.stats.collect { + case field: RcdField => RecordShape.Field(field) + }), Nil) :: Nil def expand(elems: Ls[Elem], reversed: Ls[TupleShape.Element])(using NewResolverState): Unit = elems match case Nil => // A sole spread preserves its operand's shape and context exactly. // Besides avoiding wrappers, this lets recursive rest forwarding // reach the same fixed point as forwarding an ordinary parameter. - val shape = reversed match - case TupleShape.Spread(shape, NoMarks) :: Nil => shape - case TupleShape.Spread(shape, marks: SomeMarks) :: Nil => MarkedShape(shape, marks) - case _ => TupleShape(tuple, reversed.reverse)(this) + val shape = (reversed, named) match + case (TupleShape.Spread(shape, NoMarks) :: Nil, Nil) => shape + case (TupleShape.Spread(shape, marks: SomeMarks) :: Nil, Nil) => MarkedShape(shape, marks) + case _ => TupleShape(tuple, reversed.reverse ::: named)(this) publish(shape) + // Lowering evaluates fields in source order, but packs all named + // fields into one trailing record. Shape positions must match that layout. + case Fld(_, _, S(_)) :: rest => expand(rest, reversed) case (field: Fld) :: rest => expand(rest, TupleShape.Field(field, Nil) :: reversed) case Spd(_, term) :: rest => val spreadKey = new Object diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 01b1801ace..7a8579967a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -197,6 +197,10 @@ final class NewResolverState private ( new Cache(source.map(_.capturedTypes), identity) val tupleArrayParents: Cache[Identity[TupleShape], NominalTypeShape] = new Cache(source.map(_.tupleArrayParents), identity) + // Named tuple fields have stable property identities, shared with consumers + // through the tuple's original graph rather than allocated per spread candidate. + val namedTupleRecords: Cache[Identity[Tup], Rcd] = + new Cache(source.map(_.namedTupleRecords), identity) val aggregateProducers: Seen[Identity[Tup | Rcd]] = new Seen(source.map(_.aggregateProducers)) // Explicit instantiations are visible to every graph view in this consumer; diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index bf07d926dd..8d91b65b15 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -1065,6 +1065,9 @@ class Resolver(tl: TraceLogger) case Term.Ref(_) => case Term.Lit(_) => case Term.Tup(_) => t.subTerms.foreach(traverse(_, expect = NonModule(N))) + case Term.Rcd(_, stats) => stats.foreach: + case field: RcdField => traverseSign(field.rhs, expect = NonModule(N)) + case stat => raise(ErrorReport(msg"Expected a field declaration in record type." -> stat.toLoc :: Nil)) case Term.UnitVal() => // Literals with operators. e.g., -42 case Term.App(Term.Ref(_: BuiltinSymbol), Term.Tup(Fld(term = Term.Lit(_)) :: Nil)) => @@ -1168,7 +1171,7 @@ class Resolver(tl: TraceLogger) case Term.App(Term.Ref(_: BuiltinSymbol), Term.Tup(Fld(term = Term.Lit(_)) :: Nil)) => if expect.module then raiseError() else Type.NotImplemented // TODO: Support Lit with operator - case _: (Term.DynTy | Term.FunTy | Term.WildcardTy | Term.CompType | Term.Neg | Term.Forall | Term.Constrained | Term.Tup | Term.Lit) => + case _: (Term.DynTy | Term.FunTy | Term.WildcardTy | Term.CompType | Term.Neg | Term.Forall | Term.Constrained | Term.Tup | Term.Rcd | Term.Lit) => if expect.module then raiseError() else Type.NotImplemented // TODO: Support complex types diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index c611e99859..d6a0e2ba2a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -25,6 +25,7 @@ sealed trait Shape extends ShapeLike: case us: UnknownValueShape => s"UnknownValueShape(${us.source.showDbg})" case ts: TupleShape => s"TupleShape(${ts.source.showDbg})" case rs: RecordShape => s"RecordShape(${rs.source.showDbg})" + case rs: RecordTypeShape => s"RecordTypeShape(${rs.source.showDbg})" case ts: NominalTypeShape => s"NominalTypeShape(${ts.defn.sym.showDbg})" case ts: OpaqueTypeShape => s"OpaqueTypeShape(${ts.source.showDbg})" case ts: CallableTypeShape => s"CallableTypeShape(${ts.source.showDbg})" @@ -357,6 +358,18 @@ class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppT MemberLookup.inClass(defn, ext, name) def toLoc: Opt[Loc] = defn.toLoc +/** A structural annotation exposes only its declared fields. Their symbols belong + * to the annotation, not to whichever record happens to be passed by a caller. + */ +final case class RecordTypeShape(source: Term.Rcd, fields: Ls[(RcdField, TypeResolution)], + bindings: Map[VarSymbol, DeclaredType]) extends NonAppTermShape: + def describe: Str = "record type" + def toLoc: Opt[Loc] = source.toLoc + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = + fields.reverseIterator.collectFirst { + case (field, _) if field.sym.nme == name => MemberLookup.Declared(field.sym, bindings, Nil) + }.getOrElse(MemberLookup.Missing) + /** A nominal annotation exposes only declarations, including inherited declarations. * In particular, selecting an unannotated field does not inspect its initializer. */ diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 247ca3a442..192fb5495f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -1287,7 +1287,9 @@ final case class RcdField(field: Term, rhs: Term, sym: BlockMemberSymbol) extend case _ => () object RcdField: - def apply(field: Term, rhs: Term)(using State): RcdField = + def apply(field: Term, rhs: Term)(using State): RcdField = make(field, rhs, false) + def signature(field: Term, sign: Term)(using State): RcdField = make(field, sign, true) + private def make(field: Term, rhs: Term, signature: Bool)(using State): RcdField = val name = field match case Term.Lit(Tree.StrLit(name)) => name case _ => "computed field" @@ -1300,8 +1302,10 @@ object RcdField: // Wrap rhs in Capture to supply that mark, so the entry and exit cancel and // leave the enclosing call-site marks intact. Lowering evaluates RcdField.rhs; // it does not evaluate this synthetic definition separately. - tsym.defn = S(TermDefinition(RecordField, sym, tsym, Nil, N, N, - S(Term.Capture(rhs, tsym)), TermDefFlags.empty, Modulefulness.none, Nil, N)) + val captured = Term.Capture(rhs, tsym) + tsym.defn = S(TermDefinition(RecordField, sym, tsym, Nil, N, + if signature then S(captured) else N, + if signature then N else S(captured), TermDefFlags.empty, Modulefulness.none, Nil, N)) sym.complete() RcdField(field, rhs, sym) final case class RcdSpread(rcd: Term) extends Statement diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index beb72e2d44..239dce233c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -13,6 +13,7 @@ enum TypeShape: case Union(left: TypeResolution, right: TypeResolution) case Intersection(left: TypeResolution, right: TypeResolution) case Tuple(fields: Ls[TypeResolution]) + case Record(source: Term.Rcd, fields: Ls[(RcdField, TypeResolution)]) case Function(params: Term, result: TypeResolution) case Applied(base: TypeResolution, args: Ls[TypeResolution]) // The same third-party symbol can have different inference in two exporters. @@ -65,6 +66,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case Applied(base, args) => base.validate(next); args.foreach(_.validate(next)) case Function(_, result) => result.validate(next) case Tuple(fields) => fields.foreach(_.validate(next)) + case Record(_, fields) => fields.foreach(_._2.validate(next)) case Union(left, right) => left.validate(next); right.validate(next) case Intersection(left, right) => left.validate(next); right.validate(next) case _ => () diff --git a/hkmc2/shared/src/test/mlscript-compile/NamedFieldLibrary.mls b/hkmc2/shared/src/test/mlscript-compile/NamedFieldLibrary.mls new file mode 100644 index 0000000000..b3dc3b495b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/NamedFieldLibrary.mls @@ -0,0 +1,9 @@ +#lang(0.3.x) + +module NamedFieldLibrary with ... + +fun pack(x) = [value: x] +fun select(fields: {value: Int}) = fields.value + +val fields = [value: 36] +val observed = fields.0.value diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls index 486f942724..707b4f20cd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "../../MutMap.mls" import "../../Predef.mls" diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls index 5611cb852a..bb60fb51de 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls @@ -34,7 +34,7 @@ fun idFirst(value, _) = value fun idSecond(_, value) = value fun someFirst(value, _) = Some(value) fun listFirst(value, _) = value :: Nil -fun listLike(fields) = +fun listLike(fields: {head: Str, tail: Str, name: Str, sep: Keywords.Keyword | undefined}) = let mkTail = if fields.("sep") is undefined then id else keyword(fields.("sep")) reference(fields.head) of process: Cons, name: "the first " + fields.name diff --git a/hkmc2/shared/src/test/mlscript/newres/ImportedNamedFields.mls b/hkmc2/shared/src/test/mlscript/newres/ImportedNamedFields.mls new file mode 100644 index 0000000000..db2ce6db27 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ImportedNamedFields.mls @@ -0,0 +1,19 @@ +:. + +import "../../mlscript-compile/NamedFieldLibrary.mls" + +:expect ["A", 2, 3] +let a = NamedFieldLibrary.pack("a") +let b = NamedFieldLibrary.pack([1, 2]) +[a.0.value.toUpperCase(), b.0.value.length, NamedFieldLibrary.select(value: 3)] +//│ = ["A", 2, 3] +//│ a = [{value: "a"}] +//│ b = [{value: [1, 2]}] + +:expect [4, 5] +[NamedFieldLibrary.select(value: 4), NamedFieldLibrary.pack(5).0.value] +//│ = [4, 5] + +:expect [36, 36] +[NamedFieldLibrary.fields.0.value, NamedFieldLibrary.observed] +//│ = [36, 36] diff --git a/hkmc2/shared/src/test/mlscript/newres/NamedFields.mls b/hkmc2/shared/src/test/mlscript/newres/NamedFields.mls new file mode 100644 index 0000000000..864c1e128e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/NamedFields.mls @@ -0,0 +1,92 @@ +:. + +// Named values form one record after all positional values, even when interleaved. +:expect [3, 5, 7] +private fun named(fields) = [fields.a, fields.b, fields.c] +named(a: 3, b: 5, c: 7) +//│ = [3, 5, 7] + +:expect [1, 2, 3, 4] +private fun mixed(x, y, fields) = [x, y, fields.a, fields.b] +mixed(a: 3, 1, b: 4, 2) +//│ = [1, 2, 3, 4] + +:expect [2, 4, 6] +let inner = [2, a: 4] +let combined = [b: 6, ...inner] +[combined.0, combined.1.a, combined.2.b] +//│ = [2, 4, 6] +//│ combined = [2, {a: 4}, {b: 6}] +//│ inner = [2, {a: 4}] + +:expect [8, 9] +private fun forward(...xs) = named(...xs) +let result = forward(a: 7, b: 8, c: 9) +[result.1, result.2] +//│ = [8, 9] +//│ result = [7, 8, 9] + +// Field values retain function-call marks through returned tuples and record spreads. +:expect ["LEFT", 3] +fun pack(x) = [value: x] +let left = pack("left") +let right = pack([1, 2, 3]) +[({...left.0}).value.toUpperCase(), ({...right.0}).value.length] +//│ = ["LEFT", 3] +//│ left = [{value: "left"}] +//│ right = [{value: [1, 2, 3]}] + +:expect 12 +let overwritten = [a: {old: 0}, a: {answer: 12}] +overwritten.0.a.answer +//│ = 12 +//│ overwritten = [{a: {answer: 12}}] + +// Packing must not reorder or repeat evaluation of values or computed keys. +:expect ["abcde", 2, 4, 1, 3, 5] +let log = "" +fun tick(tag: Str, value) = { + set log += tag + value +} +let evaluated = [a: tick("a", 1), tick("b", 2), (tick("c", "c")): 3, + tick("d", 4), e: tick("e", 5)] +[log, evaluated.0, evaluated.1, evaluated.2.("a"), evaluated.2.("c"), evaluated.2.e] +//│ = ["abcde", 2, 4, 1, 3, 5] +//│ evaluated = [2, 4, {a: 1, c: 3, e: 5}] +//│ log = "abcde" + +:e +fun one(fields: {a: Int}) = fields.a +one(1, a: 2) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.61: one(1, a: 2) +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Function 'one' expected 1 argument, but got 2 +//│ ║ l.60: fun one(fields: {a: Int}) = fields.a +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:e +let key = "a" +[a: {answer: 1}, (key): {other: 2}].0.a.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.71: [a: {answer: 1}, (key): {other: 2}].0.a.answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. +//│ ╟── This computed key can overwrite the selected member. +//│ ║ l.71: [a: {answer: 1}, (key): {other: 2}].0.a.answer +//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target +//│ ║ l.71: [a: {answer: 1}, (key): {other: 2}].0.a.answer +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ key = "a" + +:expect 13 +[(key): 0, a: 13].0.a +//│ = 13 + +:expect [42, 43] +class Box(val fields) +[Box(answer: 42).fields.answer, (new Box(answer: 43)).fields.answer] +//│ = [42, 43] diff --git a/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls new file mode 100644 index 0000000000..0dfc659b4a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls @@ -0,0 +1,65 @@ +:. + +:expect ["HELLO", 3] +fun describe(fields: {name: Str, count: Int}) = [fields.name.toUpperCase(), fields.count] +describe(name: "hello", count: 3) +//│ = ["HELLO", 3] + +:expect 5 +fun apply(fields: {operation: Int -> Int, value: Int}) = fields.operation(fields.value) +apply(operation: x => x + 1, value: 4) +//│ = 5 + +:e +fun hidden(fields: {name: Str}) = fields.extra +hidden(name: "visible", extra: 1) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.14: fun hidden(fields: {name: Str}) = fields.extra +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Record type does not contain member 'extra' +//│ ║ l.14: fun hidden(fields: {name: Str}) = fields.extra +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +private fun opaque[A](fields: {value: A}) = fields.value.answer +opaque(value: {answer: 42}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.25: private fun opaque[A](fields: {value: A}) = fields.value.answer +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.25: private fun opaque[A](fields: {value: A}) = fields.value.answer +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:expect ["ABC", 3] +fun get[A](fields: {value: A}): A = fields.value +[get(value: "abc").toUpperCase(), get[Array[Int]](value: [1, 2, 3]).length] +//│ = ["ABC", 3] + +:expect "quoted" +fun quoted(fields: {"name": Str}) = fields.name +quoted(name: "quoted") +//│ = "quoted" + +:expect "OK" +fun callback(fields: {f: Str -> Str}): Str = fields.f("ok") +callback(f: x => x.toUpperCase()) +//│ = "OK" + +// A typed return must also check callbacks stored in its record implementation. +:e +fun expose(): {f: Object -> Int} = {f: x => x.answer} +expose().f({answer: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.53: fun expose(): {f: Object -> Int} = {f: x => x.answer} +//│ ║ ^^^^^^^^ +//│ ╙── Value of type 'Object' does not contain member 'answer' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:expect "ALIAS" +type Wrapped[A] = {value: A} +fun unwrap[A](fields: Wrapped[A]): A = fields.value +unwrap(value: "alias").toUpperCase() +//│ = "ALIAS" From 17aea8cf4fa5aa6930929d981c31f586bc4bae67 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 17:40:36 +0800 Subject: [PATCH 080/228] Locate unknown receivers and simplify annotation diagnostics --- .../src/main/scala/hkmc2/semantics/NewResolver.scala | 2 +- .../src/test/mlscript/newres/DeclaredTypes.mls | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index dd6cb8be60..4a3514af3a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -321,7 +321,7 @@ class NewResolver: case S(param: Param) => UnknownValueShape(source)(ShapeProvenance( (msg"Constructor parameter '${param.sym.nme}' has no type annotation." -> param.toLoc) :: annotation.toList.map(sign => - msg"This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape." -> sign.toLoc))) + msg"This type annotation does not provide a type for field '${member.nme}'." -> sign.toLoc))) case _ => UnknownValueShape.at(source) publish(unknown) case _ => diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index fc18ac51bf..44e8e9985f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -80,7 +80,7 @@ read(Box(new Item)) //│ ╟── Constructor parameter 'item' has no type annotation. //│ ║ l.73: class Box(val item) //│ ║ ^^^^ -//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ╟── This type annotation does not provide a type for field 'item'. //│ ║ l.74: fun read(box: Box) = box.item.extra //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -250,7 +250,7 @@ read(Box(new Item)) //│ ╟── Constructor parameter 'item' has no type annotation. //│ ║ l.243: class Box(val item) //│ ║ ^^^^ -//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ╟── This type annotation does not provide a type for field 'item'. //│ ║ l.244: fun read(box: Box) = box.Box#item.extra //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -513,7 +513,7 @@ read(Child(new Item)) //│ ╟── Constructor parameter 'item' has no type annotation. //│ ║ l.503: class Parent(val item) //│ ║ ^^^^ -//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ╟── This type annotation does not provide a type for field 'item'. //│ ║ l.507: fun read(box: Annotated) = identity(box.item).extra //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -532,7 +532,7 @@ invoke(Box(() => 42)) //│ ╟── Constructor parameter 'callback' has no type annotation. //│ ║ l.523: class Box(val callback) //│ ║ ^^^^^^^^ -//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ╟── This type annotation does not provide a type for field 'callback'. //│ ║ l.524: fun invoke(box: Box) = box.callback() //│ ╙── ^^^ //│ = 42 @@ -613,7 +613,7 @@ fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╟── Constructor parameter 'value' has no type annotation. //│ ║ l.606: class Box(val value) //│ ║ ^^^^^ -//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ╟── This type annotation does not provide a type for field 'value'. //│ ║ l.608: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╙── ^^^^^^^ @@ -630,7 +630,7 @@ class Box(val value) //│ ╟── Constructor parameter 'value' has no type annotation. //│ ║ l.624: class Box(val value) //│ ║ ^^^^^ -//│ ╟── This type annotation restricts access to the field's declared type; constructor arguments do not determine its shape. +//│ ╟── This type annotation does not provide a type for field 'value'. //│ ║ l.625: (Box(new Item) as Box).value.oops //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 5bfe047f717ebcb4311355021fdfa771381e3954 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 18:04:27 +0800 Subject: [PATCH 081/228] Migrate parser worksheets and inherit Object interfaces --- doc/new-resolution-suite-migration.md | 103 ++++++++++------- .../hkmc2/semantics/InterfaceExposure.scala | 2 +- .../scala/hkmc2/semantics/NewResolver.scala | 109 ++++++++++++++++-- .../hkmc2/semantics/NewResolverState.scala | 2 + .../src/main/scala/hkmc2/syntax/Tree.scala | 5 + .../test/mlscript-compile/LegacyOption.mls | 4 +- .../apps/parsing/BasicExpr.mls | 2 + .../mlscript-compile/apps/parsing/Expr.mls | 2 + .../apps/parsing/Keywords.mls | 23 ++-- .../mlscript-compile/apps/parsing/Lexer.mls | 2 +- .../apps/parsing/ParseRule.mls | 2 +- .../mlscript-compile/apps/parsing/Rules.mls | 12 +- .../mlscript-compile/apps/parsing/Token.mls | 30 ++--- .../mlscript-compile/apps/parsing/Tree.mls | 2 +- .../apps/parsing/TreeHelpers.mls | 4 +- .../mlscript/apps/parsing/DirectiveTest.mls | 1 + .../mlscript/apps/parsing/LeftRecursion.mls | 1 + .../test/mlscript/apps/parsing/LexerTest.mls | 14 +-- .../apps/parsing/ParseRuleVisualizerTest.mls | 1 + .../apps/parsing/PrattParsingTest.mls | 3 +- .../apps/parsing/RecursiveDescentTest.mls | 3 +- .../test/mlscript/apps/parsing/RulesTest.mls | 4 +- .../src/test/mlscript/decls/Prelude.mls | 8 +- .../src/test/mlscript/newres/Arrays.mls | 2 +- .../test/mlscript/newres/ObjectMethods.mls | 87 ++++++++++++++ .../test/mlscript/newres/RecordInterfaces.mls | 5 +- .../mlscript/newres/TuplePatternBindings.mls | 38 ++++++ .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 28 files changed, 365 insertions(+), 110 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 3cd7cb78cb..75f014aa24 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -8,7 +8,8 @@ After dynamic shapes and non-strict suite defaults became available, all 187 deferred worksheets were retried, bringing the count to 239. After declared interfaces, generic flow, tuple projections, and Array inheritance were implemented, all 133 remaining worksheets (plus the two deferred WASM worksheets) were retried. -This pass migrates another 13, bringing the count to 252 of 372 (68%). The 51 files under +That pass migrated another 13; the parser follow-up migrates seven more worksheets, +bringing the count to 259 of 372 (70%). The 51 files under `ucs/staging` are excluded by the test runner and remain unchanged. Counts below exclude shared `.mls` configuration files and new-resolution regression tests. @@ -18,8 +19,8 @@ configuration files and new-resolution regression tests. | codegen | 86 | 39 | 125 | | ucs | 52 | 18 | 70 | | ups | 49 | 22 | 71 | -| apps | 5 | 11 | 16 | -| Total | 252 | 120 | 372 | +| apps | 12 | 4 | 16 | +| Total | 259 | 113 | 372 | Migrated files start with `:.`. Each suite's `.mls` loads the common language configuration and uses its non-strict default; nested directories inherit through @@ -90,12 +91,12 @@ interfaces even in non-strict mode. | Compilation suite | Newly migrated | Total migrated | Deferred | Total | | --- | ---: | ---: | ---: | ---: | | Main (including quotes, UPS, and regression/compatibility fixtures) | 21 | 26 | 23 | 49 | -| Applications | 4 | 4 | 16 | 20 | +| Applications | 6 | 6 | 14 | 20 | | Nofib | 12 | 12 | 27 | 39 | | WASM | 0 | 0 | 1 | 1 | -| Total | 37 | 42 | 67 | 109 | +| Total | 39 | 44 | 65 | 109 | -The added `LegacyOption.mls` is counted among the deferred fixtures: 66 original +The added `LegacyOption.mls` is counted among the deferred fixtures: 64 original fixtures remain unported, plus this temporary compatibility copy. The additional `NamedFieldLibrary.mls` regression fixture uses new resolution and is counted in the migrated total, but is not a port of one of the 107 original fixtures. @@ -109,6 +110,7 @@ The retained ports are: - UPS: `DnfCnf`, `EvenOddTree`, and `TruthyFalsy`. - Applications: `parsing/PrattParsing`, `parsing/RecursiveDescent`, and `parsing/TokenHelpers`, plus `parsing-web-demo/Examples` after the named-field fix. + `parsing/BasicExpr` and `parsing/Expr` are now migrated with their worksheets. - Nofib: `banner`, `boyer`, `boyer2`, `calendar`, `clausify`, `cryptarithm1`, `gcd`, `mandel`, `mandel2`, `puzzle`, `rsa`, and `sphere`. @@ -124,6 +126,14 @@ No visibility changes or failure suppressions were added to make fixtures pass. DOM and filesystem entry points are explicitly `dyn` in the prelude for now; fixture parameters were not made dynamic to bypass missing interfaces. +All twelve `apps/parsing` worksheets now use new resolution, including the seven +previously deferred lexer, parser, rule, and visualizer tests. Their successful +runtime output is unchanged. Lexer callers now pass the actual `noWhitespace` +options record. The heterogeneous rule-table rendering callback declares the +structural `display` interface it needs. Implementation modules that still use +legacy resolution are explicitly listed below; migrating their callers does not +claim that these dependencies have been migrated. + ### Fixes found while porting - Ordinary values select function/constructor overloads; selection receivers @@ -154,6 +164,14 @@ fixture parameters were not made dynamic to bypass missing interfaces. symbol assignment checks. `newres/MemberAssignments.mls` covers a mutable field accessed by name inside a method. +- Root classes, objects, and modules inherit `Object`'s member interface. Explicit + parents and own-member overrides take precedence. `newres/ObjectMethods.mls` + covers inferred/annotated receivers, overrides, and subclass-pattern narrowing. +- Tuple patterns propagate leading, trailing, and rest shapes, preserving unknown + alternatives and generic opacity. Rest parameter annotations now describe the + whole rest array. `newres/TuplePatternBindings.mls` covers these paths. +- `Str.at` and `process.cwd` now have declared interfaces. + ### Remaining work and design boundaries Some deferred fixtures need source interfaces rather than compiler changes: @@ -178,13 +196,14 @@ Other failures require compiler or prelude work: `Option.mls` is now ported; the affected dependency graph imports the temporary `LegacyOption.mls`, preserving shared constructor identities. Its comment requires deletion when those consumers are ported. - `apps/parsing/BasicExpr` and `Expr` compile with just the header but their - legacy worksheet consumers encounter the same boundary, so these two remain - in their original mode. A general interoperability bridge would need to consume - completed type information without re-resolving imported nodes or querying - erased types before erasure. -2. **Flow through patterns and mutation.** Tuple bindings, guarded/transforming - patterns, assignment results, and mutable container element types still leave + `BasicExpr` and `Expr` are now ported together with their worksheet consumers. + The same boundary still prevents independently retaining the `Token` and + `Keywords` ports: legacy `Lexer`/`Parser` consumers inspect their new signatures. + A general interoperability bridge would need to consume completed type information + without re-resolving imported nodes or querying erased types before erasure. +2. **Flow through patterns and mutation.** Tuple bindings now transport field shapes, + including typed arrays and middle spreads. Guarded/transforming patterns, assignment + results, and mutable container element types still leave some selections without shapes. `Char.AnyChar` loses the string shape before its `length` guard; CSV's mutable nested array loses its element interface. Assuming initializer shapes survive mutation would be unsound. @@ -192,16 +211,21 @@ Other failures require compiler or prelude work: contracts; `Map.set` and `Reflect.set` need keyword-named declarations; WebAssembly exports still need interfaces. DOM and filesystem entry points now use `dyn`; `HTMLElement` retains its nominal superclass identity with - dynamic host members. Implicit generated - `toString` methods on user classes also need a declared resolution interface. + dynamic host members. `Object.toString(): Str` is now declared, and classes + without an explicit parent inherit the `Object` interface. 4. **Imported lowering forms.** `Parser` encounters a synthesized selection during new resolution, requiring import-boundary work. Named-field argument grouping is fixed: tuple shapes now match lowering's positional values followed by one record. `Extension`, `Rules`, `Token`, and web-demo `Examples` were retried; their named-field arity errors are gone, and `Examples` is now ported. `Rules.listLike` declares its structural field interface; `makeBracketRule` - still needs one, and some imported receivers still have no resolved target. -5. **Quotation coverage.** Quasiquote lowering still lacks some new type-selection + now has one too. Some imported receivers still have no resolved target. +5. **Contextual argument insertion.** `Lexer` calls `Token.integer`, `symbol`, etc. + with a trailing `(using llt: LineLookupTable)` list. New resolution leaves these + calls partially applied instead of inserting the available contextual value. + `Lexer` also hits the opened binary `~`/builtin-operator conflict. These are + compiler implementation gaps, not reasons to remove the contextual API. +6. **Quotation coverage.** Quasiquote lowering still lacks some new type-selection and wildcard-reference forms (`CSP`, `QuoteExample1`). ### Deferred compilation inventory @@ -215,7 +239,7 @@ legacy resolution; imports that exchange options use `LegacyOption` consistently | `Benchmark.mls` | Exposed `suite.run` and callback interfaces. Filesystem access is now dynamic. | | `Block.mls` | `Str.replaceAll`, `Any.toString`, and pattern-value interfaces. | | `CSP.mls` | Quasiquote type selections and wildcard opens. | -| `CachedHash.mls` | Interface for the generated `this.toString` method. | +| `CachedHash.mls` | Original `this.toString` blocker is fixed by implicit Object inheritance; the fixture has not yet been retried. | | `Char.mls` | `AnyChar` loses the string shape before its `length` guard. | | `FingerTreeList.mls` | `Array.reduce` and numeric projection on an array rather than a fixed tuple. | | `Iter.mls` | Exposed callbacks and iterator `next` interfaces. | @@ -238,20 +262,19 @@ legacy resolution; imports that exchange options use `LegacyOption` consistently | Application fixture | Observed obstruction | | --- | --- | -| `apps/parsing/BasicExpr.mls`, `Expr.mls` | Compile independently; legacy worksheet consumers inspect new-resolution signatures. | | `apps/parsing/Accounting.mls` | Array callback arity, `reduce`, and receiver interfaces. | | `apps/parsing/CSV.mls` | Mutable nested-array elements lack the shape required for `push`. | | `apps/parsing/Extension.mls` | Receivers for `display`, `add`, and `extendChoices` have no resolved target. | -| `apps/parsing/Keywords.mls` | Generic/abstract string interfaces and patterns. | -| `apps/parsing/Lexer.mls` | Exposed `options.noWhitespace` and pattern interpretations. | -| `apps/parsing/ParseRule.mls` | Exposed `keyword.name` and subsequent patterns. | +| `apps/parsing/Keywords.mls` | Compiles after interface additions; legacy `Parser` consumers inspect its new signatures. | +| `apps/parsing/Lexer.mls` | Missing automatic contextual argument insertion and opened binary `~` resolution. The options record is now annotated. | +| `apps/parsing/ParseRule.mls` | Flow through private module/class `let` bindings and imported mutable maps; `keyword` now has an interface. | | `apps/parsing/ParseRuleVisualizer.mls` | Imported JS railroad API lacks member interfaces. | | `apps/parsing/Parser.mls` | Synthetic selections from imported legacy forms reach new resolution. | -| `apps/parsing/Rules.mls` | `makeBracketRule` needs a structural parameter annotation; an `extendChoices` receiver has no resolved target. `listLike` and named-field grouping are fixed. | +| `apps/parsing/Rules.mls` | An `extendChoices` receiver has no resolved target through mutable map lookup. Both record-parameter helpers and exported rule results are annotated. | | `apps/parsing/Test.mls` | `flags.has` and `tracer.reset` lose shapes through tuple/import flow. | -| `apps/parsing/Token.mls` | Exposed string receivers need annotations; constructor-field shapes are missing in `display`. | -| `apps/parsing/Tree.mls` | Constructor-field shapes for `length` and `slice`. | -| `apps/parsing/TreeHelpers.mls` | Exposed `text.split` and generated `Tree.toString` interface. | +| `apps/parsing/Token.mls` | Compiles with its added interfaces and subclass-pattern narrowing; legacy contextual-argument consumers cannot read the new signatures. | +| `apps/parsing/Tree.mls` | Chained `JSON.stringify(...).slice(...)` lacks a result interface; also coupled to legacy consumers. | +| `apps/parsing/TreeHelpers.mls` | Coupled to `Tree` and its consumers; string input and `Object.toString` interfaces are now present. | | `apps/parsing-web-demo/main.mls` | String callback interfaces, dynamic receiver flow through aliases/mutation, and tuple-bound `example.name`. DOM declarations themselves are now dynamic. | | Nofib fixture (under `nofib/`) | Observed obstruction | @@ -260,8 +283,8 @@ legacy resolution; imports that exchange options use `LegacyOption` consistently | `eliza.mls`, `knights.mls`, `sorting.mls` | Unannotated string receivers and callbacks. | | `circsim.mls` | Exposed record receivers such as `p.pid` and `p.compType`. | | `cryptarithm2.mls` | Callable interface for an unannotated constructor field. | -| `cichelli.mls` | Generated `toString` interfaces and callbacks. | -| `treejoin.mls` | Generated `toString` interfaces and pattern flow. | +| `cichelli.mls` | Callback interfaces; its original `toString` blocker is fixed. | +| `treejoin.mls` | Pattern flow; its original `toString` blocker is fixed. | | `lcss.mls` | Function result has no interface for `toString`. | The remaining WASM fixture, `wasm/Wasm.mls`, needs interfaces for @@ -438,8 +461,8 @@ Regression coverage is in `newres/Dynamic.mls`, `newres/Records.mls`, Evidence: new shape propagation lacks record, negation, string concatenation, and transformation cases. Examples include `ucs/general/BooleanPatterns.mls`, `patterns/String.mls`, and -`normalization/RecordImpliedByClass.mls`. Tuple bindings currently publish no -shapes. `CompiledQualifiedConstructors.mls` and `CompiledClassPatterns.mls` now +`normalization/RecordImpliedByClass.mls`. Tuple bindings now propagate shapes, +including middle spreads and declared Array element types. `CompiledQualifiedConstructors.mls` and `CompiledClassPatterns.mls` now pass and are migrated, as are most UPS fixed-point, recursive, and parametric cases. Remaining failures include unsupported pattern forms, patterns used as terms (for example `.unapply`), and synthesized selections entering new resolution @@ -585,8 +608,8 @@ These changes also fix the sibling-subclass field-extraction regression in - `codegen/ObjectMethodDebinding`: `id(Test).foo` still has no resolved target. Adding an expected error would not establish the intended debinding diagnostic. - Imported application results still lack the required interfaces: CSV's nested - tuple selection, Iter's `reverse`, and parser collections' `sort`/`join` remain - unresolved. Adding Array members cannot manufacture missing receiver shapes. + tuple selection and Iter's `reverse` remain unresolved; the parser worksheets + now pass after adding their required interfaces. Adding Array members cannot manufacture missing receiver shapes. ### Other implementation work @@ -622,15 +645,15 @@ These changes also fix the sibling-subclass field-extraction regression in finish mutation flow, primitive members, and foreign declarations in small batches. Re-run affected negative tests as well as successful programs. 4. **Complete pattern transfers and reference preservation.** Start with UCS - conjunction/record/tuple cases, then the remaining recursive and transforming + conjunction/record cases, then the remaining recursive and transforming UPS cases. Keep fixed-point behavior tested independently from matcher code generation. Preserve the now-passing constructor-field context regressions. 5. **Complete type/interface and call validation.** Preserve opaque annotations - and marked generic flow; finish generic validation, rest-parameter signatures, + and marked generic flow; finish generic validation and contextual argument insertion, module checks, and the WASM migration. Keep cross-block method work coordinated externally. 6. **Continue the audited compilation-fixture migration.** Port `LegacyOption` consumers and remove the temporary copy. Resolve the legacy-consumer signature - boundary for `BasicExpr` and `Expr`; then add + boundary for `Token` and `Keywords`; `BasicExpr` and `Expr` are ported. Add exposed interfaces and complete the pattern/host-interface gaps listed above. Rebuild `.mjs` dependencies and run existing consumers for each batch. 7. **Remove the remaining legacy suite configurations.** Require no new failure @@ -642,9 +665,10 @@ These changes also fix the sibling-subclass field-extraction regression in - `ctest`: all 49 selected compilation tests pass. - `catest`: all 20 application compilation fixtures pass with the retained mixed-mode ports. +- `adtest parsing`: all 14 selected tests pass, including all twelve parser worksheets. - `cntest`: all 39 Nofib compilation fixtures pass. - `cwtest`: the WASM compilation fixture passes. -- `hkmc2AllTests/test`: all 967 tests pass, including 718 main tests, 18 application +- `hkmc2AllTests/test`: all 969 tests pass, including 720 main tests, 18 application tests, and 22 WASM tests. The main count includes directory configurations and new-resolution regressions; it is not a count of migrated worksheets. - All retained ports have reviewed goldens. Legacy consumers that require the @@ -786,13 +810,6 @@ The files themselves retain the pre-trial configuration and output. - `apps/CSVTest.mls`: Unexpected compilation error; This selection of member '1' has no resolved target - `apps/IterTest.mls`: Unexpected compilation error; This selection of member 'reverse' has no resolved target - `apps/parsing-web-demo/ExamplesTest.mls`: Unexpected compilation error; This selection of member 'sort' has no resolved target -- `apps/parsing/DirectiveTest.mls`: Unexpected compilation error; This selection of member 'display' has no resolved target -- `apps/parsing/LeftRecursion.mls`: Unexpected compilation error; This selection of member 'display' has no resolved target -- `apps/parsing/LexerTest.mls`: Unexpected compilation error; This selection of member 'join' has no resolved target -- `apps/parsing/ParseRuleVisualizerTest.mls`: Unexpected compilation error; Resolution error in application; Class value cannot receive more argument lists. -- `apps/parsing/PrattParsingTest.mls`: Unexpected compilation error; This selection of member 'toString' has no resolved target -- `apps/parsing/RecursiveDescentTest.mls`: Unexpected compilation error; This selection of member 'toString' has no resolved target -- `apps/parsing/RulesTest.mls`: Unexpected exception; java.lang.Exception: Internal Error: Synthetic selections must not enter new resolution ### wasm diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index 3ffa5a1731..d4843df510 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -67,7 +67,7 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) if symbol.asModOrObj.isDefined || symbol.asTrm.exists(!_.isInstanceOf[ClassCtorSymbol]) then symbol.asCls.flatMap(_.defn).foreach: cls => val resolver = this.resolver - watch((cls.sym, marks))(listener => resolver.listenExt(cls.ext, ext => + watch((cls.sym, marks))(listener => resolver.listenExt(cls, ext => DefnShape(cls, ext).exit(ExitMark(ResolutionBoundary(cls.sym), S(ref.resSym), NoMarks)).exit(marks) match case shape: TermShape => listener(shape) case NoShape => ()))(emit(_, path)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index a66101cf21..e2f1ac5c04 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -197,7 +197,7 @@ class NewResolver: case TypeShape.Nominal(defn) => val bindings = bind(defn.tparams) defn.ext match - case N => publish(NominalTypeShape(defn, bindings, N)) + case N => publish(NominalTypeShape(defn, bindings, implicitParent(defn))) case S(parent) => // Only the parent's declared type is relevant here. Evaluating // its constructor arguments would reintroduce implementation flow. @@ -554,7 +554,7 @@ class NewResolver: val cls = prelude.builtins.Array.defn.get softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") val elements = new TypeResolution(tuple.source, messages => resolError(tuple.source, messages)) - val parent = NominalTypeShape(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), N) + val parent = NominalTypeShape(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), implicitParent(cls)) tupleArrayParents(new Identity(tuple)) = parent tuple.segments.foreach: case field: TupleShape.Fixed => listenTupleField(field): value => @@ -821,12 +821,92 @@ class NewResolver: UnknownValueShape.at(opaque.source).exit(marks) match case value: TermShape => narrow(value) case NoShape => () + case Marked(nominal: NominalTypeShape, _) if !nominal.isInstanceOfClass(cls) => + // A base-class annotation can contain a subclass instance. Its + // constructor test exposes that subclass's declarations; omitted + // subclass type arguments remain abstract, never inferred from + // unrelated constructor calls elsewhere in the unit. + // Reuse the abstract instantiation when recursive flow reaches + // this test again, so its omitted arguments have stable identities. + val tested = rstate.patternTypes.getOrElseUpdate((new Identity(ctor), cls.sym), { + val resolution = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) + resolution.publish(TypeShape.Nominal(cls)) + declaredType(resolution, Map.empty) + }) + listenTypeValues(tested): candidate => + if candidate.isInstanceOfClass(nominal.defn) then check(candidate, N) case _ => check(sh, N) shape match case sh: TermShape => narrow(sh) case sh: SymShape => fromSymbol(sh, narrow, ctor.target, _ => (), false) ctor.subscribeToShapes(listenConstructor) - case Pattern.Tuple(_, _) => () // Tuple binding shapes are not inferred yet. + case Pattern.Tuple(leading, spread) => + val trailing = spread.fold(Nil)(_._3) + val required = leading.length + trailing.length + def tupleBindings(tuple: TupleShape, marks: Marks)(using NewResolverState): Unit = + val segments = tuple.segments + val known = segments.count(_.isInstanceOf[TupleShape.Fixed]) + val uncertain = known != segments.length + if (uncertain || known >= required) && (spread.nonEmpty || known <= required) then + def bind(segment: TupleShape.Segment, p: Pattern)(using NewResolverState): Unit = + def receive(value: TermShape)(using NewResolverState): Unit = value.exit(marks) match + case value: TermShape => matchShapePat(value, p)(_ => ()) + case NoShape => () + segment match + case field: TupleShape.Fixed => listenTupleField(field)(receive) + case TupleShape.Unknown(_, inner, value) => value.exit(inner) match + case value: TermShape => receive(value) + case NoShape => () + // A spread can move subsequent fields. Retain every possible position + // rather than treating the first unknown segment as the only candidate. + def positions(xs: Ls[TupleShape.Segment], ps: Ls[Pattern])(using NewResolverState): Unit = + var before = 0 + var unknownBefore = false + xs.foreach: + case field: TupleShape.Fixed => + ps.iterator.zipWithIndex.foreach: (p, index) => + if index >= before && (if !unknownBefore then index == before + else spread.nonEmpty || index <= before + required - known) then bind(field, p) + before += 1 + case unknown: TupleShape.Unknown => + ps.drop(before).foreach(bind(unknown, _)) + unknownBefore = true + positions(segments, leading) + positions(segments.reverse, trailing.reverse) + spread.foreach: (_, p, _) => + // Stop trimming at an unknown segment: its possible lengths include + // consuming the remaining prefix/suffix, so all later values may remain. + def trim(xs: Ls[TupleShape.Segment], count: Int): Ls[TupleShape.Segment] = + if count == 0 then xs else xs match + case (_: TupleShape.Fixed) :: tail => trim(tail, count - 1) + case _ => xs + val middle = trim(trim(segments, leading.length).reverse, trailing.length).reverse + // Reusing an unchanged view also bounds recursive tail matching on + // arrays with unknown length; wrapping it again would grow forever. + val rest = if middle == segments then tuple + else TupleShape(tuple.source, TupleShape.Rest(tuple, middle) :: Nil)(this) + rest.exit(marks) match + case value: TermShape => matchShapePat(value, p)(_ => ()) + case NoShape => () + tuple.exit(marks) match + case value: TermShape => matched(value) + case NoShape => () + def narrow(value: TermShape)(using NewResolverState): Unit = value match + case Marked(tuple: TupleShape, marks) => tupleBindings(tuple, marks) + case Marked(unknown: UnknownValueShape, marks) => + tupleBindings(TupleShape(unknown.source, + TupleShape.Unknown(unknown.source, Nil, unknown) :: Nil)(this), marks) + case Marked(nominal: NominalTypeShape, marks) => + nominal.ancestor(prelude.builtins.Array.defn.get).foreach: array => + array.bindings.get(prelude.builtins.Array.defn.get.tparams.head.sym).foreach: element => + listenTypeValues(element): + case Marked(shape, inner) => + tupleBindings(TupleShape(element.resolution.source, + TupleShape.Unknown(element.resolution.source, inner :: Nil, shape) :: Nil)(this), marks) + case _ => () + shape match + case value: TermShape => narrow(value) + case sym: SymShape => fromSymbol(sym, narrow, Term.Missing, _ => (), false) case _ => raise(ErrorReport(msg"This pattern is not supported during shape resolution." -> pattern.toLoc :: Nil)) @@ -1072,7 +1152,7 @@ class NewResolver: listenClass(cls)((cd, marks) => val candidate = cd.sym -> marks rstate.recordResolution(sel, sel.resolvedClasses.contains(candidate))(sel.resolvedClasses ::= candidate) - listenExt(cd.ext, ext => + listenExt(cd, ext => val info = DefnShape(cd, ext).getInstanceMember(sel.id.name).withMarks(marks) info match case MemberLookup.Found(bms: BlockMemberSymbol, _) => @@ -1136,7 +1216,7 @@ class NewResolver: // constructor value. In particular, a captured constructor already carries // that exit; adding a second one here would duplicate its instance boundary. listenClass(nw.cls)((cd, marks) => - listenExt(cd.ext, extsh => + listenExt(cd, extsh => val dsh = constructorShape(cd, extsh) val sh = newShapes.getOrElseUpdate((cd.sym, marks, nw.resSym), { def typeArgs(term: Term)(using NewResolverState): Opt[Ls[Term]] = term match @@ -1188,13 +1268,20 @@ class NewResolver: if to.currentShapes.add(sh) then to.notifyShapeListeners(sh) - def listenExt(ext: Opt[Term], listener: ShapeListener[Opt[TermShape]])(using NewResolverState): Unit = - ext match + // Classes without an explicit parent expose Object's declarations. Share this + // root interface between inferred instances, nominal annotations, and this-values; + // Object itself must terminate the chain. This does not add a runtime constructor call. + private lazy val objectShape = NominalTypeShape(prelude.builtins.Object.defn.get, Map.empty, N) + private def implicitParent(defn: ClassLikeDef): Opt[TermShape] = + if defn.sym is prelude.builtins.Object then N else S(objectShape) + + def listenExt(defn: ClassLikeDef, listener: ShapeListener[Opt[TermShape]])(using NewResolverState): Unit = + defn.ext match case S(trm) => listenTerm(trm): sh => listener(S(sh)) case N => - listener(N) + listener(implicitParent(defn)) private def fromSymbol(shape: SymShape, listener: Listener, source: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(using NewResolverState): Unit = shape match @@ -1255,11 +1342,11 @@ class NewResolver: d.tsym match case ccs: ClassCtorSymbol => val cls = ccs.associatedCls.defn.get - listenExt(cls.ext, extsh => wrappedListener(constructorShape(cls, extsh))) + listenExt(cls, extsh => wrappedListener(constructorShape(cls, extsh))) case _ => wrappedListener(defnShapes.getOrElseUpdate(sym, DefnShape(d, N))) case S(d: ClassLikeDef) => - listenExt(d.ext, extsh => + listenExt(d, extsh => // defnShapes.get(sym).foreach: existing => // ??? // TODO error? wrappedListener(defnShapes.getOrElseUpdate(sym, DefnShape(d, extsh))) @@ -1513,7 +1600,7 @@ class NewResolver: // A receiver can be referenced before its body is complete or after its // definition has already been published. The definition listener handles both. def completed(defn: ClassLikeDef)(using NewResolverState): Unit = - listenExt(defn.ext, ext => + listenExt(defn, ext => // Each inner symbol has one self shape; repeated notifications must agree. val shape = selfShapes.getOrElseUpdate(sym, BaseShape(defn, ext)) softAssert(shape.defn is defn) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 7a8579967a..719fd09a39 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -187,6 +187,8 @@ final class NewResolverState private ( new Cache(source.map(_.typeInterpretations), identity) val typeValues: Cache[DeclaredType, TypeValues] = new Cache(source.map(_.typeValues), identity) + val patternTypes: Cache[(Identity[Pattern.Constructor], InnerSymbol), DeclaredType] = + new Cache(source.map(_.patternTypes), identity) val primitiveTypes: Cache[ClassSymbol, DeclaredType] = new Cache(source.map(_.primitiveTypes), identity) val abstractTypes: Cache[TypeResolution, DeclaredType] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index db23bac6d0..65de846488 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -368,6 +368,11 @@ enum Tree extends AutoLocated: // fun f(a: A) case InfixApp(id: Ident, Keywrd(Keyword.`:`), sign) => R(ParamTree(flags, id, S(sign), N, modifiers)) + // The annotation describes the rest tuple, just as for an ordinary parameter. + case InfixApp(SpreadParam(id, spd), Keywrd(Keyword.`:`), sign) => + R(ParamTree(flags, id, S(sign), S(spd), modifiers)) + case Spread(kw, S(InfixApp(id: Ident, Keywrd(Keyword.`:`), sign))) => + R(ParamTree(flags, id, S(sign), S(SpreadKind.fromKw(kw)), modifiers)) // fun f(..a) | fun f(...a) case SpreadParam(id, spd) => R(ParamTree(flags, id, N, S(spd), modifiers)) diff --git a/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls b/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls index a37ec27a66..fb7648acdc 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls @@ -4,12 +4,12 @@ // must use the same copy so constructor identities agree. -type LegacyOption[A] = LegacyOption.{ Some(A) | None } +type LegacyOption[A] = LegacyOption.{ Some[A] | None } module LegacyOption with ... -data class Some[T](value) +data class Some[T](value: T) object None fun isDefined(x) = if x is diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls index a8eb7e9b24..5399e3f142 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "../../StrOps.mls" import "../../LegacyOption.mls" import "../../Predef.mls" diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls index 711fbda317..6d58caa0b9 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "../../StrOps.mls" import "../../LegacyOption.mls" import "../../Predef.mls" diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls index c6d92e7fdd..f232afea39 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls @@ -13,7 +13,7 @@ module Keywords with ... val INT_MIN = -2147483648 val INT_MAX = 2147483647 -class Keyword(val name: Str, val leftPrec, val rightPrec) with +class Keyword(val name: Str, val leftPrec: Some[Int] | None, val rightPrec: Some[Int] | None) with fun leftPrecOrMin = if leftPrec is Some(prec) then prec else INT_MIN fun rightPrecOrMin = if rightPrec is Some(prec) then prec else INT_MIN fun leftPrecOrMax = if leftPrec is Some(prec) then prec else INT_MAX @@ -24,19 +24,16 @@ class Keyword(val name: Str, val leftPrec, val rightPrec) with (if leftPrec is Some(prec) then prec.toString() else "N/A"), ", ", (if rightPrec is Some(prec) then prec.toString() else "N/A"), ")" -fun makePrecMap(startPrec: Int, ...ops) = - let - m = MutMap.empty - i = 0 - while i < ops.length do - ops.at(i).split(" ").forEach of (op, _, _) => +fun makePrecMap(startPrec: Int, ...ops: Array[Str]) = + let m = MutMap.empty + ops.forEach of (names, i, _) => + names.split(" ").forEach of (op, _, _) => if op.length > 0 do m |> MutMap.insert of op, i + startPrec - set i += 1 m val all = MutMap.empty -fun keyword(name, leftPrec, rightPrec) = +fun keyword(name: Str, leftPrec: Some[Int] | None, rightPrec: Some[Int] | None) = let result = Keyword(name, leftPrec, rightPrec) all |> MutMap.insert(name, result) result @@ -112,7 +109,7 @@ val periodPrec = precMap |> MutMap.get(".") |> LegacyOption.unsafe.get val _period = keyword of ".", Some(periodPrec), Some(periodPrec) -val maxOperatorPrec = periodPrec +val maxOperatorPrec: Int = periodPrec val appPrec = maxOperatorPrec - 1 val prefixPrec = appPrec - 1 @@ -135,7 +132,7 @@ val _rightCurly = keyword of "}", basePrec, None val _begin = keyword of "begin", bracketPrec, basePrec val _end = keyword of "end", basePrec, None -let builtinKeywords = new Set(all |> MutMap.keysIterator) +private val builtinKeywords = new Set(all |> MutMap.keysIterator) fun extended = all Iter.filtering of case [k, _] then builtinKeywords.has(k) is false Iter.toArray() @@ -149,7 +146,7 @@ pattern FloatOperator = "+." | "-." | "*." | "/." pattern RightAssociative = "@" | "/" | "," | ":" -fun opPrec(opStr) = if +fun opPrec(opStr: Str) = if opStr is FloatOperator then tuple of Keywords.charPrec of opStr.at(0) Keywords.charPrec of opStr.at(0) @@ -163,7 +160,7 @@ fun opPrec(opStr) = if (Keywords.charPrec of lastChar) + if lastChar is RightAssociative then -1 else 0 -fun opPrecOpt(opStr) = if +fun opPrecOpt(opStr: Str) = if opStr is "" then None opStr is FloatOperator then Some of tuple of Keywords.charPrec of opStr.at(0) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls index b9184181c4..e34e2bae64 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls @@ -56,7 +56,7 @@ fun makeLineLookupTable(text: Str): LineLookupTable = ns.push(i') LineLookupTable(ns) -fun lex(str: Str, options) = +fun lex(str: Str, options: {noWhitespace: Bool}) = using LineLookupTable = makeLineLookupTable(str) fun char(idx: Int) = if idx < str.length diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls index 28f575a329..e10817809b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls @@ -51,7 +51,7 @@ module Choice with check(item is Choice, name + ": element [" + index + "] is not Choice") // Shorthands for constructing rule choices. - fun keyword(keyword)(...choices) = + fun keyword(keyword: Keywords.Keyword)(...choices) = ensureChoices(choices, "Choice.keyword") Keyword(keyword, rule("`" + keyword.name + "` keyword", ...choices)) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls index bb60fb51de..49297d108a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls @@ -24,7 +24,7 @@ val syntaxKinds = MutMap.empty val extendedKinds = new Set() -fun getRuleByKind(kind: Str) = syntaxKinds |> MutMap.get(kind) |> LegacyOption.unsafe.get +fun getRuleByKind(kind: Str): ParseRule.ParseRule[out Anything] = syntaxKinds |> MutMap.get(kind) |> LegacyOption.unsafe.get fun define(name: Str)(...choices) = syntaxKinds |> MutMap.updateWith(name) of case None then Some(rule(name, ...choices)) @@ -103,7 +103,9 @@ fun makeInfixChoice(kw: Keywords.Keyword, rhsKind: Str, compose: (Tree, Tree) -> process: (rhs, _) => lhs => compose(lhs, rhs) name: "operator `" + kw.name + "` right-hand side" -fun makeBracketRule(fields) = // opening, closing, kind, wrapContent +fun makeBracketRule(fields: { + opening: Keywords.Keyword, closing: Keywords.Keyword, kind: Str, wrapContent: Tree -> Tree +}) = // Pass the error message of closing bracket to the content. keyword(fields.opening) of reference(fields.kind) of process: (tree: Tree, next: Tree) => if next is @@ -113,7 +115,7 @@ fun makeBracketRule(fields) = // opening, closing, kind, wrapContent choices: tuple of keyword(fields.closing) of end of Tree.empty // Prefix rules and infix rules for expressions. -val termRule = rule of +val termRule: ParseRule.ParseRule[Tree] = rule of "prefix rules for expressions" letExpression // `fun` term @@ -228,7 +230,7 @@ val termRule = rule of outerPrec: Keywords.appPrec // Prefix rules and infix rules for types. -val typeRule = rule of +val typeRule: ParseRule.ParseRule[Tree] = rule of "rules for types" // "(" { "," } ")" [ ] keyword(Keywords._leftRound) of typeExpr of @@ -347,7 +349,7 @@ define("type-params-tail") of keyword(Keywords._comma) of typeVar of choices: tuple of reference("type-params-tail") of process: idFirst, name: "more type parameters" -val declRule = rule of +val declRule: ParseRule.ParseRule[Tree] = rule of "prefix rules for module items" makeLetBindings(false) // let definition keyword(Keywords._type) of diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls index be19feac71..2dbf9dcaf4 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls @@ -22,9 +22,13 @@ module LiteralKind with String Boolean +type LiteralKind = LiteralKind.{Integer | Decimal | String | Boolean} + +type Location = {start: Int, end: Int, lookupTable: LineLookupTable} + abstract class Token with - let _location = None - fun withLocation(start, end, lookupTable) = + private mut val _location: Some[Location] | None = None + fun withLocation(start: Int, end: Int, lookupTable: LineLookupTable) = set _location = Some of start: start, end: end, lookupTable: lookupTable this fun location = _location @@ -63,7 +67,7 @@ data Error() extends Token Comment(content: Str) extends Token Identifier(name: Str, symbolic: Bool) extends Token - Literal(kind, literal: Str) extends Token + Literal(kind: LiteralKind, literal: Str) extends Token fun same(a, b) = if a is Space and b is Space then true @@ -72,28 +76,28 @@ fun same(a, b) = if a is Literal(k, l) and b is Literal(k', l') then k == k' and l == l' else false -fun integer(literal, endIndex)(using llt: LineLookupTable) = +fun integer(literal: Str, endIndex: Int)(using llt: LineLookupTable) = Literal(LiteralKind.Integer, literal).withLocation of endIndex - literal.length, endIndex, llt -fun decimal(literal, endIndex)(using llt: LineLookupTable) = +fun decimal(literal: Str, endIndex: Int)(using llt: LineLookupTable) = Literal(LiteralKind.Decimal, literal).withLocation of endIndex - literal.length, endIndex, llt -fun string(literal, startIndex, endIndex)(using llt: LineLookupTable) = +fun string(literal: Str, startIndex: Int, endIndex: Int)(using llt: LineLookupTable) = Literal(LiteralKind.String, literal).withLocation(startIndex, endIndex, llt) -fun boolean(literal, endIndex)(using llt: LineLookupTable) = +fun boolean(literal: Str, endIndex: Int)(using llt: LineLookupTable) = Literal(LiteralKind.Boolean, literal).withLocation of endIndex - literal.length, endIndex, llt -fun identifier(name, endIndex)(using llt: LineLookupTable) = +fun identifier(name: Str, endIndex: Int)(using llt: LineLookupTable) = Identifier(name, false).withLocation of endIndex - name.length, endIndex, llt -fun symbol(name, endIndex)(using llt: LineLookupTable) = +fun symbol(name: Str, endIndex: Int)(using llt: LineLookupTable) = Identifier(name, true).withLocation of endIndex - name.length, endIndex, llt -fun comment(content, startIndex, endIndex)(using llt: LineLookupTable) = +fun comment(content: Str, startIndex: Int, endIndex: Int)(using llt: LineLookupTable) = Comment(content).withLocation(startIndex, endIndex, llt) -fun error(startIndex, endIndex)(using llt: LineLookupTable) = +fun error(startIndex: Int, endIndex: Int)(using llt: LineLookupTable) = Error().withLocation(startIndex, endIndex, llt) -fun space(startIndex, endIndex)(using llt: LineLookupTable) = +fun space(startIndex: Int, endIndex: Int)(using llt: LineLookupTable) = Space().withLocation(startIndex, endIndex, llt) fun summary(token) = if token is @@ -103,7 +107,7 @@ fun summary(token) = if token is Identifier(name, _) then name Literal(_, literal) then literal -fun display(token) = (if token is +fun display(token: Token) = (if token is Space then "space" Error then "error" Comment then "comment" diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls index 7d0727e5ec..95fe5545b8 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls @@ -43,7 +43,7 @@ module Tree with Modified(modifier, subject) extends Tree Tuple(trees: Stack[Tree]) extends Tree Sequence(trees: Stack[Tree]) extends Tree - Literal(kind, value) extends Tree + Literal(kind: Token.LiteralKind, value: Str) extends Tree Match(scrutinee: Tree, branches: Stack[Tree]) extends Tree Lambda(params: Stack[Tree], body: Tree) extends Tree App(callee: Tree, argument: Tree) extends Tree diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls index 87a71f4f19..6f56def6c5 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls @@ -16,7 +16,7 @@ fun first(array) = if array is [first, ...] then first fun second(array) = if array is [_, second, ...] then second -fun indented(text) = text.split("\n").join("\n ") +fun indented(text: Str) = text.split("\n").join("\n ") fun showAsTree(thing) = fun itemize(something) = if something is @@ -33,7 +33,7 @@ fun showAsTree(thing) = tuple of ("Stack of \n" + " " + indented of items.join("\n")), [] Nil then ["Nil", []] Str then [JSON.stringify(something), []] // TODO: This doesn't work. - Int then [something.toString(), []] + Int as number then [number.toString(), []] Tree.Empty then ["Empty", []] Tree.Error(Tree.Empty, m) then tuple of "Error", [["message", go(m)]] Tree.Error(t, m) then tuple of "Error", [["tree", go(t)], ["message", go(m)]] diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/DirectiveTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/DirectiveTest.mls index 68f7c1c0f3..85ea1c7340 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/DirectiveTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/DirectiveTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/MutMap.mls" diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/LeftRecursion.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/LeftRecursion.mls index e349f897c5..dbd247f4f2 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/LeftRecursion.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/LeftRecursion.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/MutMap.mls" diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/LexerTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/LexerTest.mls index d5ba2e7694..64133aa892 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/LexerTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/LexerTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/apps/parsing/Lexer.mls" @@ -5,13 +6,13 @@ import "../../../mlscript-compile/apps/parsing/Token.mls" import "../../../mlscript-compile/Stack.mls" import "../../../mlscript-compile/Iter.mls" -fun lines(...strs) = strs.join("\n") +fun lines(...strs: Array[Str]) = strs.join("\n") open Lexer { lex } open Stack fun printLexed(text) = print of - lex(text, false) + lex(text, noWhitespace: false) Iter.fromStack() Iter.mapping of Token.display Iter.joined of "\n" @@ -240,10 +241,7 @@ fun take(pattern P, idx: Str, acc: Str) = set idx = idx + 1 set acc = acc + ch else [acc, idx] -//│ ╔══[COMPILATION ERROR] Name not found: str -//│ ║ l.238: while idx < str.length and str.charAt(idx) is ch and ch is P -//│ ╙── ^^^ -//│ ╔══[COMPILATION ERROR] Name not found: str -//│ ║ l.238: while idx < str.length and str.charAt(idx) is ch and ch is P -//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'length' has no resolved target +//│ ║ l.239: while idx < str.length and str.charAt(idx) is ch and ch is P +//│ ╙── ^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/ParseRuleVisualizerTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/ParseRuleVisualizerTest.mls index 09689789d6..400cdbe162 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/ParseRuleVisualizerTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/ParseRuleVisualizerTest.mls @@ -1,3 +1,4 @@ +:. :js :silent diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/PrattParsingTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/PrattParsingTest.mls index 4aca5615ea..d1a033afbb 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/PrattParsingTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/PrattParsingTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/apps/parsing/PrattParsing.mls" @@ -9,7 +10,7 @@ open Lexer { lex } open PrattParsing { parse } fun example(input) = - let tokens = lex of input, false + let tokens = lex of input, noWhitespace: false print of "Tokens: " + TokenHelpers.panorama(tokens) let expr = parse of tokens print of "Parsed: " + expr.toString() diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/RecursiveDescentTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/RecursiveDescentTest.mls index 90faadd9aa..dd92dba250 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/RecursiveDescentTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/RecursiveDescentTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/apps/parsing/RecursiveDescent.mls" @@ -10,7 +11,7 @@ open RecursiveDescent { parse } open BasicExpr { Expr } fun example(input) = - let tokens = lex of input, false + let tokens = lex of input, noWhitespace: false print of "Tokens: " + TokenHelpers.panorama(tokens) let expr = parse of tokens print of "Parsed: " + expr.toString() diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/RulesTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/RulesTest.mls index cbb1951d3f..b23ae381a2 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/RulesTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/RulesTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Iter.mls" @@ -105,7 +106,8 @@ print of Rules.declRule.display //│ > | "#" Rules.syntaxKinds - Iter.mapping of case [name, rule] then rule.display + Iter.mapping of (entry: [Str, {display: Str}]) => + if entry is [name, rule] then rule.display Iter.joined("\n") print() //│ > ::= "=" ["and" ] diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 5ce764cf88..d7ec4ac8ef 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -4,7 +4,8 @@ declare type Any declare type Anything declare type Nothing -declare class Object +declare class Object with + fun toString(): Str declare module Object with fun create @@ -117,6 +118,7 @@ declare class Int extends Num declare class Str with val length: Int fun toString(): Str + fun at(index: Int): Str | undefined fun charAt(index: Int): Str fun charCodeAt(index: Int): Num fun codePointAt(index: Int): Int | undefined @@ -263,7 +265,9 @@ declare module console with timeEnd trace -declare val process // TODO make `module` +declare module process with + fun cwd(): Str + val env // Host APIs remain explicitly dynamic until their interfaces are modeled. declare val fs: dyn diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index ed7a89ab21..f2355c5f53 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -84,7 +84,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.153: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.155: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:85 diff --git a/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls b/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls new file mode 100644 index 0000000000..732afbf3b7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls @@ -0,0 +1,87 @@ +:. + +:expect "Empty" +object Empty +Empty.toString() +//│ = "Empty" + +:expect "Box(1)" +data class Box(value: Int) +Box(1).toString() +//│ = "Box(1)" + +:expect "BOX(2)" +fun show(box: Box): Str = box.toString().toUpperCase() +show(Box(2)) +//│ = "BOX(2)" + +:expect "custom" +class Parent with + fun toString(): Str = "custom" +new Parent().toString() +//│ = "custom" + +:e +Empty.toString(1) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.25: Empty.toString(1) +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Function with a declared signature expected 0 arguments, but got 1 +//│ ║ l.25: Empty.toString(1) +//│ ╙── ^^^^^^^^^^^^^^ +//│ = "Empty" + +:expect "empty" +fun lower(value: Empty): Str = value.toString().toLowerCase() +lower(Empty) +//│ = "empty" + +:expect "empty" +data class Wrapped(kind: Empty) +fun lowerWrapped(value: Wrapped): Str = if value is Wrapped(kind) then kind.toString().toLowerCase() +lowerWrapped(Wrapped(Empty)) +//│ = "empty" + +module Kind with + object Red + object Blue +type Kind = Kind.{Red | Blue} +data class Colored(kind: Kind) + +:expect "red" +fun lowerColored(value: Colored): Str = if value is Colored(kind) then kind.toString().toLowerCase() +lowerColored(Colored(Kind.Red)) +//│ = "red" + +abstract class Base +data class Derived(value: Str) extends Base + +:expect "VALUE" +fun derivedValue(value: Base): Str = if value is Derived(text) then text.toUpperCase() +derivedValue(new Derived("value")) +//│ = "VALUE" + +:expect "Box(3)" +fun renderObject(value: Object): Str = value.toString() +renderObject(Box(3)) +//│ = "Box(3)" + +:expect "child" +class HasMethod with + fun label(): Str = "parent" +class Child extends HasMethod with + fun label(): Str = "child" +new Child().label() +//│ = "child" + +// Recursive narrowing must reuse its abstract subclass instantiation. +abstract class Link[A] +data class Cell[A](value: A, next: Link[A]) extends Link[A] +object End extends Link[Int] +fun depth(xs: Link[Int]): Int = if xs is + Cell(_, next) then 1 + depth(next) + else 0 + +:expect 2 +depth(Cell(1, Cell(2, End))) +//│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls index 0dfc659b4a..c131adc6ae 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls @@ -55,7 +55,10 @@ expose().f({answer: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.53: fun expose(): {f: Object -> Int} = {f: x => x.answer} //│ ║ ^^^^^^^^ -//│ ╙── Value of type 'Object' does not contain member 'answer' +//│ ╟── Value of type 'Object' does not contain member 'answer' +//│ ║ in ../decls/Prelude.mls +//│ ║ l.7: fun toString(): Str +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :expect "ALIAS" diff --git a/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls b/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls new file mode 100644 index 0000000000..d289cad66e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls @@ -0,0 +1,38 @@ +:. + +:expect "ABC" +fun second(pair: [Int, Str]) = if pair is [_, text] then text.toUpperCase() +second([1, "abc"]) +//│ = "ABC" + +:expect "a-b" +fun joined(...parts: Array[Str]): Str = parts.join("-") +joined("a", "b") +//│ = "a-b" + +:expect ["a", "b", "c"] +private fun split(values) = if values is [first, ...middle, last] then [first.name, middle.0.name, last.name] +split([{name: "a"}, {name: "b"}, {name: "c"}]) +//│ = ["a", "b", "c"] + +:expect "ABC" +fun head(values: Array[Str]) = if values is [first, ...] then first.toUpperCase() +head(["abc", "def"]) +//│ = "ABC" + +:expect "FIRST" +private fun select(value) = if value is [[first, _], _] then first.toUpperCase() +select([["first", 2], 3]) +//│ = "FIRST" + +:e +fun opaque[A](pair: [A, Int]) = if pair is [item, _] then item.name +opaque([{name: "hidden"}, 1]) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.29: fun opaque[A](pair: [A, Int]) = if pair is [item, _] then item.name +//│ ║ ^^^^^^^^^ +//│ ╟── Cannot resolve member 'name' of a value with unknown shape. +//│ ╟── Type parameter 'A' does not specify a member interface. +//│ ║ l.29: fun opaque[A](pair: [A, Int]) = if pair is [item, _] then item.name +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index f56afc5715..9990c9296b 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.307: fun +//│ ║ l.311: fun //│ ║ ^^^ -//│ ║ l.308: const(c: Int): Int32 +//│ ║ l.312: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 9384df16bf7b2d03334741f00fde8a322156acf9 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 18:08:12 +0800 Subject: [PATCH 082/228] Restore Array constructor interface and preserve typed spreads --- .../scala/hkmc2/codegen/js/JSBuilder.scala | 6 +- .../scala/hkmc2/semantics/NewResolver.scala | 62 +++++- .../hkmc2/semantics/NewResolverState.scala | 2 +- .../src/test/mlscript/decls/Prelude.mls | 11 +- .../src/test/mlscript/interop/Arrays.mls | 19 +- .../test/mlscript/invalml/InvalMLCodeGen.mls | 4 +- .../src/test/mlscript/newres/Arrays.mls | 88 +++++++- .../test/mlscript/newres/DeclaredTypes.mls | 24 +++ .../mlscript/newres/ForeignConstructors.mls | 29 +++ .../src/test/mlscript/newres/SpreadCalls.mls | 197 +++++++++++++++--- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 11 files changed, 381 insertions(+), 65 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ForeignConstructors.mls 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 7437b6d4c6..d7541626fe 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -1254,14 +1254,16 @@ object JSBuilder: * After checking for independent storage, reference sites use this to append `.class` * when accessing a class value. It returns true only for class/module/object symbols, * not for term symbols — so constructor calls like `Foo(args)` which resolve to the term - * symbol are not affected. */ + * symbol are not affected. Foreign declarations do not generate companion storage: + * their class value is the native binding even when constructor parameters are declared. */ def shouldBeLifted: Bool = val bsym = dsym.asBlkMember ( (dsym.asTrm orElse bsym.flatMap(_.asTrm)).isDefined || (dsym.asCls orElse bsym.flatMap(_.asCls)).flatMap(_.defn).exists(_.paramsOpt.isDefined) ) && - (dsym.asModOrObj orElse dsym.asCls).isDefined + (dsym.asModOrObj orElse dsym.asCls).isDefined && + dsym.defn.forall(_.hasDeclareModifier.isEmpty) end JSBuilder diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 4a3514af3a..5c492dcb84 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -154,11 +154,15 @@ class NewResolver: private def typeValues(using rs: NewResolverState) = rs.typeValues private def abstractTypes(using rs: NewResolverState) = rs.abstractTypes - // An omitted argument in a nominal annotation must not subscribe to the - // class parameter's constructor inference and silently refine that annotation. - private def abstractType(source: TypeResolution)(using NewResolverState): DeclaredType = abstractTypes.getOrElseUpdate(source, { + // An omitted argument in an annotation must not subscribe to constructor + // inference. Keep its parameter and annotation as a diagnostic witness: the + // enclosing nominal type is concrete even though this argument is unknown. + private def abstractType(source: TypeResolution, omitted: Opt[VarSymbol])(using NewResolverState): DeclaredType = abstractTypes.getOrElseUpdate((source, omitted), { val resolution = new TypeResolution(source.source, source.fail) - resolution.publish(TypeShape.Abstract) + resolution.publish(omitted match + case S(param) => TypeShape.Inferred(UnknownValueShape(source.source)(ShapeProvenance( + msg"Type argument '${param.nme}' is omitted in this annotation, so its shape is unknown." -> source.source.toLoc :: Nil))) + case N => TypeShape.Abstract) declaredType(resolution, Map.empty) }) @@ -179,7 +183,8 @@ class NewResolver: def follow(current: DeclaredType, args: Ls[DeclaredType], aliases: Set[TypeResolution], publish: Listener)(using NewResolverState): Unit = current.resolution.listen: shape => def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = - current.bindings ++ params.map(_.sym).zip(args.padTo(params.length, abstractType(current.resolution))) + current.bindings ++ params.zipWithIndex.map: (param, index) => + param.sym -> args.lift(index).getOrElse(abstractType(current.resolution, S(param.sym))) def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current.bindings), Nil, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) @@ -554,6 +559,38 @@ class NewResolver: case TupleShape.UnknownField(source, _) => receive(UnknownValueShape.at(source)) case TupleShape.ValueField(value, _) => receive(value) + /** Array spreads have unknown length, but their declared element type still + * constrains every element. Never recover elements from constructor values: + * Arrays are mutable, and the one-number constructor creates empty slots. + */ + private def listenArrayElements(shape: TermShape)(listener: Listener)(using NewResolverState): Bool = + val cls = prelude.builtins.Array.defn.get + softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") + val param = cls.tparams.head.sym + val Marked(value, _) = shape + // Explicit arguments belong to the allocation/application itself. Outer + // marks transport that instance through parameters; they must affect + // delivered element shapes, but not the lookup of its explicit arguments. + val (head, instanceContext) = value.applicationHead + val context = shape.applicationHead._2 + def receive(element: TermShape)(using NewResolverState): Unit = element.exit(context) match + case value: TermShape => listener(value) + case NoShape => () + head match + case nominal: NominalTypeShape => nominal.ancestor(cls) match + case S(array) => + array.bindings.get(param) match + case S(bound) => + listenTypeValues(bound)(receive) + true + case N => false + case N => false + case defn: DefnShape if defn.clsDef.contains(cls) && shape.isInstanceOfClass(cls) + && rstate.hasExplicitTypeArgument(param, instanceContext) => + listenTypeArgument(param)(receive) + true + case _ => false + /** Subscribe once to complete argument-tuple candidates. A pending spread is not * an argument, nor evidence of an arity mismatch; each resolved combination is. */ @@ -1073,7 +1110,7 @@ class NewResolver: case N => // An explicit projection can name a narrower class, but // it cannot recover that class's implementation arguments. - val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) + val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs)), N) MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation) case _ => info member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc) @@ -1397,10 +1434,15 @@ class NewResolver: val spread = TupleShape(term, TupleShape.Unknown(term, Nil, DynShape()) :: Nil)(this) expand(rest, TupleShape.Spread(spread, marks) :: reversed) case shape @ Marked(_, marks) => - // Opaque iterables (e.g. external Arrays) have no resolved - // element layout. Their runtime spread is still permitted. - val unknown = TupleShape(term, TupleShape.Unknown(term, Nil, UnknownValueShape.spread(term, shape)) :: Nil)(this) - expand(rest, TupleShape.Spread(unknown, marks) :: reversed) + val typed = listenArrayElements(shape): + case Marked(element, context) => + val spread = TupleShape(term, TupleShape.Unknown(term, context :: Nil, element) :: Nil)(this) + expand(rest, TupleShape.Spread(spread, NoMarks) :: reversed) + if !typed then + // Other opaque iterables have no known element type. Their + // runtime spread remains permitted without authorizing calls. + val unknown = TupleShape(term, TupleShape.Unknown(term, Nil, UnknownValueShape.spread(term, shape)) :: Nil)(this) + expand(rest, TupleShape.Spread(unknown, marks) :: reversed) expand(tuple.fields, Nil) case record: Rcd => listenAggregate(record, listener): publish => def expand(stats: Ls[Statement], reversed: Ls[RecordShape.Element])(using NewResolverState): Unit = stats match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index a57fe0e725..18e2d1171e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -187,7 +187,7 @@ final class NewResolverState private ( new Cache(source.map(_.typeInterpretations), identity) val typeValues: Cache[DeclaredType, TypeValues] = new Cache(source.map(_.typeValues), identity) - val abstractTypes: Cache[TypeResolution, DeclaredType] = + val abstractTypes: Cache[(TypeResolution, Opt[VarSymbol]), DeclaredType] = new Cache(source.map(_.abstractTypes), identity) val signatureParameters: Cache[VarSymbol, DeclaredType] = new Cache(source.map(_.signatureParameters), identity) diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index a665aec534..efd9db93a2 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -102,12 +102,11 @@ declare class Class // [ <1 empty item> ] // > Array(1, 2) // [ 1, 2 ] -// We used to declare it as a class taking exactly one argument here to avoid that footgun: -// declare class Array[T](val length: Int): Array[T] -// but this made instance checks wrongly use `Array.class`; -// TODO: handle Array and other JS classes that can be instantiated without `new` specially in codegen. -declare class Array[T] with - val length: Int +// Expose only the length-taking constructor; use tuple literals for initial elements. +// Foreign constructor signatures describe the native binding and do not introduce +// the `.class` storage used by generated MLscript constructor functions. +// The `val length` parameter also supports matching with `Array(length)`. +declare class Array[T](val length: Int): Array[T] with fun join(...separators): Str fun reverse(): Array[T] fun sort(...compare): Array[T] diff --git a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls index 372a72581b..aa30b2d956 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls @@ -63,28 +63,25 @@ Array Array(1) //│ = [<1 empty items>] -:breakme // TODO better report an error, here :e Array() +//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 0 +//│ ║ l.67: Array() +//│ ╙── ^^ //│ = [] -:breakme // TODO better report an error, here :e Array(1, 2, 3) +//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 3 +//│ ║ l.74: Array(1, 2, 3) +//│ ╙── ^^^^^^^^^ //│ = [1, 2, 3] if arr is Array then "ok" //│ = "ok" -:fixme // TODO handle? +:expect 2 if arr is Array(n) then n -//│ ╔══[COMPILATION ERROR] Class Array does not have a parameter list -//│ ║ l.81: if arr is Array(n) then n -//│ ║ ^^^^^ -//│ ╟── but the pattern has one sub-pattern -//│ ║ l.81: if arr is Array(n) then n -//│ ╙── ^ -//│ ═══[RUNTIME ERROR] Error: match error - +//│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls b/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls index f5a3a22735..e371ef0fce 100644 --- a/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls +++ b/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls @@ -192,7 +192,7 @@ fun nott = case //│ case false: //│ return true; //│ } -//│ throw globalThis.Object.freeze(new globalThis.Error.class("match error")) +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ }); //│ return lambda2 //│ }; @@ -208,7 +208,7 @@ nott of false //│ } else if (false === false) { //│ return true //│ } -//│ throw globalThis.Object.freeze(new globalThis.Error.class("match error")); +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = true //│ Type: Bool diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index bad7391c34..36f22a910b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -84,7 +84,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.119: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.118: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:85 @@ -108,3 +108,89 @@ xs.map((x, ...) => x.original) //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Replacement(2)] + +// Declared constructor parameters do not introduce a generated `.class` binding. +:sjs +:expect 3 +let allocated = new Array[Int](3) +allocated.length +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let allocated; +//│ allocated = globalThis.Object.freeze(new globalThis.Array(3)); +//│ return allocated.length +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 3 +//│ allocated = [<3 empty items>] + +// Both bare instance checks and constructor-field patterns use the native Array. +:expect [true, 3] +[allocated is Array, if allocated is Array(length) then length else 0] +//│ = [true, 3] + +// Foreign classes with companion modules continue to use their native bindings. +:expect true +new String("hello") is String +//│ = true + +// The length-only Array interface rejects element-list construction. +:ge +new Array[Int](1, 2) +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.137: new Array[Int](1, 2) +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Array' expected 1 argument, but got 2 +//│ ║ in ../decls/Prelude.mls +//│ ║ l.108: declare class Array[T](val length: Int): Array[T] with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.109: fun join(...separators): Str +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.110: fun reverse(): Array[T] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.111: fun sort(...compare): Array[T] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.112: ... (more lines omitted) ... +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ = [1, 2] + +:ge +new Array[Int]() +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.156: new Array[Int]() +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Array' expected 1 argument, but got 0 +//│ ║ in ../decls/Prelude.mls +//│ ║ l.108: declare class Array[T](val length: Int): Array[T] with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.109: fun join(...separators): Str +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.110: fun reverse(): Array[T] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.111: fun sort(...compare): Array[T] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.112: ... (more lines omitted) ... +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ = [] + +// TODO: select the callable overload when a class also has a companion module. +:fixme +:expect 3 +Array[Int](3).length +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.177: Array[Int](3).length +//│ ║ ^^^^^^^^^^ +//│ ╟── Module 'Array' expected 0 type arguments, but got 1 +//│ ║ in ../decls/Prelude.mls +//│ ║ l.127: declare module Array with +//│ ╙── ^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.177: Array[Int](3).length +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Module 'Array' cannot be called like a function. +//│ ║ in ../decls/Prelude.mls +//│ ║ l.127: declare module Array with +//│ ╙── ^^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'length' has no resolved target +//│ ║ l.177: Array[Int](3).length +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 44e8e9985f..84698e7013 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -657,3 +657,27 @@ class Box[X](val value: [X, X]) fun foo(x: Box[Item]) = x.value.0.oops foo(Box[Item]([new Item, new Item])) //│ = 42 + +// Omitted type arguments are unknown; the enclosing class still has its interface. +:ge +class Pair[A, B](val value: [A, B]) +fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.664: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'oops' of a value with unknown shape. +//│ ╟── Type argument 'B' is omitted in this annotation, so its shape is unknown. +//│ ║ l.664: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ╙── ^^^^ + +// An omitted alias parameter retains the annotation that left it unspecified. +:ge +type HalfPair[T] = Pair[Int, T] +fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.676: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'oops' of a value with unknown shape. +//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. +//│ ║ l.676: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ╙── ^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/ForeignConstructors.mls b/hkmc2/shared/src/test/mlscript/newres/ForeignConstructors.mls new file mode 100644 index 0000000000..5fe0405e3d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ForeignConstructors.mls @@ -0,0 +1,29 @@ +:. + +// Explicit foreign function overloads and class constructors share the native binding. +declare class String with + constructor(value: Str) + val length: Int +declare fun String(value: Str): Str + +:sjs +:expect "hello" +String("hello") +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ return runtime.safeCall(globalThis.String("hello")) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = "hello" + +:sjs +:expect 5 +let boxed = new String("hello") +boxed.length +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let boxed; boxed = globalThis.Object.freeze(new globalThis.String("hello")); return boxed.length +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 5 +//│ boxed = String { "0": "h", "1": "e", "2": "l", "3": "l", "4": "o", length: 5 } + +:expect true +boxed is String +//│ = true diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index 415a33848f..81830d81b0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -169,7 +169,9 @@ keep(9, ...growing(3), 8) //│ = [9, 3, 2, 1, 8] -let arr = new Array(1,2,3,4) +// Hide both length and element types while retaining concrete runtime test values. +fun opaque(values): Array = values +let arr = opaque([1,2,3,4]) //│ arr = [1, 2, 3, 4] :sr @@ -189,7 +191,7 @@ let arr = if maybe then [1, 2] else [1, 2, 3, 4] :e four(...arr) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.190: four(...arr) +//│ ║ l.192: four(...arr) //│ ║ ^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got 2 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -202,64 +204,64 @@ four(...arr) fun lastOfTwo(a, b) = b fun firstOfTwo(a, b) = a fun onlyOne(a) = a -[lastOfTwo(...new Array("ignored"), Box(91)).value, - firstOfTwo(Box(92), ...new Array("ignored")).value, - onlyOne(...new Array(), Box(93)).value] +[lastOfTwo(...opaque(["ignored"]), Box(91)).value, + firstOfTwo(Box(92), ...opaque(["ignored"])).value, + onlyOne(...opaque([]), Box(93)).value] //│ = [91, 92, 93] // Every unknown segment can supply the remaining argument slots. :expect [1, 2, 3, 4] -four(...new Array(1, 2), ...new Array(3, 4)) +four(...opaque([1, 2]), ...opaque([3, 4])) //│ = [1, 2, 3, 4] // Rest consumption across an unknown segment stays unknown on subsequent calls. :expect [1, 2, 3, 4] fun dropTwo(a, b, ...tail) = tail -four(...dropTwo(...new Array(0, 0, 1, 2, 3), 4)) +four(...dropTwo(...opaque([0, 0, 1, 2, 3]), 4)) //│ = [1, 2, 3, 4] // Known fields that must remain in the rest tail retain their value shapes. :expect 94 fun afterOne(a, ...tail) = tail fun tailLast(a, b, c) = c -tailLast(...afterOne(...new Array("ignored"), 1, 2, Box(94))).value +tailLast(...afterOne(...opaque(["ignored"]), 1, 2, Box(94))).value //│ = 94 // An opaque spread does not authorize a call through its unknown element. :e fun invokeUnknown(f) = f() -invokeUnknown(...new Array(() => 95)) +invokeUnknown(...opaque([() => 95])) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.230: fun invokeUnknown(f) = f() +//│ ║ l.232: fun invokeUnknown(f) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.231: invokeUnknown(...new Array(() => 95)) -//│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ╟── This spread has no known element shape. -//│ ║ l.231: invokeUnknown(...new Array(() => 95)) -//│ ╙── ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.173: fun opaque(values): Array = values +//│ ║ ^^^^^ +//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. +//│ ║ l.173: fun opaque(values): Array = values +//│ ╙── ^^^^^ //│ = 95 // Unknown elements must not silently resolve against another known candidate. :e fun inspectUnknown(x) = x.value inspectUnknown(Box(96)) -inspectUnknown(...new Array(Box(96))) +inspectUnknown(...opaque([Box(96)])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.245: fun inspectUnknown(x) = x.value +//│ ║ l.247: fun inspectUnknown(x) = x.value //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This spread has no known element shape. -//│ ║ l.247: inspectUnknown(...new Array(Box(96))) -//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. +//│ ║ l.173: fun opaque(values): Array = values +//│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Too many known fields remain a proven mismatch even with an unknown spread. :e -four(...new Array(), 1, 2, 3, 4, 5) +four(...opaque([]), 1, 2, 3, 4, 5) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.259: four(...new Array(), 1, 2, 3, 4, 5) -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.261: four(...opaque([]), 1, 2, 3, 4, 5) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got at least 5 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -268,16 +270,16 @@ four(...new Array(), 1, 2, 3, 4, 5) // Rest arity may be valid while the callable argument remains unknown. :e fun invokeRest(f, x, ...tail) = f() -invokeRest(...new Array(() => 97), Box(0)) +invokeRest(...opaque([() => 97]), Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.270: fun invokeRest(f, x, ...tail) = f() +//│ ║ l.272: fun invokeRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.271: invokeRest(...new Array(() => 97), Box(0)) -//│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ╟── This spread has no known element shape. -//│ ║ l.271: invokeRest(...new Array(() => 97), Box(0)) -//│ ╙── ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.173: fun opaque(values): Array = values +//│ ║ ^^^^^ +//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. +//│ ║ l.173: fun opaque(values): Array = values +//│ ╙── ^^^^^ //│ = 97 // When repeatedTuples receives [Box(98)], both a and b must acquire its element @@ -290,3 +292,138 @@ private fun repeatedTuples(xs) = [second(...a).value, second(...b).value] repeatedTuples([Box(98)]) //│ = [98, 98] + +// A public callback signature supplies the callable interface even for an opaque spread. +:expect 97 +fun invokeAnnotatedRest(f: () -> Int, x, ...tail) = f() +invokeAnnotatedRest(...opaque([() => 97]), Box(0)) +//│ = 97 + +// Array element annotations survive spreads; their length remains unknown. +:expect 97 +let callbacks = new mut Array[() -> Int](0) +callbacks.push(() => 97) +private fun invokeTypedRest(f, x, ...tail) = f() +invokeTypedRest(...callbacks, Box(0)) +//│ = 97 +//│ callbacks = [fun] + +// A public unannotated callback is checked for unknown callers despite a typed local call. +:e +fun invokePublicRest(f, x, ...tail) = f() +invokePublicRest(...callbacks, Box(0)) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.313: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ ^^^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.313: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ ^ +//│ ╟── Parameter 'f' admits values of unknown shape. +//│ ║ l.313: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ ^ +//│ ╟── 'invokePublicRest' is exposed by this compilation unit. +//│ ║ l.313: fun invokePublicRest(f, x, ...tail) = f() +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ = 97 + +// A function parameter's Array annotation carries its element interface through a spread. +:expect 97 +fun invokeArray(xs: Array[() -> Int]) = + private fun invoke(f, x, ...tail) = f() + invoke(...xs, Box(0)) +invokeArray(callbacks) +//│ = 97 + +// A private tuple-based call can infer the callback directly. +:expect 98 +private fun invokeTuple(f, x, ...tail) = f() +invokeTuple(...[() => 98], Box(0)) +//│ = 98 + +// Aliases and nested tuple elements retain their declared shapes through rest forwarding. +:expect 42 +class Item(val value: Int) +type Pairs[T] = Array[[T, Int]] +private fun first(head, ...tail) = head +private fun skip(head, ...tail) = first(...tail) +fun readPairs(xs: Pairs[Item]) = skip(0, ...xs).0.value +readPairs([[Item(42), 1]]) +//│ = 42 + +// Annotating an array with a base element type must hide subclass-only members. +:ge +class Base +class Child extends Base with + val extra = 42 +fun readBase(xs: Array[Base]) = first(...xs).extra +readBase([new Child]) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.358: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Different type arguments keep distinct element interfaces at their call sites. +:expect [41, 42] +class Left(val left: Int) +class Right(val right: Int) +fun head[A](xs: Array[A]): A = first(...xs) +[head[Left]([Left(41)]).left, head[Right]([Right(42)]).right] +//│ = [41, 42] + +// Constructor type arguments are scoped to each array allocation. +:expect [43, 44] +let lefts = new mut Array[Left](0) +let rights = new mut Array[Right](0) +lefts.push(Left(43)) +rights.push(Right(44)) +[first(...lefts).left, first(...rights).right] +//│ = [43, 44] +//│ lefts = [Left(43)] +//│ rights = [Right(44)] + +// An unannotated allocation cannot borrow another array's element type or its pushed values. +:e +let unknownCallbacks = new mut Array(0) +unknownCallbacks.push(() => 99) +private fun invokeUnannotatedArray(f, ...tail) = f() +invokeUnannotatedArray(...unknownCallbacks) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.389: private fun invokeUnannotatedArray(f, ...tail) = f() +//│ ║ ^^^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.390: invokeUnannotatedArray(...unknownCallbacks) +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── This spread has no known element shape. +//│ ║ l.390: invokeUnannotatedArray(...unknownCallbacks) +//│ ╙── ^^^^^^^^^^^^^^^^ +//│ = 99 +//│ unknownCallbacks = [fun] + +// TODO: calling a captured helper merges the enclosing activations, also for ordinary tuples. +:fixme +:expect [43, 44] +private fun firstInArray(xs) = first(...xs) +[firstInArray(lefts).left, firstInArray(rights).right] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.407: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Right' does not contain member 'left' +//│ ║ l.369: class Right(val right: Int) +//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.407: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Left' does not contain member 'right' +//│ ║ l.368: class Left(val left: Int) +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[43, 44]', got: 'undefined' + +// Passing an allocation through a private parameter preserves its declared element type. +:expect [43, 44] +private fun localFirstInArray(xs) = + private fun head(x, ...tail) = x + head(...xs) +[localFirstInArray(lefts).left, localFirstInArray(rights).right] +//│ = [43, 44] diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 60898647ce..5dc11f0c5f 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.272: fun +//│ ║ l.271: fun //│ ║ ^^^ -//│ ║ l.273: const(c: Int): Int32 +//│ ║ l.272: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 55e916e4bef59fd975b7f023d0e4aa3d8a057a4f Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 24 Sep 2026 18:48:04 +0800 Subject: [PATCH 083/228] Minor --- hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls | 1 - 1 file changed, 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls index e34e2bae64..ddf68bdc8c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls @@ -1,4 +1,3 @@ -#config(liftDefns: Some(LiftDefns)) import "../../Predef.mls" From 86e02144dfd4ebd95c381d0aa2ccd9cbf542c559 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 19:02:13 +0800 Subject: [PATCH 084/228] docs: streamline new-resolution migration notes --- doc/new-resolution-design.md | 147 ++++ doc/new-resolution-suite-migration.md | 938 +++++--------------------- doc/new-resolution-wasm.md | 89 +-- doc/reference.md | 58 ++ 4 files changed, 411 insertions(+), 821 deletions(-) create mode 100644 doc/new-resolution-design.md diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md new file mode 100644 index 0000000000..6d2ad3905a --- /dev/null +++ b/doc/new-resolution-design.md @@ -0,0 +1,147 @@ +# New-resolution implementation invariants + +This note describes the resolver's implementation contracts. User-facing rules +are in the [language reference](reference.md#resolution-interfaces); outstanding +ports and implementation gaps belong in the [migration worklist](new-resolution-suite-migration.md). + +## Inference ownership and completed references + +[`NewResolverState`](../hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala) +owns the inference graph and caches. Listeners have type +`TermShape => NewResolverState ?=> Unit`, so imported callbacks access hosts +through the consuming state. Importing copies accessed hosts' candidates and +listeners lazily; caches consult individual source entries on demand. It must +not copy or enumerate an exporter's entire resolver maps. + +Type-parameter references retain their originating host. This lets exporter-private +inference on third-party parameters survive re-exporting. Legacy annotation +interpretations also remain local, rather than attaching consumer callbacks to +shared prelude syntax. + +Completion seals each file or diff-test block's recorded decisions and original +host data read by erasure/lowering. Symbol flow and elimination listeners remain +active against private host data, but cannot change completed reference targets. +This applies even between worksheet blocks sharing an elaborator state. An unknown +reaching a sealed elimination can still report an error without changing its target. + +[`CompilerCacheTest`](../hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala) +checks independent consumers and unchanged source candidates, listeners, and +legacy annotations. [`PublisherTest`](../hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala) +covers cycles, reentrant replay, transitive private inference, and bounded copying +in the presence of unrelated exporter hosts. `newres/GenericMethods.mls` covers +exported mapped arrays and imported generic functions and methods. + +## Captures and generic flow + +Class bodies introduce lexical captures, and a class and its constructor share +one resolution boundary. A method's reference to an outer constructor must include +the enclosing instance boundary as well as the method boundary. Consuming a +reconstructed instance cancels the old instance exit against its capture, retaining +the fresh constructor exit. Explicit `new` uses the same constructor context for +arguments and subsequent parameter lists. + +Context fragments compose from the definition to its consumer; argument flow +traverses that composition in reverse. Capture paths have no truncation or depth +limit. The optional `checkMarkPaths` assertions in +[`Shape.scala`](../hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala) +detect repeated boundaries in either direction. They are disabled by default +because scanning every new mark's tail makes chain construction quadratic in depth. +`newres/RecursiveEnvironment.mls` and `ConstructorFieldRecovery.mls` exercise +recursive calls, nested captures, partial construction, and independent field results. + +Explicit and inferred function/constructor type arguments flow through the +corresponding type-parameter symbols with entry/exit marks. Explicit arguments +prevent further refinement from value arguments. Inference also connects nested +nominal parameters, such as `Foo[A]` containing a `Box[A]`. Generic methods use +the same flow as free functions, including callback-result inference and curried +signatures. Declared callable shapes retain their type parameters. + +Generic definitions receive a fresh abstract activation when their type parameters +are declared. This enforces generic opacity independently of visibility, exposure, +or strict mode. The abstract activation cannot match a concrete caller's exit, +so concrete calls still substitute into generic results without specializing the +definition's allowed operations. + +## Declared interfaces and exposure checking + +Type interpretations retain arrows, type arguments, and lexical captures. +Generic aliases and inherited declared interfaces substitute their arguments; +declared member selections carry those contexts without reading implementation +value flow. An unannotated member read through a declared interface produces an +unknown shape, rather than consulting its initializer or method body. +Callback parameter types constrain implementation parameters, and callback results +constrain inferred type arguments. `newres/DeclaredTypes.mls` covers these paths, +separate signatures, tuple constraints, and distinct generic instantiations. + +[`InterfaceExposure`](../hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala) +checks values reachable through a file's exposed interface before completion. +Worksheet blocks run this pass only under strict resolution. It seeds unannotated +parameters with `UnknownValueShape` and follows returned functions, record/tuple +contents, public instance members, and both interpretations of exposed overloads. +Callable result annotations constrain returned implementations through their +declared domains. Private helpers retain local inference unless their values escape. + +Discovery uses a queue and temporary observers of shape publishers. Each reached +shape is processed once, with listeners discovering subsequent candidates; +there is no repeated whole-graph scan. Observers detach before completion and +are never copied into consumer states. Imported new-resolution definitions have +already had their interfaces checked and are not recursively checked by importers. + +Unknown values and unresolved spreads carry shared, lazy provenance. Messages, +locations, and diagnostic chains are materialized only on error; provenance does +not affect shape equality. Diagnostics can identify the parameter and the storage +and return steps that expose it. `newres/InterfaceExposure.mls` covers discovery, +typed closures, overloads, and later exposure of a private definition from an +earlier block; its expected failures also document remaining precision gaps. + +## Structural and dynamic shapes + +Record fields have their own `BlockMemberSymbol`s. Lookup follows overwrites and +spreads in source order, preserving selected values' shapes and captures. Named +fields in term tuples are expressions, not type annotations. Tuple shapes group +them into one trailing record, matching lowering while retaining source evaluation +order, last-write-wins lookup, computed-key uncertainty, and property identities +across imports. See `newres/NamedFields.mls`, `ImportedNamedFields.mls`, and +`RecordInterfaces.mls`. + +Tuples preserve zero-based projections and expose the builtin Array class as +their parent. The parent's element argument is the union of element shapes, +including annotations and call-context marks. Mutable arrays discard initial +element shapes and length because mutation can change both. Unknown layouts +permit indexed access with an unknown result without authorizing arbitrary members. +Tuple-pattern transfer handles leading, trailing, and rest elements, including +declared Array element types. See `newres/Arrays.mls` and `TuplePatternBindings.mls`. + +`DynShape` is distinct from `UnknownValueShape`: recursive widening, mutable reads, +and other losses of precision do not license dynamic lookup or calls. Known invalid +alternatives still report errors alongside dynamic candidates. Constructor patterns +narrow unknown inputs to declared class interfaces, retaining unknown type arguments +and provenance for unannotated fields instead of specializing them from local calls. + +Dynamic selections have no fabricated nominal definition. Lowering retains the +runtime receiver and property name. Dynamic values propagate through record/tuple +spreads and unique wildcard opens; competing wildcard receivers still need +disambiguation. Class projections, constructor patterns, and type references require +known, unambiguous identities. See `newres/Dynamic.mls`, `Records.mls`, +`SpreadCalls.mls`, and `loose/Targets.mls`. + +## Overloads, foreign declarations, and lowering + +Ordinary values select function/constructor overloads; selection receivers select +companion modules. Preserve this distinction through nested captures and opens. +`newres/OverloadedCalls.mls` covers direct, generic, stored-function, module-member, +and captured uses. + +Foreign declarations expose call and constructor capabilities explicitly. +The JS backend uses native class values for `new` and patterns without generated +MLscript `.class` storage. Bodyless foreign methods use foreign-call normalization +even when `declare` is on the enclosing class, so JavaScript `undefined` results +follow the existing unit normalization. Prelude declarations are parsed as one +compilation unit, permitting references across blank lines. + +Generated runtime-helper selections, such as `runtime.assertFail`, are synthetic; +they do not provide a fallback for unresolved source selections. Free-variable +collection and assignment lowering must handle resolved direct references and +retain the existing binding/assignment checks. Constructor-pattern resolution +discards every nested capture when recovering class identity; leaving one behind +can make generated patterns test a constructor function instead of its class. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 75f014aa24..ca23e4d4e3 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -1,19 +1,18 @@ # New-resolution suite migration -## Scope and current progress - -The first pass tried all 372 active `.mls` files in `basics`, `codegen`, `ucs`, -`ups`, and `apps` with new resolution and strict resolution, retaining 185 ports. -After dynamic shapes and non-strict suite defaults became available, all 187 -deferred worksheets were retried, bringing the count to 239. After declared -interfaces, generic flow, tuple projections, and Array inheritance were implemented, -all 133 remaining worksheets (plus the two deferred WASM worksheets) were retried. -That pass migrated another 13; the parser follow-up migrates seven more worksheets, -bringing the count to 259 of 372 (70%). The 51 files under -`ucs/staging` are excluded by the test runner and remain unchanged. Counts below exclude shared `.mls` -configuration files and new-resolution regression tests. - -| Suite | Migrated | Retained on existing configuration | Active files | +## Scope and status + +This is the worklist for migrating existing worksheets and compilation fixtures +to new resolution. Language rules live in the [language reference](reference.md#resolution-interfaces); +resolver invariants and regression coverage live in the +[resolver design notes](new-resolution-design.md). Backend-specific constraints +are in [WASM resolution](new-resolution-wasm.md). + +The checked-in worksheet headers give the following status. Counts exclude shared +`.mls` configurations, new-resolution regression tests, and the 51 files under +`ucs/staging`, which the test runner excludes. + +| Suite | Migrated | Legacy configuration | Active files | | --- | ---: | ---: | ---: | | basics | 60 | 30 | 90 | | codegen | 86 | 39 | 125 | @@ -21,225 +20,146 @@ configuration files and new-resolution regression tests. | ups | 49 | 22 | 71 | | apps | 12 | 4 | 16 | | Total | 259 | 113 | 372 | +| wasm (separate backend) | 19 | 2 | 21 | + +All twelve `apps/parsing` worksheets use new resolution, but some implementation +modules they import still use legacy resolution. Worksheet migration does not +imply that its dependencies have been migrated. -Migrated files start with `:.`. Each suite's `.mls` loads the common language -configuration and uses its non-strict default; nested directories inherit through -`:..`. The redundant `#lang(strictResolution: false)` settings have been removed -from the six general suites, including WASM. `newres` retains strict resolution; -`newres/loose` still needs an explicit override of that strict parent. Non-strict -resolution permits multiple selection/call targets, but still rejects missing -targets and known invalid operations. The common language configuration disables -JS execution, so each file retains its original execution flags. A parser-only test must not acquire runtime execution -merely because its resolution configuration changes. - -The previous batch comprised one basics test (`ObjectExtensions`), 16 codegen -tests, two UCS compiled-pattern tests, and 35 UPS tests. They include JavaScript -imports, spread calls, qualified constructors, and recursive, parametric, and -fixed-point patterns. Five new UPS directory configurations connect these tests -to the common language configuration. - -In that batch, forty-nine files needed only the configuration header and refreshed goldens. -`BadThis`, `BasicTerms`, `Spreads`, and `ImportMLs` additionally expect static -errors for invalid operations already present in their negative cases; no existing -error expectations were removed. `Throw` uses `new Error(...)`, preserving its -throw/catch results. Tests of foreign callable classes remain deferred rather -than being rewritten to avoid the capability under test. - -The latest ports are: - -- basics: `DynamicSelection`, `ExplicitLabels`, `LiteralSelection`, `MutArr`. -- codegen: `Arrays`, `BlockPrinter`, `CurriedClassInheritance`, `Inliner`, `Misc`, - `NestedScoped`, `PlainClasses`, `TraceLog`. -- UPS: `regex/EmailAddress`. - -Nine need only the configuration header and refreshed goldens. `ExplicitLabels`, -`CurriedClassInheritance`, `PlainClasses`, and `Arrays` additionally expect compilation errors -for existing negative cases now rejected statically: a shadowed label's missing -`break`, invalid constructor overapplication/member selections, and an empty call -on a plain class, and known out-of-bounds tuple reads. Their existing runtime-error -expectations remain in place. - -The two remaining WASM worksheets were also retried; both remain deferred. -WASM therefore remains at 19 migrated worksheets out of 21, outside the table -above. - -Blocked files retain their complete original sources and golden outputs. No new -per-file `:todo`, `:fixme`, `:ignore`, or resolution-mode exception hides a migration -failure. Existing expected failures remain visible. The inventory below records -trial observations, not newly accepted failures. - -Compilation fixtures are now being migrated as well; the worksheet counts above -are independent of that work. Mixed-mode imports remain necessary. A fixture -must pass both standalone compilation and its existing consumers before its -language header is retained. See the compilation-fixture audit below. - -The annotated-receiver capture assertion in WASM `Basics` is fixed; a later -unannotated `this.x` initializer still blocks that worksheet (see task 3). -Method calls across REPL blocks remain assigned to the other branch. -The constructor-pattern context-mixing regression now passes; the remaining -pattern-transfer gaps are listed below. - -## Compilation-fixture migration - -All 107 original fixtures under `hkmc2/shared/src/test/mlscript-compile/` were inventoried. -Four already used new resolution. Each of the other 103 was tried independently -with `#lang(0.3.x)`, retaining its dependencies' configuration; the remaining -candidates were retried after the fixes below. Combined compilation and runtime -consumer tests decide which ports are retained. Real files check their exposed -interfaces even in non-strict mode. - -| Compilation suite | Newly migrated | Total migrated | Deferred | Total | -| --- | ---: | ---: | ---: | ---: | -| Main (including quotes, UPS, and regression/compatibility fixtures) | 21 | 26 | 23 | 49 | -| Applications | 6 | 6 | 14 | 20 | -| Nofib | 12 | 12 | 27 | 39 | -| WASM | 0 | 0 | 1 | 1 | -| Total | 39 | 44 | 65 | 109 | - -The added `LegacyOption.mls` is counted among the deferred fixtures: 64 original -fixtures remain unported, plus this temporary compatibility copy. The additional -`NamedFieldLibrary.mls` regression fixture uses new resolution and is counted in -the migrated total, but is not a port of one of the 107 original fixtures. - -The retained ports are: - -- Main: `Example`, `FilePrivateFunctions`, `NestedFunctionsNoLift`, `NoFreeze`, - `Option`, `QuoteExample2`, `Record`, and `StrOps`. -- All ten `quotes/` fixtures: `CSPBar`, `CSPBaz`, `CSPFoo`, `CSPNest`, `Cubic`, - `Gib12`, `Opened`, `QuoteFoo`, `QuoteInc`, and `SafeDiv`. -- UPS: `DnfCnf`, `EvenOddTree`, and `TruthyFalsy`. -- Applications: `parsing/PrattParsing`, `parsing/RecursiveDescent`, and - `parsing/TokenHelpers`, plus `parsing-web-demo/Examples` after the named-field fix. - `parsing/BasicExpr` and `parsing/Expr` are now migrated with their worksheets. -- Nofib: `banner`, `boyer`, `boyer2`, `calendar`, `clausify`, `cryptarithm1`, - `gcd`, `mandel`, `mandel2`, `puzzle`, `rsa`, and `sphere`. - -Most ports need only the language header. `Example.funnySlash` now -declares its generic callable parameter interface, and `rsa.int_if_char` declares -its string parameter. `Example.Test(...)` remains a direct constructor call: -needing `new` there was an overload-resolution bug, not a language rule. The -prelude now declares callable `Error` overloads, so existing `Error(...)` calls -also remain unchanged. `Term.codegen` emits the new language header when it -regenerates quotation fixtures, preserving their migration during runtime tests. -`Option` now declares its generic parameter, callback, and return interfaces. -No visibility changes or failure suppressions were added to make fixtures pass. -DOM and filesystem entry points are explicitly `dyn` in the prelude for now; -fixture parameters were not made dynamic to bypass missing interfaces. - -All twelve `apps/parsing` worksheets now use new resolution, including the seven -previously deferred lexer, parser, rule, and visualizer tests. Their successful -runtime output is unchanged. Lexer callers now pass the actual `noWhitespace` -options record. The heterogeneous rule-table rendering callback declares the -structural `display` interface it needs. Implementation modules that still use -legacy resolution are explicitly listed below; migrating their callers does not -claim that these dependencies have been migrated. - -### Fixes found while porting - -- Ordinary values select function/constructor overloads; selection receivers - select companion modules. This distinction survives nested captures and opens. - Function/module overloads are accepted under new resolution, and interface - exposure checks both overloads. `newres/OverloadedCalls.mls` exercises direct, - generic, stored-function, module-member, and captured uses. -- Discarding captures for constructor-pattern resolution now traverses every - nested capture. Previously, parser fixtures compiled but tested the generated - constructor function instead of its class, producing runtime match errors. -- The prelude declares `Array.at`, `fill`, and `splice`, common `Str` and `Num` - methods, and the basic Set, Map, and RegExp interfaces. Primitive literals use - those declared interfaces, including inheritance (`Int` extends `Num`). - `newres/Arrays.mls` and `PrimitiveMembers.mls` cover execution and missing-member - diagnostics. The previously expected failure for string `repeat` now succeeds. -- Calls to bodyless foreign method declarations use foreign-call normalization, - even when `declare` appears on their enclosing class rather than the method. - In particular, an out-of-range `Array.at` returns unit through the existing - JavaScript `undefined` normalization instead of failing an MLscript-call check. -- Foreign callable constructor declarations cover `Error`, `TypeError`, - `RangeError`, `Array`, and `String`. The JS backend uses their existing host - class values for `new` and patterns, without an MLscript `.class` wrapper. -- The prelude's own diff test now parses it as one compilation unit, matching - normal compiler loading so mutually referring declarations can cross blank - lines. `newres/HostInterfaces.mls` checks dynamic DOM and filesystem access - without requiring a DOM in the test VM. -- Assignment lowering accepts resolved member references and uses the existing - symbol assignment checks. `newres/MemberAssignments.mls` covers a mutable field - accessed by name inside a method. - -- Root classes, objects, and modules inherit `Object`'s member interface. Explicit - parents and own-member overrides take precedence. `newres/ObjectMethods.mls` - covers inferred/annotated receivers, overrides, and subclass-pattern narrowing. -- Tuple patterns propagate leading, trailing, and rest shapes, preserving unknown - alternatives and generic opacity. Rest parameter annotations now describe the - whole rest array. `newres/TuplePatternBindings.mls` covers these paths. -- `Str.at` and `process.cwd` now have declared interfaces. - -### Remaining work and design boundaries - -Some deferred fixtures need source interfaces rather than compiler changes: -public callback parameters need function types, and public receivers need a -nominal or structural interface. For example, `QuoteExample.bind` calls its -unannotated `k`, and many Nofib helpers expose unannotated comparators or printers. -Choosing which helpers should instead be private changes the exported API and -was not done automatically. Annotation boundaries must remain opaque. - -Other failures require compiler or prelude work: - -1. **Legacy consumers inspecting new-resolution return annotations.** `Option` - compiles after adding generic callback/parameter interfaces. In the attempted - port, `getOrElse` has result annotation `A` and `flatMap` has `Option[B]`. - The legacy consumer `apps/parsing/Parser.mls` calls these functions (for - example, `getOrElse` at line 101 and `flatMap` at line 167). Its - `Resolver.resolveType` calls `resolveSign` on the imported return annotation; - that reads `legacyResolvedSym` from new-resolution syntax and fails with - `Legacy symbol query on a new-resolution term`. The application run reports - this failure for `Parser`, `Test`, `ParseRuleVisualizer`, and - `parsing-web-demo/main`; the latter three also depend on `Parser`. - `Option.mls` is now ported; the affected dependency graph imports the temporary - `LegacyOption.mls`, preserving shared constructor identities. - Its comment requires deletion when those consumers are ported. - `BasicExpr` and `Expr` are now ported together with their worksheet consumers. - The same boundary still prevents independently retaining the `Token` and - `Keywords` ports: legacy `Lexer`/`Parser` consumers inspect their new signatures. - A general interoperability bridge would need to consume completed type information - without re-resolving imported nodes or querying erased types before erasure. -2. **Flow through patterns and mutation.** Tuple bindings now transport field shapes, - including typed arrays and middle spreads. Guarded/transforming patterns, assignment - results, and mutable container element types still leave - some selections without shapes. `Char.AnyChar` loses the string shape before - its `length` guard; CSV's mutable nested array loses its element interface. - Assuming initializer shapes survive mutation would be unsound. -3. **Remaining host interfaces.** `Array.reduce` needs its accumulator/callback - contracts; `Map.set` and `Reflect.set` need keyword-named declarations; - WebAssembly exports still need interfaces. DOM and filesystem entry points - now use `dyn`; `HTMLElement` retains its nominal superclass identity with - dynamic host members. `Object.toString(): Str` is now declared, and classes - without an explicit parent inherit the `Object` interface. -4. **Imported lowering forms.** `Parser` encounters a synthesized selection - during new resolution, requiring import-boundary work. Named-field argument - grouping is fixed: tuple shapes now match lowering's positional values followed - by one record. `Extension`, `Rules`, `Token`, and web-demo `Examples` were - retried; their named-field arity errors are gone, and `Examples` is now ported. - `Rules.listLike` declares its structural field interface; `makeBracketRule` - now has one too. Some imported receivers still have no resolved target. -5. **Contextual argument insertion.** `Lexer` calls `Token.integer`, `symbol`, etc. - with a trailing `(using llt: LineLookupTable)` list. New resolution leaves these - calls partially applied instead of inserting the available contextual value. - `Lexer` also hits the opened binary `~`/builtin-operator conflict. These are - compiler implementation gaps, not reasons to remove the contextual API. -6. **Quotation coverage.** Quasiquote lowering still lacks some new type-selection - and wildcard-reference forms (`CSP`, `QuoteExample1`). - -### Deferred compilation inventory - -Paths below are relative to `mlscript-compile/`. Each row gives a concrete -remaining obstruction, not a claim that it is the only one. Deferred files keep -legacy resolution; imports that exchange options use `LegacyOption` consistently. +| Compilation suite | New resolution | Legacy resolution | Total | +| --- | ---: | ---: | ---: | +| Main (including quotes, UPS, and regression/compatibility fixtures) | 26 | 23 | 49 | +| Applications | 6 | 14 | 20 | +| Nofib | 12 | 27 | 39 | +| WASM | 0 | 1 | 1 | +| Total | 44 | 65 | 109 | + +These totals include the new-resolution `NamedFieldLibrary` regression fixture +and the temporary legacy `LegacyOption` compatibility fixture. + +## Migration procedure + +- Start migrated worksheets with `:.` before any blank line. Suite `.mls` files + inherit the common `#lang(0.3.x)` configuration through `:..`, including in + nested directories. The general suites use non-strict resolution; `newres` + enables strict resolution and `newres/loose` explicitly overrides it. +- Preserve each worksheet's execution flags. The common language configuration + disables JS execution; a parser-only test must not acquire runtime execution + merely because its resolution configuration changes. WASM has its own flags. +- Add `#lang(0.3.x)` to compilation fixtures only after checking both standalone + compilation and existing consumers. Real files check their exposed interfaces + even in non-strict mode. Add the callable, nominal, or structural interfaces + their public APIs require. Making a helper private changes the exported API; + using `dyn` to bypass a missing interface changes the language contract. +- Preserve successful results and negative-test intent. Existing invalid + operations may need compilation-error expectations in addition to runtime-error + expectations. Do not remove an expectation without resolving the intended + behavior, or rewrite a callable-constructor test to avoid the feature it tests. +- Keep blocked files on their existing configuration with their source and + goldens intact. Do not add per-file `:todo`, `:fixme`, `:ignore`, or resolution + exceptions to claim a port. Use focused regressions for unresolved compiler bugs. + +## Remaining work + +### Mixed-mode imports and fixture interfaces + +`Option` uses new resolution, but legacy consumers such as `apps/parsing/Parser` +inspect imported return annotations through `Resolver.resolveType`/`resolveSign`. +This queries legacy symbols on new-resolution syntax. The affected dependency +graph imports `LegacyOption` temporarily; consumers exchanging `Some`/`None` +values, including reflection and `Block`, must share the same constructor identities. +Port these consumers together and delete `LegacyOption`, restoring `Option` imports. +The same signature boundary blocks independent ports of `Token` and `Keywords`. +A general interoperability bridge must consume completed type information without +re-resolving imported nodes or querying erased types before erasure. + +Many fixtures also need source interfaces: for example, `QuoteExample.bind` calls +an unannotated callback, and Nofib helpers expose comparators and printers. +The inventory below separates these from compiler and prelude gaps. + +### Shape propagation and capture precision + +- **Generators:** `newres/GeneratorResults.mls` records a call whose result is + treated as the generator body's return value, leaving `.next` unresolved. + Model the iterator result produced by lowering; use `codegen/Generators` as + the worksheet acceptance case. +- **Mutation and control flow:** complete assignment/result flow, mutable + aggregate element interfaces, direct member-reference shapes, handlers, and + other result-bearing forms. Check `basics/MutVal`, `codegen/SetStmt`, + `codegen/ScopedBlocksAndHandlers`, and the CSV/Iter application consumers. + Initializer shapes cannot be assumed to survive mutation. +- **Captured activations:** preserve activation identity through captured + functions, constructor aliases, reconstruction of the same class, and partial + construction. Existing regressions include `CtxSens`, `ValCtxSens`, + `Projections`, `RecursiveEnvironment`, `SpreadCalls`, and `InterfaceExposure` + under `newres`. Do not turn unknown shapes into dynamic values or truncate + capture paths to make these cases pass. +- **Exposed nominal results:** `newres/InterfaceExposure.mls` records a returned + private class whose callable methods are not checked through its nominal result + annotation. Follow declared member interfaces back to their implementations + without exposing initializer shapes hidden by annotations. +- **Cross-block flow:** remaining cases include parameter flow in + `basics/MiscArrayTests`, unfinished closures in `codegen/FirstClassFunctionTransform`, + and the unresolved receiver in `codegen/ObjectMethodDebinding`. Coordinate + cross-block method changes with the separate implementation work before porting. + +### Patterns and generated references + +Complete record, conjunction/negation, string concatenation, guarded and +transforming pattern flow, and pattern values such as `.unapply`. Representative +worksheets are `ucs/general/BooleanPatterns`, `ucs/patterns/RecordPattern`, +`ucs/patterns/String`, `ups/RecursiveTransformations`, and `ups/examples/HindleyMilner`. +`Char.AnyChar` also loses its string interface before its `length` guard. + +`ucs/examples/EitherOrBoth` and the imported parser implementation expose +synthetic selections entering new resolution. Generated matcher code should retain +the source reference's completed target and receiver path through the `Lowering` +capability, without re-running lookup or cloning listener hosts with no candidates. +Transfer rules must distinguish matched inputs, bound fields, and transformed +outputs. Union alternatives, filter conjunctions successively, and avoid inventing +positive bindings for negation. Recursive patterns need cycle-aware subscriptions +and demonstrated convergence. + +Acceptance coverage should include tuple/record extraction, nested and imported +constructors, aliases, guards, transformed results, repeated references, and +recursive UPS matchers. Preserve both runtime results and matcher structure. + +### Calls, declarations, and diagnostics + +- Implement automatic contextual argument insertion for `Lexer` calls to + `Token.integer`, `symbol`, and related APIs with trailing `using` lists. + Resolve the opened binary `~`/builtin-operator conflict without removing those APIs. +- Complete type-argument validation (`basics/GenericClasses`), constructor-value + member lookup (`codegen/ParamClasses`, `basics/DynamicInstantiation`), and + module/call checks. Decide nominal module-forwarding compatibility for + `basics/CyclicModuleForwarders` before changing its expected outcomes. +- Extend host interfaces where needed: `Array.reduce` accumulator/callback + contracts, keyword-named `Map.set` and `Reflect.set`, and WebAssembly exports. + `Array.concat` currently returns `Array[Any]`; improving precision needs a + declared element constraint on its rest arguments. +- Complete quasiquote type-selection and wildcard-reference lowering (`CSP`, + `QuoteExample1`). +- Preserve source origins in synthesized references and diagnostics. In particular, + check the lost undefined-binding use sites in `ups/UpsBugsBacklog` and + `ups/syntax/MixedParameters` without changing the selected symbol. + +Retry remaining legacy worksheets against the current compiler and prelude before +treating a previously observed diagnostic as an implementation gap. + +## Deferred compilation fixtures + +Paths are relative to `hkmc2/shared/src/test/mlscript-compile/`. These are the +last recorded obstructions, not a fresh failure audit or an exhaustive diagnosis. +Retry each fixture before implementing a fix: subsequent compiler and prelude +changes may have removed its first blocker. Deferred fixtures retain legacy +resolution; imports that exchange options must use `LegacyOption` consistently. | Main fixture | Observed obstruction | | --- | --- | -| `Benchmark.mls` | Exposed `suite.run` and callback interfaces. Filesystem access is now dynamic. | +| `Benchmark.mls` | Exposed `suite.run` and callback interfaces. | | `Block.mls` | `Str.replaceAll`, `Any.toString`, and pattern-value interfaces. | | `CSP.mls` | Quasiquote type selections and wildcard opens. | -| `CachedHash.mls` | Original `this.toString` blocker is fixed by implicit Object inheritance; the fixture has not yet been retried. | +| `CachedHash.mls` | Retry candidate: implicit Object inheritance supplies the required `this.toString` interface. | | `Char.mls` | `AnyChar` loses the string shape before its `length` guard. | | `FingerTreeList.mls` | `Array.reduce` and numeric projection on an array rather than a fixed tuple. | | `Iter.mls` | Exposed callbacks and iterator `next` interfaces. | @@ -262,20 +182,20 @@ legacy resolution; imports that exchange options use `LegacyOption` consistently | Application fixture | Observed obstruction | | --- | --- | -| `apps/parsing/Accounting.mls` | Array callback arity, `reduce`, and receiver interfaces. | -| `apps/parsing/CSV.mls` | Mutable nested-array elements lack the shape required for `push`. | +| `apps/Accounting.mls` | Array callback arity, `reduce`, and receiver interfaces. | +| `apps/CSV.mls` | Mutable nested-array elements lack the shape required for `push`. | | `apps/parsing/Extension.mls` | Receivers for `display`, `add`, and `extendChoices` have no resolved target. | -| `apps/parsing/Keywords.mls` | Compiles after interface additions; legacy `Parser` consumers inspect its new signatures. | -| `apps/parsing/Lexer.mls` | Missing automatic contextual argument insertion and opened binary `~` resolution. The options record is now annotated. | -| `apps/parsing/ParseRule.mls` | Flow through private module/class `let` bindings and imported mutable maps; `keyword` now has an interface. | +| `apps/parsing/Keywords.mls` | Legacy `Parser` consumers cannot inspect its new-resolution signatures. | +| `apps/parsing/Lexer.mls` | Missing automatic contextual argument insertion and opened binary `~` resolution. | +| `apps/parsing/ParseRule.mls` | Flow through private module/class `let` bindings and imported mutable maps. | | `apps/parsing/ParseRuleVisualizer.mls` | Imported JS railroad API lacks member interfaces. | | `apps/parsing/Parser.mls` | Synthetic selections from imported legacy forms reach new resolution. | -| `apps/parsing/Rules.mls` | An `extendChoices` receiver has no resolved target through mutable map lookup. Both record-parameter helpers and exported rule results are annotated. | +| `apps/parsing/Rules.mls` | An `extendChoices` receiver has no resolved target through mutable map lookup. | | `apps/parsing/Test.mls` | `flags.has` and `tracer.reset` lose shapes through tuple/import flow. | -| `apps/parsing/Token.mls` | Compiles with its added interfaces and subclass-pattern narrowing; legacy contextual-argument consumers cannot read the new signatures. | +| `apps/parsing/Token.mls` | Legacy contextual-argument consumers cannot read its new-resolution signatures. | | `apps/parsing/Tree.mls` | Chained `JSON.stringify(...).slice(...)` lacks a result interface; also coupled to legacy consumers. | -| `apps/parsing/TreeHelpers.mls` | Coupled to `Tree` and its consumers; string input and `Object.toString` interfaces are now present. | -| `apps/parsing-web-demo/main.mls` | String callback interfaces, dynamic receiver flow through aliases/mutation, and tuple-bound `example.name`. DOM declarations themselves are now dynamic. | +| `apps/parsing/TreeHelpers.mls` | Coupled to `Tree` and its consumers. | +| `apps/parsing-web-demo/main.mls` | String callback interfaces, dynamic receiver flow through aliases/mutation, and tuple-bound `example.name`. | | Nofib fixture (under `nofib/`) | Observed obstruction | | --- | --- | @@ -283,535 +203,27 @@ legacy resolution; imports that exchange options use `LegacyOption` consistently | `eliza.mls`, `knights.mls`, `sorting.mls` | Unannotated string receivers and callbacks. | | `circsim.mls` | Exposed record receivers such as `p.pid` and `p.compType`. | | `cryptarithm2.mls` | Callable interface for an unannotated constructor field. | -| `cichelli.mls` | Callback interfaces; its original `toString` blocker is fixed. | -| `treejoin.mls` | Pattern flow; its original `toString` blocker is fixed. | +| `cichelli.mls` | Callback interfaces. | +| `treejoin.mls` | Pattern flow. | | `lcss.mls` | Function result has no interface for `toString`. | The remaining WASM fixture, `wasm/Wasm.mls`, needs interfaces for `WebAssembly.Instance.exports` and its exposed `wasmInst.imports` receiver. -## Straightforward fixes applied - -1. Named record/tuple fields elaborate their values with the enclosing - interpretation. `value: expression` inside a term tuple is not a type - annotation. Tuple shapes collect these values into one trailing record, matching - lowering while retaining source evaluation order, last-write-wins lookup, - computed-key uncertainty, and property identities across imports. Structural - record annotations expose only their declared fields and support generic - substitution and callback interfaces. `newres/NamedFields.mls`, - `ImportedNamedFields.mls`, and `RecordInterfaces.mls` cover these behaviors; - `basics/NamedArgs.mls` also exercises the existing runtime convention. -2. Compiler-generated `runtime.assertFail` calls use a synthetic selection, like - other runtime helpers. This is not a fallback for unresolved source selections. - `basics/Assert.mls` and `DisruptiveComments.mls` retain runtime assertion tests. -3. Free-variable collection recognizes the new direct reference forms. Pattern - guards no longer produce spurious unused-binding warnings; see - `ucs/patterns/GuardedPatternBindings.mls`. -4. Type applications propagate the underlying shape, preserving the requested - interpretation and capture handling. Class validation and lowering look - through erased type arguments. `newres/TypeApplications.mls` checks generic - construction, inheritance, and function-result member lookup. Generic substitution - and constraint flow are now implemented as described in task 3; type-argument - validation is still incomplete (`basics/GenericClasses.mls`). -5. Pattern compilation annotations forward binding-shape propagation to the - annotated pattern. Remaining pattern forms and matcher synthesis are separate - work, described below. -6. Argument matching retains the original parameter and argument counts for - diagnostics. A two-argument call with one supplied argument no longer reports - the counts of the unmatched tails. -7. The assertion test explicitly constructs `new Error(...)`. An invalid - selection from a declared class now expects its compilation error in - `basics/Declare.mls`. - -One intended semantic change is visible in `codegen/ImportConflicts.mls`: an -explicitly bound `Some5` wins over a wildcard open, yielding `"555"` rather than -`5`. That follows the already-agreed wildcard-open rule. - -## Design questions and proposed decisions - -### 1. Structural members and opaque values - -Record fields now have their own `BlockMemberSymbol`s. Member lookup follows -record overwrites and spreads in source order and retains the selected value's -shape and captures. Tuples retain precise zero-based element projections and expose -the builtin Array class as their parent. This supports inherited members, Array -patterns, and inference of Array element type arguments. The parent uses the union -of element shapes, preserving both annotations and call-context marks. - -The prelude now declares Array's common instance methods and their result -interfaces, and identifies `Array.prototype` as an Array value. Callback parameter flow supports rest parameters receiving the -remaining arguments; Array callbacks receive the element, index, and array. -Imported legacy signatures preserve substituted parameters across the member's -lexical boundary. Mutable arrays discard their initial element shapes and length, -because methods such as `push` and `reverse` can change both. Unknown layouts still -permit indexed access with an unknown result, without authorizing arbitrary members. - -Generic methods use the same marked parameter flow as generic free functions, -including explicit arguments, callback-result inference, and curried signatures. -`Array.map[U]` returns `Array[U]`. Declared callable shapes retain their type -parameters. `NewResolverState` owns the inference graph and caches. Listeners have -type `TermShape => NewResolverState ?=> Unit`, so imported callbacks resolve hosts -through the consuming state. Importing touches only the accessed hosts: their -candidates and listeners are copied lazily, and caches consult individual source -entries on demand. Type-parameter references retain their originating host, so -exporter-private inference on third-party parameters survives re-exporting without -copying or enumerating whole resolver maps. Legacy annotation interpretations -also stay local instead of attaching consumer callbacks to shared prelude syntax. -Completion seals each file or diff-test block's recorded decisions and the original -host data read by erasure/lowering. Symbol flow and elimination listeners remain -active against private host data; they cannot change completed reference targets, -even between blocks sharing the same elaborator state. - -`newres/GenericMethods.mls` now covers exported mapped arrays and imported generic -functions and methods with inferred results. Compiler-cache tests check independent -consumers and unchanged source candidates, listeners, and legacy annotations. -Publisher tests cover cycles, reentrant replay, transitive private inference, and -bounded copying in the presence of unrelated exporter hosts. -The external-identity case in `newres/NestedModules.mls` also passes. A remaining -`:fixme` records a capture assertion when a map callback reads a constructor field -in a module initializer, before importing comes into play. - -Array declarations remain incomplete: `concat` returns `Array[Any]` because its -rest arguments currently have no declared element constraint. This does not infer -result precision from an opaque annotation. - -**Accepted policy:** JavaScript imports (including package imports), `globalThis`, -and explicit dynamic selection/instantiation introduce `DynShape`. Ordinary -selections and calls on these values are checked at runtime and yield dynamic -values. `foo() as dyn` explicitly gives a result this behavior; `fun bar(x: dyn)` -provides it to a parameter independently of call-site inference. Type aliases may -also denote `dyn`. These rules apply in both strict and non-strict resolution. -Dynamic instantiation uses the existing `new!` syntax. - -Before sealing a file, `InterfaceExposure` checks the values reachable through its -exposed interface. Diff-test/worksheet blocks run this check only under strict -resolution; non-strict blocks retain local inference without flooding their public -definitions. The pass seeds unannotated parameters with `UnknownValueShape`, then -follows returned functions, record/tuple contents, and public instance members. -Local examples do not close an exposed interface: `fun foo(x) = x.a` is rejected -in a strict block even if that block calls `foo({a: 1})`. Private helpers can retain -local inference unless their function values escape. -Parameter annotations provide their declared shapes; callable result annotations -constrain returned implementations through their declared domains. - -Generic opacity is independent of interface exposure, visibility, and strict mode. -Each generic definition is checked under a fresh abstract activation when its type -parameters are declared. Thus `private fun foo[A](x: A) = x.a` is also rejected, -even without callers or when every local instantiation supplies a type with `a`. -The diagnostic identifies the type parameter, without an exposure hint. The -abstract activation cannot match a concrete caller's exit, so inferred and explicit -type arguments still substitute into results such as `identity[A](x: A): A = x`. -Coverage includes strict and non-strict `InterfaceExposure.mls` tests. - -Discovery uses a queue and temporary observers of shape publishers. Each reached -shape is processed once, with listeners discovering subsequent candidates; there -is no repeated whole-graph scan. Observers are detached before the unit is sealed -and are never copied into consumer states. New-resolution imports have already had -their interfaces checked; the importer does not recursively recheck definitions -from other compilation units. -An unknown reaching a previously sealed elimination can still report an error, -without changing its recorded target. - -Unknown values and unresolved spreads carry shared, lazy provenance. Messages, -locations, and diagnostic chains are materialized only on an error; provenance is -excluded from shape equality. Diagnostics can identify a generic parameter or -ordinary parameter and then show the record/tuple storage and return steps that -expose it. `newres/InterfaceExposure.mls` covers these paths, recursive discovery, -private helpers, returned typed closures, overloaded class exports, and later -exposure of a private definition from an earlier block. Constructor patterns -narrow unknown inputs to declared class interfaces, retaining unknown type -arguments and provenance for unannotated fields. They cannot discard the external -alternative and specialize those fields from local calls alone. - -Flooding also reveals existing limits of capture precision. Forwarding through -captured functions or constructor aliases, rebuilding an instance of the same -class, and exposing partially applied constructors can lose the originating -activation. These cases conservatively report unknown-value errors and remain -explicit `:fixme` regressions in `CtxSens`, `ValCtxSens`, `Projections`, -`RecursiveEnvironment`, and `SpreadCalls`; they need a more precise representation -of captured activations. Unknowns are not converted to dynamic values to bypass -these failures. Unsupported inference forms can still have no candidates at all; -completion of such empty flows remains separate from rejecting an unknown target. - -An additional gap is recorded in `InterfaceExposure.mls`: a private class returned -through a nominal result annotation can expose callable methods without those -implementations being flooded. Fixing this requires following the declared member -interface back to its implementations, while still keeping unannotated fields and -results opaque; traversing every initializer would incorrectly expose values the -annotation hides. - -Dynamic selections have no fabricated nominal definition. Lowering retains their -runtime receiver and property name. Dynamic values propagate through record and -tuple spreads and unique wildcard opens. Competing wildcard-open receivers still -require disambiguation: choosing one would change which runtime object is read. -Class projections, constructor patterns, and type references also retain their -requirements for a known, unambiguous identity. - -`DynShape` is distinct from `UnknownValueShape`: recursive widening, mutable -record reads, and other losses of inference precision do not automatically -license dynamic member lookup or calls. A known missing member in another receiver -candidate still reports an error even if a dynamic candidate is also present. -The same rule applies to values recovered from constructor patterns. - -Regression coverage is in `newres/Dynamic.mls`, `newres/Records.mls`, -`newres/SpreadCalls.mls`, `newres/Arrays.mls`, and `newres/loose/Targets.mls`. - -### 2. Pattern transfer and synthesized references - -Evidence: new shape propagation lacks record, negation, string -concatenation, and transformation cases. Examples include -`ucs/general/BooleanPatterns.mls`, `patterns/String.mls`, and -`normalization/RecordImpliedByClass.mls`. Tuple bindings now propagate shapes, -including middle spreads and declared Array element types. `CompiledQualifiedConstructors.mls` and `CompiledClassPatterns.mls` now -pass and are migrated, as are most UPS fixed-point, recursive, and parametric -cases. Remaining failures include unsupported pattern forms, patterns used as -terms (for example `.unapply`), and synthesized selections entering new resolution -in `ucs/examples/EitherOrBoth.mls`. - -**Question:** what resolution state should synthesized matcher references share -with source references, and what precision should recursive/transformed pattern -bindings promise? - -**Proposal:** retain the source reference's completed target and receiver path -when lowering synthesizes matcher code; use the existing `Lowering` capability -to obtain that result. Do not clone listener hosts with empty candidate sets or -re-run name lookup from the generated tree. Add explicit pattern transfer rules -that distinguish matched input shapes, bound-field shapes, and transformed -output shapes. Compose alternatives by union and conjunctions by successive -filtering; negation must not invent positive bindings. Reuse a dependency graph -with cycle-aware subscriptions for recursive patterns. Establish convergence -before trying to fix recursive cases by adding recursion limits. - -Acceptance cases: bound members after tuple/record extraction, nested constructors, -aliases, guards, transformed results, repeated references, qualified imported -constructors, and recursive UPS matchers. Compare results and generated matcher -structure with the existing tests. `ups/examples/HindleyMilner.mls` now finishes -its new-resolution trial, but retains unsupported member and pattern cases -(see the recheck below). - -### 3. Declared interfaces versus inferred values - -Evidence: the prior WASM `Basics` failure mixes a nominal annotated receiver with -an untyped constructor field carrying a different capture context. -`codegen/CurriedClassInheritance.mls` now migrates after updating its existing -negative expectations; `ParamClasses.mls` still exposes constructor-value member -lookup rather than a remaining annotation-opacity decision. - -**Implemented:** annotations are opaque shapes. `b: Base` exposes Base's declared -members and preserves virtual dispatch; an implementation or observed argument -cannot add subclass members. Parameters, result annotations, separate signatures, -and constructor fields use the same declared interfaces, even without observed -calls. Reading an unannotated member through such an interface yields an unknown -shape instead of following its initializer or method body. - -Type interpretations retain arrows, type arguments, and lexical captures. Generic -aliases and inherited declared interfaces substitute their type arguments. Explicit -and inferred function/constructor type arguments flow through the corresponding -type-parameter symbols with entry/exit marks; explicit arguments prevent further -refinement from value arguments. Inference also connects parameters through nested -nominal arguments, as in `Foo[A]` containing a `Box[A]`. Declared member selections -carry those type-argument contexts without reading implementation value flow. - -`newres/DeclaredTypes.mls` covers unused annotated functions, unannotated fields, -subclass overrides, inherited fields, curried constructors, nested captures, -generic aliases, separate signatures, and distinct generic instantiations. Its -function/tuple-constraint example now passes: callback parameter types flow into -implementation parameters, callback results constrain inferred type arguments, -and calls through declared signatures contribute argument constraints. Tuple -types retain their element interfaces, and zero-based projections preserve -capture marks through tuple values, declared types, and spreads. Mutable tuple -reads do not reuse initializer shapes. Distinct callback instantiations and -opaque annotated tuple elements have regression coverage. - -WASM tuple reads and writes pass, using indexed lowering. The complete callback -example and calls through tuple elements remain explicit WASM `:fixme` cases: -that backend still lacks the required first-class function representation and -anonymous-function lowering. REPL method cases remain assigned to the other branch. - -The original annotated-receiver cases from WASM `Basics` now pass and are covered -by `newres/wasm/DeclaredTypes.mls`. A fresh trial reaches a later capture assertion -in the unannotated initializer `class Foo(val x) with val y = this.x`. -`CurriedClassInheritance` now expects the earlier compilation errors as well as -its existing runtime errors. `ParamClasses` still reaches unimplemented -constructor-value member lookup (`Foo.class`); it and WASM `Basics` remain deferred. - -### 4. Dynamic construction and foreign callable classes - -`codegen/ImportJSClass.mls` now passes with dynamic construction and is migrated. -`basics/DynamicInstantiation.mls` remains blocked by unimplemented member lookup -in `DefnShape.getMemberImpl` for `new! C.class(1, 2)`; its previous class-lowering -failure is no longer the first observed blocker. -`PredefUsage.mls` calls JavaScript's `String` as a function, while its current -declaration supplies only a module interpretation. Bare MLscript classes also -reject old empty-call syntax, as intended by the earlier migration. - -**Question:** how should foreign declarations expose both call and constructor -capabilities, without making every module or bare class implicitly callable? - -**Proposal:** give foreign declarations explicit callable/constructible -capabilities using the existing overload interpretation mechanism. Explicitly -dynamic construction already lowers to dynamic IR; keep static `new` on the -listener-resolved class path. -Change test syntax only where that preserves the test's intent; tests explicitly -about callable JS constructors must not be rewritten to avoid that feature. - -Acceptance cases: imported JS classes, callable-and-constructible externals, -plain MLscript classes, companion values, dynamic constructor expressions, and -errors when a selected interpretation lacks the required capability. - -## Remaining implementation work, not new semantic decisions - -### Rechecked generator and Hindley–Milner failures - -On the merged tree, `codegen/Generators.mls` finishes its new-resolution trial -in about 1.3 seconds. Its earlier timeout was not reproduced. The primary -failure is unresolved `.next`: `NewResolver.appShape` follows a generator's body -as if the call returned its ordinary result, whereas lowering produces a -JavaScript generator object. The small `:fixme` regression -[`newres/GeneratorResults.mls`](../hkmc2/shared/src/test/mlscript/newres/GeneratorResults.mls) -shows this without `yield`: a generator returning `1` makes resolution reject -`.next` as a member of an integer literal. Generator calls need their own result -shape and iterator-member behavior; annotation recognition already works. - -`ups/examples/HindleyMilner.mls` now finishes a new-resolution trial with JavaScript -disabled in about 1.4 seconds. It still reports unsupported member-variable shapes -and pattern/member errors, so the original worksheet retains its existing mode. -The recursive environment regression -[`newres/RecursiveEnvironment.mls`](../hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls) -now resolves and executes, including actual recursive calls, explicit `new`, -nested class captures, wildcard opens, and partial construction. - -The unbounded marks came from a missing lexical boundary: a method's reference to -an outer constructor captured the method scope but skipped its enclosing instance. -Class bodies now introduce captures, and a class and its constructor share one -resolution boundary. Consuming a reconstructed instance cancels the old instance -exit against its capture, retaining the fresh constructor exit. Explicit `new` -uses the same constructor context, including for arguments and later parameter -lists. Context fragments compose from the definition to its consumer; argument -flow traverses that composition in reverse. Optional debugging assertions detect -repeated boundaries in either direction; the private `checkMarkPaths` flag disables -them by default because scanning each new mark's tail makes chain construction -quadratic in its depth. There is no truncation or depth limit. -These changes also fix the sibling-subclass field-extraction regression in -`newres/ConstructorFieldRecovery.mls`, whose independent results are now checked. - -### What the latest trials now reach - -- `basics/MiscArrayTests`: `reverse` and the correctly shaped `map` callbacks work. - Its unary callback now fails statically, as well as at runtime; the later `map` - function still needs parameter flow from a subsequent REPL block. -- `codegen/FirstClassFunctionTransform`: tuple-held calls work, making three old - error expectations obsolete, but later closure cases still report an unfinished - `foo` definition. The worksheet is not migrated by merely removing expectations. -- `codegen/SetStmt`: the first `concat` resolves; update-assignment argument flow - and reassigned aggregate shapes remain incomplete. -- `codegen/ObjectMethodDebinding`: `id(Test).foo` still has no resolved target. - Adding an expected error would not establish the intended debinding diagnostic. -- Imported application results still lack the required interfaces: CSV's nested - tuple selection and Iter's `reverse` remain unresolved; the parser worksheets - now pass after adding their required interfaces. Adding Array members cannot manufacture missing receiver shapes. - -### Other implementation work - -- Audit assignments to member symbols and definition initializers. The trial - reaches unimplemented direct member-reference shape cases and missing - assignment lowering in several mutation tests. -- Add transfer rules for result-bearing control flow, handlers, and quotation - where the resolver currently has no shape rule. Preserve the established - capture model and the boundary between elaboration and lowering. -- Review module-forwarding compatibility in `basics/CyclicModuleForwarders.mls`. - The latest trial no longer overflows the stack; it rejects forwarding between - the distinct nominal module types `M1` and `M2`. Do not discard its old expected - outcomes without deciding the intended module compatibility rule. -- Preserve source origins in synthesized references and diagnostics. Some - migrated error cases now report a definition rather than a use site, or lose - a location. In particular, `ups/UpsBugsBacklog.mls` and - `ups/syntax/MixedParameters.mls` retain their expected undefined-binding errors - but lose the use-site location. These need attention without changing the - chosen symbol. - -## Execution order and completion gates - -1. **Retain this measured batch.** Run compilation fixtures, main difftests, - application difftests, and the aggregate suite. Review output changes; keep - nonmigrated files intact. WASM `Basics` retains its previous configuration - pending its remaining unannotated-initializer capture fix. -2. **Model generator results.** Recursive constructor contexts now normalize - through lexical captures. Use `GeneratorResults` to specify iterator results rather - than propagating the generator body's return shape. The earlier generator - timeout was not reproduced. -3. **Complete structural targets and external signatures.** Dynamic JS values and - record fields, tuple projections, and inherited Array members are supported; - finish mutation flow, primitive members, and foreign declarations in small batches. Re-run affected negative tests as - well as successful programs. -4. **Complete pattern transfers and reference preservation.** Start with UCS - conjunction/record cases, then the remaining recursive and transforming - UPS cases. Keep fixed-point behavior tested independently from matcher code - generation. Preserve the now-passing constructor-field context regressions. -5. **Complete type/interface and call validation.** Preserve opaque annotations - and marked generic flow; finish generic validation and contextual argument insertion, - module checks, and the WASM migration. Keep cross-block method work coordinated externally. -6. **Continue the audited compilation-fixture migration.** Port `LegacyOption` - consumers and remove the temporary copy. Resolve the legacy-consumer signature - boundary for `Token` and `Keywords`; `BasicExpr` and `Expr` are ported. Add - exposed interfaces and complete the pattern/host-interface gaps listed above. - Rebuild `.mjs` dependencies and run existing consumers for each batch. -7. **Remove the remaining legacy suite configurations.** Require no new failure - suppressions, no lost negative diagnostics, reviewed goldens, and a successful - `hkmc2AllTests/test`. Commit code and golden outputs together under the agent's - identity once that gate is met. - -## Validation - -- `ctest`: all 49 selected compilation tests pass. -- `catest`: all 20 application compilation fixtures pass with the retained mixed-mode ports. -- `adtest parsing`: all 14 selected tests pass, including all twelve parser worksheets. -- `cntest`: all 39 Nofib compilation fixtures pass. -- `cwtest`: the WASM compilation fixture passes. -- `hkmc2AllTests/test`: all 969 tests pass, including 720 main tests, 18 application - tests, and 22 WASM tests. The main count includes directory configurations and - new-resolution regressions; it is not a count of migrated worksheets. -- All retained ports have reviewed goldens. Legacy consumers that require the - compatibility option constructors import `LegacyOption`; reflection uses that - same copy as `Block.mls`. No trial-generated failures remain in golden outputs. - -## Deferred-file inventory - -The following are the first observed worksheet-trial failures, which may be -symptoms rather than root causes. This inventory predates the compilation-fixture -batch above; its overload and primitive-member fixes may unblock additional -worksheets, which have not been re-audited in this batch. Each path is relative to `hkmc2/shared/src/test/mlscript/`. -The files themselves retain the pre-trial configuration and output. - -### basics - -- `basics/BadAssignments.mls`: Unexpected exception; scala.NotImplementedError: SelfRef(globalThis:globalThis) (of class SelfRef) -- `basics/BadModuleUses.mls`: Unexpected lack of compilation or type error after `module Example with` -- `basics/BadOverloading.mls`: Unexpected lack of compilation or type error after `class Foo` -- `basics/BadTypeClasses.mls`: Unexpected lack of compilation or type error after `M.f` -- `basics/Classes.mls`: Unexpected compilation error; This selection of member 'id' has no resolved target -- `basics/CompanionModules_Classes.mls`: Unexpected compilation error; Resolution error in application; Module 'C' cannot be called like a function. -- `basics/CompanionModules_Functions.mls`: Unexpected compilation error; Resolution error in application; Module 'foo' cannot be called like a function. -- `basics/CyclicModuleForwarders.mls`: Unexpected compilation error; Cannot use a value of type 'module M1' at an unrelated type 'module M2' -- `basics/DynamicFields.mls`: Unexpected compilation error; Resolution error in selection; Instance of class constructor 'DynCtor' does not contain member 'dynField' -- `basics/DynamicInstantiation.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) -- `basics/FunDefs.mls`: Unexpected compilation error; Resolution error in application; Value of type 'Int' cannot receive more argument lists. -- `basics/GenericClasses.mls`: Unexpected lack of compilation or type error after `class Foo[A](x: Foo[A, A])` -- `basics/Inheritance.mls`: Unexpected lack of error to fix after `[Bar.x, Bar.foo]` -- `basics/MiscArrayTests.mls`: Unexpected compilation error; Resolution error in function type; Callback parameter list does not match its declared function type. -- `basics/ModuleMethods.mls`: Unexpected lack of compilation or type error after `fun f(m: M)` -- `basics/MultiParamListClasses.mls`: Unexpected compilation error; Resolution error in application; Instance of class constructor 'Foo' cannot receive more argument lists. -- `basics/MutRcd.mls`: Unexpected compilation error; Resolution error in selection; Record literal does not contain member 'foo' -- `basics/MutVal.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `basics/New.mls`: Unexpected lack of warnings after `new` -- `basics/NewMut.mls`: Unexpected compilation error; Resolution error in selection; Instance of class 'Foo' does not contain member 'y' -- `basics/NewlineOps.mls`: Unexpected compilation error; This selection of member 'length' has no resolved target -- `basics/NewlineSels.mls`: Unexpected compilation error; This selection of member 'c' has no resolved target -- `basics/OpenIn.mls`: Unexpected compilation error; Builtin '~' is not a binary operator -- `basics/Overloading.mls`: Unexpected compilation error; Resolution error in application; Module 'Foo' cannot be called like a function. -- `basics/PrefixOps.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Bin' -- `basics/Puns.mls`: Unexpected exception; scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(x))))) (of class Record) -- `basics/Records.mls`: Unexpected compilation error; Resolution error in selection; String literal does not contain member 'repeat' -- `basics/StrTest.mls`: Unexpected lack of compilation or type error after `(~)("a")` -- `basics/Underscores.mls`: Unexpected compilation error; This selection of member 'f' has no resolved target -- `basics/ValMemberSymbols.mls`: Unexpected compilation error; This selection of member 'x' has no resolved target - -### codegen - -- `codegen/AuxiliaryConstructors.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `codegen/BadOpen.mls`: Unexpected compilation error; Resolution error in selection; Module 'Foo' does not contain member 'y' -- `codegen/ClassMatching.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) -- `codegen/ConfigDirective.mls`: Unexpected compilation error; This selection of member 'call' has no resolved target -- `codegen/ConsoleLog.mls`: Unexpected compilation error; This selection of member 'log' has no resolved target -- `codegen/CurriedClasses.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) -- `codegen/ErasedTypes.mls`: Unexpected compilation error; Resolution error in application; Value of type 'Function' cannot be called like a function. -- `codegen/FirstClassFunctionTransform.mls`: Unexpected lack of compilation error after `bar([foo].0)` -- `codegen/Generators.mls`: Unexpected compilation error; This selection of member 'next' has no resolved target -- `codegen/Getters.mls`: Unexpected compilation error; This selection of member 'oops' has no resolved target -- `codegen/Hygiene.mls`: Unexpected compilation error; Resolution error in selection; String literal does not contain member 'foo' -- `codegen/ImportExample.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `codegen/ImportedOps.mls`: Unexpected compilation error; Builtin '~' is not a binary operator -- `codegen/ModuleMethods.mls`: Unexpected lack of compilation or type error after `Example |>. s(123)` -- `codegen/Modules.mls`: Unexpected compilation error; Resolution error in application; Module 'None' cannot be called like a function. -- `codegen/NestedClasses.mls`: Unexpected compilation error; This selection of member 'x' has no resolved target -- `codegen/NoFreeze.mls`: Unexpected compilation error; This selection of member 'x' has no resolved target -- `codegen/NoModuleCheck.mls`: Unexpected lack of compilation or type error after `M."foo"(1)` -- `codegen/ObjectMethodDebinding.mls`: Unexpected compilation error; This selection of member 'foo' has no resolved target -- `codegen/Open.mls`: Unexpected compilation error; Resolution error in selection; String literal does not contain member 'length' -- `codegen/OpenWildcard.mls`: Unexpected compilation error; Wildcard-open reference 'None' is ambiguous; candidate: object 'None' defined here -- `codegen/ParamClasses.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.DefnShape.getMemberImpl`) -- `codegen/PartialApps.mls`: Unexpected compilation error; Resolution error in application; Tuple literal cannot receive more argument lists. -- `codegen/PredefUsage.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot be called like a function. -- `codegen/PrivateMembers.mls`: Unexpected compilation error; This selection of member 'y' has no resolved target -- `codegen/Pwd.mls`: Unexpected compilation error; This selection of member 'pop' has no resolved target -- `codegen/Quasiquotes.mls`: Unexpected compilation error; Unsupported quasiquote type member reference -- `codegen/RandomStuff.mls`: Unexpected exception; java.lang.StackOverflowError -- `codegen/ReboundLet.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `codegen/RuntimeUsage.mls`: Unexpected compilation error; Resolution error in selection; Class value does not contain member 'leaveOut' -- `codegen/SanityChecks.mls`: Unexpected compilation error; Resolution error in application; Function literal expected 2 arguments, but got 1 -- `codegen/ScopedBlocks.mls`: Unexpected compilation error; Resolution error in application; Module 'Foo' cannot receive more argument lists. -- `codegen/ScopedBlocksAndHandlers.mls`: Unexpected exception; shape propagation for handler expressions is unimplemented. -- `codegen/Scoping.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `codegen/SelfReferences.mls`: Unexpected lack of compilation or type error after `module Foo with` -- `codegen/SetStmt.mls`: Unexpected compilation error; This selection of member 'concat' has no resolved target -- `codegen/ThisCallVariations.mls`: Unexpected compilation error; This selection of member 'call' has no resolved target -- `codegen/ThisCalls.mls`: Unexpected lack of compilation or type error after `Example |>. g(123)` -- `codegen/While.mls`: Unexpected compilation error; Resolution error in application; Integer literal cannot be called like a function. - -### ucs - -- `ucs/examples/BinarySearchTree.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot receive more argument lists. -- `ucs/examples/EitherOrBoth.mls`: Unexpected exception; java.lang.Exception: Internal Error: Synthetic selections must not enter new resolution -- `ucs/examples/LeftistTree.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot receive more argument lists. -- `ucs/examples/ListFold.mls`: Unexpected compilation error; Builtin '~' is not a binary operator -- `ucs/examples/ULC.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot receive more argument lists. -- `ucs/general/BooleanPatterns.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Negation(Literal(IntLit(2))),Negation(Literal(IntLit(3)))) (of class Composition) -- `ucs/hygiene/HygienicBindings.mls`: Unexpected compilation error; Resolution error in application; Class value cannot receive more argument lists. -- `ucs/normalization/Deduplication.mls`: Unexpected compilation error; This selection of member 'length' has no resolved target -- `ucs/normalization/InheritanceNormalization.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:B),None)) (of class Composition) -- `ucs/normalization/OverlapOfPrimitives.mls`: Unexpected compilation error; Resolution error in application; Module 'String' cannot receive more argument lists. -- `ucs/normalization/RecordImpliedByClass.mls`: Unexpected exception; scala.NotImplementedError: Record(List((Ident(a),Alias(Wildcard(),Ident(av))), (Ident(b),Alias(Wildcard(),Ident(bv))))) (of class Record) -- `ucs/patterns/BooleansOps.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Alias(Wildcard(),Ident(a)),Alias(Wildcard(),Ident(b))) (of class Composition) -- `ucs/patterns/ConjunctionPattern.mls`: Unexpected exception; scala.NotImplementedError: Composition(false,Composition(false,Constructor(MemberRef(member:A),None),Constructor(MemberRef(member:A),None)),Constructor(MemberRef(member:B),None)) (of class Composition) -- `ucs/patterns/RecordPattern.mls`: Unexpected exception; scala.NotImplementedError: Record(List((Ident(x),Alias(Wildcard(),Ident(a))), (Ident(y),Alias(Wildcard(),Ident(b))))) (of class Record) -- `ucs/patterns/String.mls`: Unexpected exception; scala.NotImplementedError: Concatenation(Literal(StrLit(0x)),Alias(Wildcard(),Ident(body))) (of class Concatenation) -- `ucs/patterns/where.mls`: Unexpected compilation error; This selection of member 'get' has no resolved target -- `ucs/syntax/Else.mls`: Unexpected compilation error; Resolution error in selection; Instance of class 'Set' does not contain member 'has' -- `ucs/syntax/SimpleUCS.mls`: Unexpected compilation error; This selection of member 'get' has no resolved target - -### ups - -- `ups/EmptyJunctions.mls`: Unexpected exception; scala.NotImplementedError: Range(IntLit(5),IntLit(3),true) (of class Range) -- `ups/Future.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Test' -- `ups/LocalPatterns.mls`: Unexpected compilation error; Resolution error in selection; Expected a term; got pattern 'Zero' -- `ups/MatchResult.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Cross' -- `ups/RecursiveTransformations.mls`: Unexpected exception; scala.NotImplementedError: Negation(Constructor(Capture(MemberRef(member:Bin),term:f),None)) (of class Negation) -- `ups/SimpleTransform.mls`: Unexpected compilation error; Resolution error in application; Function 'area' expected 1 argument, but got 2 -- `ups/examples/BasicSeqStackParse.mls`: Unexpected compilation error; This selection of member 'at' has no resolved target -- `ups/examples/Computation.mls`: Unexpected compilation error; This selection of member 'padStart' has no resolved target -- `ups/examples/DoubleTripleList.mls`: Unexpected compilation error; This selection of member 'head' has no resolved target -- `ups/examples/EvaluationContext.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `ups/examples/EvaluationContext2.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `ups/examples/HindleyMilner.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `ups/examples/ListPredicates.mls`: Unexpected compilation error; This selection of member 'length' has no resolved target -- `ups/examples/Negation.mls`: Unexpected compilation error; Resolution error in application; Function 'flatten' expected 1 argument, but got 3 -- `ups/examples/PrecedenceClimbStackParse.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'ParseStep' -- `ups/examples/Record.mls`: Unexpected compilation error; Resolution error in application; Function 'bmi' expected 1 argument, but got 2 -- `ups/fixpoint/ListFusion.mls`: Unexpected compilation error; This selection of member 'toString' has no resolved target -- `ups/regex/Identifier.mls`: Unexpected compilation error; This selection of member 'forEach' has no resolved target -- `ups/regex/Separation.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Integer' -- `ups/regex/TailRepetition.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'Zero' -- `ups/syntax/InterestingPatterns.mls`: Unexpected compilation error; Resolution error in member reference; Expected a term; got pattern 'TreeDepth' -- `ups/syntax/PatternBody.mls`: Unexpected compilation error; This selection of member 'toString' has no resolved target - -### apps - -- `apps/AccountingTest.mls`: Unexpected exception; scala.NotImplementedError: an implementation is missing (`semantics.NewResolver.listen`) -- `apps/CSVTest.mls`: Unexpected compilation error; This selection of member '1' has no resolved target -- `apps/IterTest.mls`: Unexpected compilation error; This selection of member 'reverse' has no resolved target -- `apps/parsing-web-demo/ExamplesTest.mls`: Unexpected compilation error; This selection of member 'sort' has no resolved target - -### wasm - -- `wasm/Basics.mls`: Unexpected exception; java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes -- `wasm/Binaryen.mls`: Unexpected compilation error; Resolution error in selection; Instance of class 'Instance' does not contain member 'exports' +## Validation and completion gates + +Follow the [repository test workflow](../README.md#running-the-tests-1): + +1. Rebuild compilation fixtures before running their worksheet consumers: `ctest` + for main fixtures, `catest` for applications, `cntest` for Nofib, and `cwtest` + for WASM. In particular, runtime tests need the generated `.mjs` dependencies. +2. Run affected consumers, including negative cases, and review golden changes. + Keep nonmigrated files intact; do not retain trial-generated failures. +3. Run `hkmc2AllTests/test` for each retained batch. Commit intentional golden + updates together with the change, using the agent's identity for agent commits. +4. Remove legacy suite configurations only after all covered files migrate, + without new failure suppressions or lost negative diagnostics. Remove + `LegacyOption` once its consumers use the new interfaces consistently. + +Update the counts and blocker inventory after each retained batch. Test totals +include configurations and regression tests, so they are not migration counts. diff --git a/doc/new-resolution-wasm.md b/doc/new-resolution-wasm.md index 61df50a66a..8baecd4dc0 100644 --- a/doc/new-resolution-wasm.md +++ b/doc/new-resolution-wasm.md @@ -1,68 +1,41 @@ +# New resolution and WASM -## Initial WASM migration milestone (`8e66dbefd`) +The [migration worklist](new-resolution-suite-migration.md) tracks remaining ports. +The general [resolver invariants](new-resolution-design.md) also apply to WASM. -The shared `mlscript/wasm/.mls` enables new resolution, strict resolution, and -WASM execution while disabling JS execution. Migrated tests start with `:.`, -before any blank line, so these commands apply throughout the file. No -`:global` is needed. The synthetic Predef import now explicitly targets JS, -and WASM emission checks the compilation target as well as `:wasm`; this avoids -sending the host-side bootstrap import to WASM when the option appears at the -very top of a file. `newres/wasm/FileWideFlags.mls` covers that placement across -two blocks. `Binaryen.mls` is unchanged. +## Configuration and lowering contracts -Eight existing files migrate without compiler design changes: `ClassInheritance`, -`Exceptions`, `MainFunctions`, `Matching`, `ScopedLocals`, `SingletonUnit`, -`Singletons`, and `Strings`. +`hkmc2/shared/src/test/mlscript/wasm/.mls` inherits the common non-strict +`#lang(0.3.x)` configuration and enables WASM execution. Migrated worksheets +start with `:.` before any blank line so the flags apply throughout the file; +no `:global` is needed. The common language configuration disables JS execution. -The remaining eleven files were tried with the shared configuration and failed. -Their original configurations and snapshots are retained, rather than marking -the migration failures as expected. The main remaining work is deferred type interpretation: +The synthetic Predef import explicitly targets JS. WASM emission checks the +compilation target as well as `:wasm`, so placing the directive at the top of a +file does not send the host-side bootstrap import to WASM. +`newres/wasm/FileWideFlags.mls` covers this across two blocks. -- `NumericTypes` and `BuiltinOperators`: annotations such as `Int64` and `Int32` - are elaborated as new references, but `ErasedType.eraseSign` still queries - legacy `sign.symbol` eagerly. Declared erased types become unknown, causing - primitive/reference representation conversion errors. Type interpretation must - resolve before erased signatures are consumed, including forward definitions, - aliases, and wildcard names; replacing this query with another eager lookup - would not establish that contract. -- `TailRecOptCasts`: the separately compiled declaration - `fun useBox(b: Box): Int = b.v` has no resolved target for `v` before any call. - Declared parameter types should publish instance shapes through the existing - listener-based resolution infrastructure, independently of calls. +Type interpretation must finish before erased signatures are consumed, including +forward definitions, aliases, and wildcard names. Eager legacy symbol queries on +new-resolution annotations violate this ordering. Declared parameter types provide +interfaces independently of calls; they do not reveal unannotated field initializer +shapes. `newres/wasm/DeclaredTypes.mls` covers annotated receivers and tuple reads +and writes, which use indexed lowering. -The trial also exposed smaller adaptation gaps: assignment lowering does not -accept `SimpleRef` or captured selection l-values; mutable tuple shape handling -throws an unimplemented-case exception; some old expected-error cases now -succeed. These were not changed after reaching the design boundary. +## Remaining migration and backend gaps -Blocked files: `Basics`, `BuiltinOperators`, `Casts`, `ClassMethods`, `ControlFlow`, -`NumericIntrinsicReview`, `NumericTypes`, `ReplImports`, `TailRecOptCasts`, `Tuples`, -and `VirtualMethods`. Cross-block method handling remains owned by the other -branch. +Nineteen of the 21 existing worksheets use new resolution. The remaining files +retain their legacy configuration and goldens: -## Type interpretation migration in progress +- `wasm/Basics.mls`: the last trial reached a capture assertion in the unannotated + initializer `class Foo(val x) with val y = this.x`. Annotated-receiver regressions + already pass; annotation opacity is a settled rule. +- `wasm/Binaryen.mls`: `WebAssembly.Instance.exports` needs a declared interface. -Ten of the eleven remaining files now use the shared configuration. `Basics` -retains its previous configuration pending the capture fix below. Deferred type -interpretations preserve forward aliases and unboxed signatures, and annotations -publish nominal instance shapes before a function is called. Assignment lowering -accepts the new reference forms; mutable tuples forward their value shapes. -The old empty-call syntax for bare classes and paramless getters has been updated. +The compilation fixture `mlscript-compile/wasm/Wasm.mls` also needs the exports +interface and an interface for its exposed `wasmInst.imports` receiver. -The retained WASM suite passes all 22 files, including the new forward-alias -regression. Trying `Basics` with new resolution exposes a capture-context assertion for: - -```mlscript -class Foo(val x) -fun getX(f: Foo) = f.x -getX(Foo(42)) -``` - -The nominal shape supplied by the annotation knows the field symbol but lacks the -construction context carried by its inferred value. Returning that value tries -to exit `getX` while its pending entry mark belongs to the constructor `Foo`. -This must be addressed in the relationship between declared interfaces and -call-derived value shapes, without bypassing capture invariants. The outstanding -semantic question is whether annotations fix the available member interface or -permit refinement from concrete arguments. This is an intermediate checkpoint; the migration is not complete until that -question and the assertion are resolved. +The callback example and calls through tuple elements in +`newres/wasm/DeclaredTypes.mls` remain expected failures: WASM lacks the required +first-class function representation and anonymous-function lowering. Coordinate +cross-block method changes with the separate implementation work. diff --git a/doc/reference.md b/doc/reference.md index a1b683cfb8..179327e15b 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -517,6 +517,10 @@ data class Some[T](value: T) extends Option[T] object None extends Option[Nothing] ``` +Under new resolution, classes, objects, and modules without an explicit parent +inherit Object's declared member interface. Explicit parents and members declared +on the receiver take precedence. + ### Class with Multiple Parameter Lists ```mlscript @@ -673,6 +677,11 @@ f(a: 0) // calls f({a: 0}) f of a: 0, b: 1 // calls f({a: 0, b: 1}) ``` +With positional and named arguments mixed, positional values are followed by +one record containing the named fields. Expressions still evaluate in source +order, and later occurrences of a named field overwrite earlier ones. +Here `a: expression` is a field value, not a type annotation. + --- ## 10. Arrays @@ -712,6 +721,13 @@ fun g(x, ...rest) = rest f(1, 2, 3) // xs = [1, 2, 3] ``` +Under new resolution, an annotation on a rest parameter describes the whole rest +array. Tuple values support precise zero-based projections such as `xs.0` and +inherit the declared Array interface. Mutable arrays do not retain their initial +length or precise element shapes after mutation. Array callbacks such as `map` +receive the element, index, and array; their declared function interfaces must +account for these arguments, for example with unused or rest parameters. + --- ## 11. The Ultimate Conditional Syntax (UCS) @@ -1272,6 +1288,48 @@ fun f(x: Int): Str = String(x) val v: Option[Int] = Some(1) ``` +### Resolution Interfaces + +With new resolution (`#lang(0.3.x)`), annotations determine the interface available +to member lookup and calls. A receiver annotated `Base` exposes Base's declared +members and preserves virtual dispatch; concrete arguments cannot add subclass +members to that interface. The same rule applies to parameters, result annotations, +separate signatures, and constructor fields. Reading an unannotated member through +an annotated interface does not infer its shape from its initializer or method body. +Structural record annotations likewise expose only their declared fields. + +Compilation files check that their exposed functions and values work for inputs +admitted by their interfaces. A public `fun read(x) = x.a` needs an interface for +`x` even if local callers all provide `{a: 1}`. A private helper can use local +inference, but returning it or storing it in an exposed record or tuple makes its +interface subject to the same check. Callable result annotations provide the +declared parameter interface when checking returned functions. + +Worksheet blocks perform this exposed-interface check only under +`#lang(strictResolution: true)`. Non-strict resolution allows multiple selection +or call targets, but still rejects missing targets and known invalid operations. + +Generic parameters remain opaque regardless of strict mode, visibility, or local +callers. For example, `private fun read[A](x: A) = x.a` is invalid because `A` +does not supply member `a`, even when every call supplies a record with that field. +Explicit and inferred type arguments still substitute into result interfaces: +an identity function with parameter and result type `A` preserves its caller's type. + +### Dynamic Values + +JavaScript imports (including package imports), `globalThis`, and explicit dynamic +selection or instantiation introduce dynamic values. Ordinary selections and +calls on those values are checked at runtime and produce dynamic values. +Use `value as dyn` for an explicit dynamic result or `fun useValue(x: dyn)` +for a dynamic parameter. Type aliases can also denote `dyn`; dynamic construction +uses `new!`. + +These rules apply in both strict and non-strict resolution. A value whose interface +is unknown is not automatically dynamic. If inference includes both a dynamic +candidate and a known candidate with an invalid operation, the known error is +still reported. Dynamic values also do not remove the need for an unambiguous +class or type identity in constructor patterns, class projections, or type references. + ### Generic Type Parameters Covariant, contravariant, invariant: From 237fb3941440851d89ff5ec0c3619683271198b8 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 19:12:37 +0800 Subject: [PATCH 085/228] fix: align new-resolution control-flow results with lowering --- doc/new-resolution-design.md | 10 +++ doc/new-resolution-suite-migration.md | 23 +++-- .../scala/hkmc2/semantics/Elaborator.scala | 7 +- .../scala/hkmc2/semantics/NewResolver.scala | 9 ++ .../src/main/scala/hkmc2/semantics/Term.scala | 1 + .../src/test/mlscript/apps/IterTest.mls | 1 + .../src/test/mlscript/basics/MutVal.mls | 12 +-- .../src/test/mlscript/decls/Prelude.mls | 2 +- .../mlscript/newres/ControlFlowResults.mls | 83 +++++++++++++++++++ .../test/mlscript/newres/HandlerResults.mls | 24 ++++++ .../src/test/mlscript/newres/MutationFlow.mls | 18 ++++ 11 files changed, 174 insertions(+), 16 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ControlFlowResults.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/HandlerResults.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/MutationFlow.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 6d2ad3905a..7f629239dc 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -125,6 +125,16 @@ disambiguation. Class projections, constructor patterns, and type references req known, unambiguous identities. See `newres/Dynamic.mls`, `Records.mls`, `SpreadCalls.mls`, and `loose/Targets.mls`. +## Normal results of control flow + +Resolution must agree with lowering about which expressions produce values. +Assignments, `drop`, imperative conditionals, and loops return unit; their +operands or branch results must not supply the result's member interface. +A normally completed `try` returns its body's value, preserving its capture +marks, while the `finally` clause runs for effects. `throw` and `continue` supply +no normal result. `newres/ControlFlowResults.mls` checks these contracts, including +backtracking assignment and independent calls through cleanup blocks. + ## Overloads, foreign declarations, and lowering Ordinary values select function/constructor overloads; selection receivers select diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index ca23e4d4e3..06a324b93d 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -14,12 +14,12 @@ The checked-in worksheet headers give the following status. Counts exclude share | Suite | Migrated | Legacy configuration | Active files | | --- | ---: | ---: | ---: | -| basics | 60 | 30 | 90 | +| basics | 61 | 29 | 90 | | codegen | 86 | 39 | 125 | | ucs | 52 | 18 | 70 | | ups | 49 | 22 | 71 | -| apps | 12 | 4 | 16 | -| Total | 259 | 113 | 372 | +| apps | 13 | 3 | 16 | +| Total | 261 | 111 | 372 | | wasm (separate backend) | 19 | 2 | 21 | All twelve `apps/parsing` worksheets use new resolution, but some implementation @@ -83,11 +83,18 @@ The inventory below separates these from compiler and prelude gaps. treated as the generator body's return value, leaving `.next` unresolved. Model the iterator result produced by lowering; use `codegen/Generators` as the worksheet acceptance case. -- **Mutation and control flow:** complete assignment/result flow, mutable - aggregate element interfaces, direct member-reference shapes, handlers, and - other result-bearing forms. Check `basics/MutVal`, `codegen/SetStmt`, - `codegen/ScopedBlocksAndHandlers`, and the CSV/Iter application consumers. - Initializer shapes cannot be assumed to survive mutation. +- **Mutation and control flow:** decide how reassignment affects the inferred + interface of mutable storage, including across already compiled worksheet + blocks. `newres/MutationFlow.mls` records a reassigned array still checked + against its initializer's tuple length; accumulating both shapes would still + reject valid later indexing. `codegen/SetStmt` also lacks argument flow through + its update callback. Member-variable definitions and CSV's nested mutable-array + element interfaces need work; initializer shapes cannot be assumed to survive + mutation. Handler inference needs separate flows for the receiver, values + passed to resumptions, and abortive results (`newres/HandlerResults.mls` and + `codegen/ScopedBlocksAndHandlers`). Agree these designs before implementation. + Ordinary result-shape contracts are covered by `newres/ControlFlowResults.mls`; + `basics/MutVal` and `apps/IterTest` use new resolution. - **Captured activations:** preserve activation identity through captured functions, constructor aliases, reconstruction of the same class, and partial construction. Existing regressions include `CtxSens`, `ValCtxSens`, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 6b7631775e..27c1cf1423 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1200,8 +1200,11 @@ extends Importer: val res = new Term.IfLike(kw, form, split).withLoc(loc) if newResolution then // log(s"Listening to if-like split ${res.showDbg}") - split.results.foreach: trm => - pipeTerm(trm, res) + // Imperative branches are evaluated for effects; lowering discards their + // values and returns unit, including when a loop executes zero iterations. + if form.isImperative then pipeTerm(unit, res) + else split.results.foreach: trm => + pipeTerm(trm, res) res diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 0110342831..700fea0569 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -55,6 +55,9 @@ class NewResolver: // without results; compare their identities instead. private def aggregateProducers(using rs: NewResolverState) = rs.aggregateProducers private def defnShapes(using rs: NewResolverState) = rs.defnShapes + // These results carry no value flow or lexical context. Reuse one immutable + // shape so repeated subscriptions do not manufacture distinct unit candidates. + private lazy val unitResultShape = IntroShape(unit, N) /** Interpret types through completed symbolic candidates, independently of term overloads. */ def typeResolution(term: Term)(using rs: NewResolverState): TypeResolution = @@ -1699,6 +1702,12 @@ class NewResolver: sh.replayShapes(listener) case Blk(sts, rs) => listen(rs)(listener) + case Try(body, _) => + // Finally is evaluated for effects; normal completion returns the body's + // value. Its cleanup value must not contribute candidates to this result. + listen(body)(listener) + case _: Assgn | _: Drop => listener(unitResultShape) + case _: Throw | _: Continue => () // These expressions do not complete normally. // case u: UnitVal => case Missing => () // FIXME: Currently get this from light-elaborated Predef import diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 192fb5495f..ce27adc2b2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -874,6 +874,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case Annotated(annotation, target) => "annotation" case Ret(res) => "return" case Try(body, finallyDo) => "try expression" + case _: Handle => "handler expression" case Missing => "missing" case LeadingDotSel(name) => "leading dot selection" case Resolved(t, sym) => t.describe diff --git a/hkmc2/shared/src/test/mlscript/apps/IterTest.mls b/hkmc2/shared/src/test/mlscript/apps/IterTest.mls index a405b396ac..60c648ae22 100644 --- a/hkmc2/shared/src/test/mlscript/apps/IterTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/IterTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/Iter.mls" diff --git a/hkmc2/shared/src/test/mlscript/basics/MutVal.mls b/hkmc2/shared/src/test/mlscript/basics/MutVal.mls index c5eee2532a..e7e381deea 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MutVal.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MutVal.mls @@ -1,3 +1,4 @@ +:. :js @@ -35,9 +36,9 @@ O.gy class Foo(x, val y, mut val z) //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ define Foo⁰ as class Foo² { -//│ val y⁰; +//│ val y¹; //│ val z⁰; -//│ constructor Foo¹(x, y, z) { define y⁰ as val y¹ = y; define z⁰ as val z¹ = z; end } +//│ constructor Foo¹(x, y, z) { define y¹ as val y² = y; define z⁰ as val z¹ = z; end } //│ }; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -48,11 +49,12 @@ class Foo(x, val y, mut val z) with //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ define Foo³ as class Foo⁵ { //│ private val x⁰; -//│ val y²; +//│ val y³; //│ val z²; //│ constructor Foo⁴(x, y, z) { -//│ set Foo⁵.this.x⁰ = x; -//│ define y² as val y³ = y; +//│ @private +//│ define x¹ as val x⁰ = x; +//│ define y³ as val y⁴ = y; //│ define z² as val z³ = z; //│ do Predef⁰.print⁰(Foo⁵.this.x⁰); //│ end diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 2910862356..f971c580d7 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -170,8 +170,8 @@ declare class Array[T](val length: Int): Array[T] with fun includes(value: T, ...from): Bool fun toString(): Str declare module Array with + fun from(...args): Array[Any] fun - from concat isArray val prototype: Array[Any] diff --git a/hkmc2/shared/src/test/mlscript/newres/ControlFlowResults.mls b/hkmc2/shared/src/test/mlscript/newres/ControlFlowResults.mls new file mode 100644 index 0000000000..765a8d0f11 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ControlFlowResults.mls @@ -0,0 +1,83 @@ +:. + +// Backtracking assignment returns its body value and restores the previous value. +:expect [42, 0] +let saved = 0 +let result = set saved = 1 in {answer: 42} +[result.answer, saved] +//│ = [42, 0] +//│ result = {answer: 42} +//│ saved = 0 + +// The finally clause restores storage even when the body calls another function. +:expect [43, 0] +fun answer() = {answer: 43} +let saved = 0 +let result = set saved = 1 in answer() +[result.answer, saved] +//│ = [43, 0] +//│ result = {answer: 43} +//│ saved = 0 + +// Imperative conditionals discard their branch results. +:e +fun answer() = {answer: 42} +let result = if true do answer() +result.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.26: result.answer +//│ ║ ^^^^^^^^^^^^^ +//│ ╙── Unit value does not contain member 'answer' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ result = () + +// Loops also return unit, including when their bodies never execute. +:e +fun answer() = {answer: 42} +let result = while false do answer() +result.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.38: result.answer +//│ ║ ^^^^^^^^^^^^^ +//│ ╙── Unit value does not contain member 'answer' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ result = () + +// Assignment returns unit, independently of the assigned value. +:e +let saved = 0 +let result = set saved = 1 +result.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.50: result.answer +//│ ║ ^^^^^^^^^^^^^ +//│ ╙── Unit value does not contain member 'answer' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ result = () +//│ saved = 1 + +// Discarding a value does not expose its members through the result. +:e +let result = drop {answer: 42} +result.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.62: result.answer +//│ ║ ^^^^^^^^^^^^^ +//│ ╙── Unit value does not contain member 'answer' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ result = () + +// Cleanup preserves call-context marks for independently inferred results. +:expect [42, 43, 0] +let saved = 0 +private fun protectedValue(value) = set saved = 1 in value +[protectedValue({first: 42}).first, protectedValue({second: 43}).second, saved] +//│ = [42, 43, 0] +//│ saved = 0 + +// A throwing branch contributes no normal result shape. +:expect 42 +private fun maybeAnswer(fail) = + if fail then throw new Error("no answer") else {answer: 42} +maybeAnswer(false).answer +//│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/newres/HandlerResults.mls b/hkmc2/shared/src/test/mlscript/newres/HandlerResults.mls new file mode 100644 index 0000000000..c8fbe55ba1 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/HandlerResults.mls @@ -0,0 +1,24 @@ +:. +:lift +:effectHandlers + +// FIXME: The handler receiver and its result need shape propagation. This +// handler aborts: its record, not the rest of the handled body, is the result. +:fixme +:expect 42 +abstract class AnswerEffect with + fun answer() +fun handled() = + handle h = AnswerEffect with + fun answer()(resume) = {answer: 42} + h.answer() + {answer: 0} +handled().answer +//│ ╔══[COMPILATION ERROR] Member reference 'AnswerEffect' has no resolved target +//│ ║ l.12: handle h = AnswerEffect with +//│ ╙── ^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target +//│ ║ l.16: handled().answer +//│ ╙── ^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/MutationFlow.mls b/hkmc2/shared/src/test/mlscript/newres/MutationFlow.mls new file mode 100644 index 0000000000..1d6bc76f8a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/MutationFlow.mls @@ -0,0 +1,18 @@ +:. + +// FIXME: Assignment leaves the variable's initializer shape in place, so the +// later read is checked against a one-element tuple instead of the stored array. +:fixme +:expect 1 +let values = [0] +set values = values.concat([1]) +values.1 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.9: values.1 +//│ ║ ^^^^^^^^ +//│ ╟── Tuple literal does not contain member '1' +//│ ║ l.7: let values = [0] +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '1', got: 'undefined' +//│ values = [0, 1] From dd607db1f4d894eb4478d294eb6d5ebebb185991 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 19:59:47 +0800 Subject: [PATCH 086/228] fix: propagate mutable array elements through Array type parameter --- doc/new-resolution-design.md | 16 +- doc/new-resolution-suite-migration.md | 19 +- doc/reference.md | 6 +- .../src/test/scala/hkmc2/CompilerTest.scala | 33 +++ .../scala/hkmc2/semantics/Elaborator.scala | 4 +- .../hkmc2/semantics/InterfaceExposure.scala | 12 +- .../scala/hkmc2/semantics/NewResolver.scala | 103 +++++-- .../hkmc2/semantics/NewResolverState.scala | 4 + .../main/scala/hkmc2/semantics/Shape.scala | 16 +- .../src/test/mlscript/decls/Prelude.mls | 4 +- .../src/test/mlscript/newres/Arrays.mls | 11 +- .../test/mlscript/newres/DeclaredTypes.mls | 75 +++--- .../test/mlscript/newres/MutableArrays.mls | 255 ++++++++++++++++++ 13 files changed, 473 insertions(+), 85 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 7f629239dc..c1ecf27290 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -106,9 +106,19 @@ across imports. See `newres/NamedFields.mls`, `ImportedNamedFields.mls`, and Tuples preserve zero-based projections and expose the builtin Array class as their parent. The parent's element argument is the union of element shapes, -including annotations and call-context marks. Mutable arrays discard initial -element shapes and length because mutation can change both. Unknown layouts -permit indexed access with an unknown result without authorizing arbitrary members. +including annotations and call-context marks. Mutable literals instead bind the +same builtin `Array[T]` parameter to live element flow. An allocation mark separates +literals, and enclosing call marks separate activations. The consumer-owned cache +publishes the nominal interface before subscribing to initializer elements, so +empty arrays can receive writes and recursive arrays can refer to themselves. +Indexed writes, `fill`, and typed `push`/`unshift` rest arguments contribute to the +same parameter; numeric projections, spreads, patterns, and callbacks listen to it. +The union retains overwritten and removed elements and has no positional or length +precision. Written annotations remain opaque. Interface exposure follows element +bindings so arrays cannot hide escaping closures. `newres/MutableArrays.mls` and +`CompilerTest` cover element flow, capture contexts, and importer isolation. +Unknown layouts permit indexed access with an unknown result without authorizing +arbitrary members. Tuple-pattern transfer handles leading, trailing, and rest elements, including declared Array element types. See `newres/Arrays.mls` and `TuplePatternBindings.mls`. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 06a324b93d..ab6cf1a941 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -83,14 +83,17 @@ The inventory below separates these from compiler and prelude gaps. treated as the generator body's return value, leaving `.next` unresolved. Model the iterator result produced by lowering; use `codegen/Generators` as the worksheet acceptance case. -- **Mutation and control flow:** decide how reassignment affects the inferred - interface of mutable storage, including across already compiled worksheet +- **Mutation and control flow:** mutable arrays use the coarse `Array[T]` element + union described in the [resolver notes](new-resolution-design.md); precise + positions and lengths are out of scope. Decide how reassignment affects the + inferred interface of mutable storage, including across already compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned array still checked against its initializer's tuple length; accumulating both shapes would still reject valid later indexing. `codegen/SetStmt` also lacks argument flow through - its update callback. Member-variable definitions and CSV's nested mutable-array - element interfaces need work; initializer shapes cannot be assumed to survive - mutation. Handler inference needs separate flows for the receiver, values + its update callback. Generic `Array[A]` parameters currently receive element + flow one way; writes through them need a decision on two-way mutable element + constraints (`newres/MutableArrays.mls`). Member-variable definitions still need + work. Handler inference needs separate flows for the receiver, values passed to resumptions, and abortive results (`newres/HandlerResults.mls` and `codegen/ScopedBlocksAndHandlers`). Agree these designs before implementation. Ordinary result-shape contracts are covered by `newres/ControlFlowResults.mls`; @@ -143,7 +146,9 @@ recursive UPS matchers. Preserve both runtime results and matcher structure. - Extend host interfaces where needed: `Array.reduce` accumulator/callback contracts, keyword-named `Map.set` and `Reflect.set`, and WebAssembly exports. `Array.concat` currently returns `Array[Any]`; improving precision needs a - declared element constraint on its rest arguments. + declared element constraint on its rest arguments. `Array.splice` must separate + its optional deletion count from inserted elements before those elements can + constrain `T`; `newres/MutableArrays.mls` records the gap. - Complete quasiquote type-selection and wildcard-reference lowering (`CSP`, `QuoteExample1`). - Preserve source origins in synthesized references and diagnostics. In particular, @@ -190,7 +195,7 @@ resolution; imports that exchange options must use `LegacyOption` consistently. | Application fixture | Observed obstruction | | --- | --- | | `apps/Accounting.mls` | Array callback arity, `reduce`, and receiver interfaces. | -| `apps/CSV.mls` | Mutable nested-array elements lack the shape required for `push`. | +| `apps/CSV.mls` | Retry with the mutable-array element flow; regexp/nullish result interfaces and `at` results still need checking. | | `apps/parsing/Extension.mls` | Receivers for `display`, `add`, and `extendChoices` have no resolved target. | | `apps/parsing/Keywords.mls` | Legacy `Parser` consumers cannot inspect its new-resolution signatures. | | `apps/parsing/Lexer.mls` | Missing automatic contextual argument insertion and opened binary `~` resolution. | diff --git a/doc/reference.md b/doc/reference.md index 179327e15b..ea9b9f4060 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -723,8 +723,10 @@ f(1, 2, 3) // xs = [1, 2, 3] Under new resolution, an annotation on a rest parameter describes the whole rest array. Tuple values support precise zero-based projections such as `xs.0` and -inherit the declared Array interface. Mutable arrays do not retain their initial -length or precise element shapes after mutation. Array callbacks such as `map` +inherit the declared Array interface. Mutable array literals use one `Array[T]` +element interface: initializer elements and subsequent writes contribute to `T`. Reads +see the accumulated element shapes, without tracking positions, length, or which +elements have been overwritten or removed. Array callbacks such as `map` receive the element, index, and array; their declared function interfaces must account for these arguments, for example with unused or rest parameters. diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index d788468b3e..10f484cfa4 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -345,6 +345,39 @@ class CompilerTest extends AnyFunSuite: assert(parameters.map(p => (p.shapes.toVector, p.shapeListeners.toVector)) == before, "A consumer must not change the cached definition's inference graph") + test("mutable array parameter flow stays private to each importer"): + val fs = new InMemoryFileSystem(loadStandardLibrary()) + given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace = false + given Raise = diagnostic => fail(diagnostic.toString) + val prelude = cctx.getPrelude(paths.preludeFile).ctx + val parameter = prelude.builtins.Array.defn.get.tparams.head.sym + val originalShapes = parameter.shapes.toVector + val originalListeners = parameter.shapeListeners.toVector + fs.write("/Arrays.mls", """#lang(0.3.x, strictResolution: true) + |module Arrays with + | fun empty() = mut [] + | fun singleton[A](x: A) = mut [x] + |""".stripMargin) + val compiler = new MLsCompiler(_ => summon[Raise]) + List("first", "second").foreach: field => + val path = s"/$field.mls" + fs.write(path, s"""#lang(0.3.x, strictResolution: true) + |import "./Arrays.mls" + |class Item(val $field: Int) + |let xs = Arrays.empty() + |xs.push(Item(1)) + |xs.0.$field + |Arrays.singleton(Item(2)).0.$field + |""".stripMargin) + compiler.compileModule(Path(path)) + assert(parameter.shapes.toVector == originalShapes, + "Mutable element inference must not publish into the shared Array parameter") + assert(parameter.shapeListeners.toVector == originalListeners, + "Mutable element inference must not attach listeners to the shared Array parameter") + test("an exported generic selection is rejected before a consumer can specialize it"): val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 27c1cf1423..774239bab6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1193,7 +1193,9 @@ extends Importer: // Resolve the term interpretation of the l-value before lowering. The // selected definition is recorded even when its value publishes no shape. // Use this for synthesized assignments as well as source-level `set`. - if newResolution then listenTerm(lhs)(_ => ()) + if newResolution then + listenTerm(lhs)(_ => ()) + assignArrayElement(lhs, rhs) Term.Assgn(lhs, rhs) def ifLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit, loc: Opt[Loc]): Term.IfLike = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index d4843df510..e15f18d7e2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -144,9 +144,15 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) callable.result.foreach: result => watch((result, marks))(resolver.listenTypeValues(result)): shape => emit(shape.exit(marks), path) - // Nominal annotations expose their declared interface, not an initializer's - // inferred implementation. Unknown and dynamic values have no static graph. - case _: (NominalTypeShape | RecordTypeShape | OpaqueTypeShape | UnknownValueShape | DynShape | ErrShape) => () + case nominal: NominalTypeShape => + // Array element bindings may contain escaping closures. Follow only the + // declared binding; concrete annotations still hide implementation shapes. + resolver.arrayElementType(nominal).foreach: binding => + watch((shape, "array elements"))(listener => { resolver.listenArrayElements(shape)(listener); () }): element => + emit(element, path.via(msg"This value is stored in this array." -> binding.resolution.source.toLoc)) + // Structural annotations hide initializer implementations. Unknown and + // dynamic values have no static graph. + case _: (RecordTypeShape | OpaqueTypeShape | UnknownValueShape | DynShape | ErrShape) => () def check(exports: Ls[BlockMemberSymbol], values: Ls[Term]): Unit = try diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 700fea0569..2d560d9ded 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -205,12 +205,12 @@ class NewResolver: case TypeShape.Nominal(defn) => val bindings = bind(defn.tparams) defn.ext match - case N => publish(NominalTypeShape(defn, bindings, implicitParent(defn))(S(tpe.resolution.source))) + case N => publish(NominalTypeShape(defn, bindings, implicitParent(defn))(S(tpe.resolution.source))(this)) case S(parent) => // Only the parent's declared type is relevant here. Evaluating // its constructor arguments would reintroduce implementation flow. listenTypeValues(declaredType(typeResolution(parent.cls), bindings)): ext => - publish(NominalTypeShape(defn, bindings, S(ext))(S(tpe.resolution.source))) + publish(NominalTypeShape(defn, bindings, S(ext))(S(tpe.resolution.source))(this)) case TypeShape.Alias(symbol, rhs) => // Revisiting the same alias reference without reaching an outer // nominal/function shape supplies no additional interface. Track @@ -249,8 +249,8 @@ class NewResolver: val ps = params match case Tup(fields) => DeclaredParams(fields.collect: case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current.bindings)) - , fields.exists(!_.isInstanceOf[Fld])) - case single => DeclaredParams(S(declaredType(typeResolution(single), current.bindings)) :: Nil, false) + , fields.exists(!_.isInstanceOf[Fld]), N) + case single => DeclaredParams(S(declaredType(typeResolution(single), current.bindings)) :: Nil, false, N) publish(CallableTypeShape(tpe.resolution.source, ps :: Nil, S(declaredType(ret, current.bindings)), Nil)) case TypeShape.Tuple(fields) => @@ -330,7 +330,8 @@ class NewResolver: listenTypeValues(signature(td.sign.get))(publish) else if td.params.nonEmpty then publish(CallableTypeShape(source, - td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), ps.restParam.nonEmpty)), result, Nil)) + td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), + ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature))), result, Nil)) else result match case S(tpe) => listenTypeValues(tpe)(publish) case N => @@ -582,7 +583,7 @@ class NewResolver: val cls = prelude.builtins.Array.defn.get softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") val elements = new TypeResolution(tuple.source, messages => resolError(tuple.source, messages)) - val parent = NominalTypeShape(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), implicitParent(cls))(N) + val parent = NominalTypeShape(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), implicitParent(cls))(N)(this) tupleArrayParents(new Identity(tuple)) = parent tuple.segments.foreach: case field: TupleShape.Fixed => listenTupleField(field): value => @@ -592,6 +593,53 @@ class NewResolver: case NoShape => () parent + /** Mutable literals share Array's existing element parameter, with an allocation + * mark to keep distinct arrays and enclosing call activations independent. + * Seed the interface before following elements so empty and recursive arrays + * can receive writes. No positions or lengths survive this conversion. + */ + private def mutableArray(source: Term.Mut, underlying: Tup)(using NewResolverState): TermShape = + rstate.mutableArrays.get(new Identity(source)) match + case S(array) => array + case N => + val cls = prelude.builtins.Array.defn.get + softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") + val param = cls.tparams.head.sym + val site = FlowSymbol("mutable array")(using rstate.owner) + val context = ExitMark(ResolutionBoundary(cls.sym), S(site), NoMarks) + val elements = new TypeResolution(source, messages => resolError(source, messages)) + elements.publish(TypeShape.Parameter(param, param.inferenceHost)) + val array = NominalTypeShape(cls, Map(param -> declaredType(elements, Map.empty)), implicitParent(cls))(N)(this) + val shape = MarkedShape(array, context) + rstate.mutableArrays(new Identity(source)) = shape + listenTerm(underlying): tuple => + listenArrayElements(tuple): element => + publishParameter(param, element.enter(context :: Nil)) + shape + + private[semantics] def arrayElementType(array: NominalTypeShape): Opt[DeclaredType] = + val cls = prelude.builtins.Array.defn.get + array.ancestor(cls).flatMap(_.bindings.get(cls.tparams.head.sym)) + + /** Element writes constrain the same declared parameter as Array's methods. + * Written concrete annotations remain opaque under inferTypeArguments. + */ + private[semantics] def assignArrayElement(lhs: Term, rhs: Term)(using NewResolverState): Unit = + val receiver = lhs match + case NewSel(prefix, id, N) if id.name.toIntOption.exists(_ >= 0) => S(prefix) + case DynSel(prefix, _, true) => S(prefix) + case _ => N + receiver.foreach: prefix => + listenTerm(prefix): array => + val (head, context) = array.applicationHead + head match + case nominal: NominalTypeShape => arrayElementType(nominal).foreach: element => + listenTerm(rhs): value => + value.enter(context) match + case value: TermShape => inferTypeArguments(element, value, context) + case NoShape => () + case _ => () + private[semantics] def listenTupleField(field: TupleShape.Fixed)(listener: Listener)(using NewResolverState): Unit = def receive(shape: TermShape)(using NewResolverState): Unit = shape.exit(field.marks) match case value: TermShape => listener(value) @@ -606,7 +654,7 @@ class NewResolver: * constrains every element. Never recover elements from constructor values: * Arrays are mutable, and the one-number constructor creates empty slots. */ - private def listenArrayElements(shape: TermShape)(listener: Listener)(using NewResolverState): Bool = + private[semantics] def listenArrayElements(shape: TermShape)(listener: Listener)(using NewResolverState): Bool = val cls = prelude.builtins.Array.defn.get softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") val param = cls.tparams.head.sym @@ -620,6 +668,8 @@ class NewResolver: case value: TermShape => listener(value) case NoShape => () head match + case tuple: TupleShape => + listenArrayElements(tuple.arrayParent)(receive) case nominal: NominalTypeShape => nominal.ancestor(cls) match case S(array) => array.bindings.get(param) match @@ -986,7 +1036,7 @@ class NewResolver: case Marked(callable: CallableTypeShape, context) => val ps = callable.paramLists.head zipArgumentShapes(context :: Nil, ps.params.length, ps.hasRest, args, res, lhs): (index, value) => - if index < ps.params.length then ps.params(index).foreach: tpe => + (if index < ps.params.length then ps.params(index) else ps.rest).foreach: tpe => value match case value: TermShape => inferTypeArguments(tpe, value, context :: Nil) case NoShape => () @@ -1333,7 +1383,7 @@ class NewResolver: // Classes without an explicit parent expose Object's declarations. Share this // root interface between inferred instances, nominal annotations, and this-values; // Object itself must terminate the chain. This does not add a runtime constructor call. - private lazy val objectShape = NominalTypeShape(prelude.builtins.Object.defn.get, Map.empty, N)(N) + private lazy val objectShape = NominalTypeShape(prelude.builtins.Object.defn.get, Map.empty, N)(N)(this) private def implicitParent(defn: ClassLikeDef): Opt[TermShape] = if defn.sym is prelude.builtins.Object then N else S(objectShape) @@ -1345,6 +1395,25 @@ class NewResolver: case N => listener(implicitParent(defn)) + /** Bodyless members reached through constructed instances need the same + * signature bindings as members reached through nominal annotations. Explicit + * constructor arguments are read-only interfaces; inferred arguments remain + * writable parameter flow. Keep the class context on each delivered value. + */ + private def instanceBindings(td: TermDefinition, marks: Ls[Marks])(using NewResolverState): Map[VarSymbol, DeclaredType] = + td.tsym.owner.toList.flatMap(_.asDefnSym.defn.toList).flatMap(_.tparams).map: param => + val explicit = rstate.hasExplicitTypeArgument(param.sym, marks) + val bound = rstate.instanceParameterTypes.getOrElseUpdate((param.sym, explicit), { + val source = SimpleRef(param.sym)(param.sym.id) + val resolution = new TypeResolution(source, messages => resolError(source, messages)) + if explicit then listenTypeArgument(param.sym): value => + resolution.publish(TypeShape.Inferred(value)) + else resolution.publish(TypeShape.Parameter(param.sym, param.sym.inferenceHost)) + declaredType(resolution, Map.empty) + }) + param.sym -> bound + .toMap + private def fromSymbol(shape: SymShape, listener: Listener, source: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(using NewResolverState): Unit = shape match case declared: DeclaredSymShape => @@ -1400,6 +1469,12 @@ class NewResolver: MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N) case _ => shape wrappedListener(captured) + case S(d: TermDefinition) if d.body.isEmpty && !d.tsym.isInstanceOf[ClassCtorSymbol] + && d.tsym.owner.exists(_.asDefnSym.defn.exists(_.tparams.nonEmpty)) => + listenDeclaredMember(bms, instanceBindings(d, markss), resSym, trm, N, selected, receiver): value => + value.exit(markss) match + case value: TermShape => listener(value) + case NoShape => () case S(d: TermDefinition) => d.tsym match case ccs: ClassCtorSymbol => @@ -1537,15 +1612,7 @@ class NewResolver: case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => (), false) case value: TermShape => instantiate(value) listener(shape) - case Mut(underlying: Tup) => listenTerm(underlying): - case Marked(tuple: TupleShape, marks) => - // Array methods can change both the elements and the length. Retain the - // producer dependency, but none of the initializer's fixed layout. - val fields = TupleShape.Unknown(trm, Nil, UnknownValueShape.at(trm)) :: Nil - TupleShape(trm, TupleShape.Rest(tuple, fields) :: Nil)(this).exit(marks) match - case shape: TermShape => listener(shape) - case NoShape => () - case shape => listener(shape) + case mut @ Mut(underlying: Tup) => listener(mutableArray(mut, underlying)) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => listenAggregate(tuple, listener): publish => val fields = tuple.fields.collect { case Fld(_, key, S(value)) => (key, value) } diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index edf1e225bd..3039d94b93 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -199,6 +199,10 @@ final class NewResolverState private ( new Cache(source.map(_.capturedTypes), identity) val tupleArrayParents: Cache[Identity[TupleShape], NominalTypeShape] = new Cache(source.map(_.tupleArrayParents), identity) + val instanceParameterTypes: Cache[(VarSymbol, Bool), DeclaredType] = + new Cache(source.map(_.instanceParameterTypes), identity) + val mutableArrays: Cache[Identity[Term.Mut], TermShape] = + new Cache(source.map(_.mutableArrays), identity) // Named tuple fields have stable property identities, shared with consumers // through the tuple's original graph rather than allocated per spread candidate. val namedTupleRecords: Cache[Identity[Tup], Rcd] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 6823c25ae3..0a28682ad9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -381,7 +381,7 @@ final case class RecordTypeShape(source: Term.Rcd, fields: Ls[(RcdField, TypeRes * interfaces such as a tuple's Array parent have no written annotation. */ final case class NominalTypeShape(defn: ClassLikeDef, bindings: Map[VarSymbol, DeclaredType], - parent: Opt[TermShape])(val annotation: Opt[Term]) extends NonAppTermShape: + parent: Opt[TermShape])(val annotation: Opt[Term])(resolver: NewResolver) extends NonAppTermShape: def describe: Str = s"value of type '${defn.sym.nme}'" def toLoc: Opt[Loc] = defn.toLoc override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = @@ -394,10 +394,16 @@ final case class NominalTypeShape(defn: ClassLikeDef, bindings: Map[VarSymbol, D protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = defn.body.members.get(name) match case S(member) => MemberLookup.Declared(member, bindings, Nil, annotation) - case N => parent.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)).withAnnotation(annotation) - -/** Parameter types and arity exposed by one list in a declared calling interface. */ -final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool) + case N => + (name.toIntOption, resolver.arrayElementType(this)) match + case (S(index), S(element)) if index >= 0 => MemberLookup.Indexed(TupleShape.TypedField(element, Nil), Nil) + case _ => parent.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)).withAnnotation(annotation) + +/** Parameter types and arity exposed by one list in a declared calling interface. + * A rest annotation describes the whole trailing array; hasRest also distinguishes + * an unannotated rest parameter from a fixed-arity list. + */ +final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool, rest: Opt[DeclaredType]) /** Calls through annotations expose only the declared result. Argument shapes * constrain type parameters in the interface; they do not recover its implementation. diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index f971c580d7..a4d7594fd7 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -155,8 +155,8 @@ declare class Array[T](val length: Int): Array[T] with fun at(index: Int): T | undefined fun fill(value: T, ...bounds): Array[T] fun splice(start: Int, ...items): Array[T] - fun push(...items): Int - fun unshift(...items): Int + fun push(...items: Array[T]): Int + fun unshift(...items: Array[T]): Int fun pop(): T | undefined fun shift(): T | undefined fun concat(...arrays): Array[Any] diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 818e1bfac7..5406004a28 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -102,10 +102,9 @@ xs.map((x, ...) => x.original) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.101: xs.map((x, ...) => x.original) //│ ║ ^^^^^^^^^^ -//│ ╟── Cannot resolve member 'original' of a value with unknown shape. -//│ ╟── The shape of this value is unknown. -//│ ║ l.99: let xs = mut [Original(1)] -//│ ╙── ^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Replacement' does not contain member 'original' +//│ ║ l.100: set xs.0 = Replacement(2) +//│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Replacement(2)] @@ -136,7 +135,7 @@ new String("hello") is String :ge new Array[Int](1, 2) //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.137: new Array[Int](1, 2) +//│ ║ l.136: new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls @@ -155,7 +154,7 @@ new Array[Int](1, 2) :ge new Array[Int]() //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.156: new Array[Int]() +//│ ║ l.155: new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 84698e7013..3605cbb5d3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -457,10 +457,9 @@ set xs.0 = Second(2) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.456: [...xs].0.first //│ ║ ^^^^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'first' of a value with unknown shape. -//│ ╟── The shape of this value is unknown. -//│ ║ l.454: let xs = mut [First(1)] -//│ ╙── ^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.455: set xs.0 = Second(2) +//│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -468,10 +467,10 @@ set xs.0 = Second(2) :ge [1, 2].2 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.469: [1, 2].2 +//│ ║ l.468: [1, 2].2 //│ ║ ^^^^^^^^ //│ ╟── Tuple literal does not contain member '2' -//│ ║ l.469: [1, 2].2 +//│ ║ l.468: [1, 2].2 //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -507,14 +506,14 @@ private fun identity(value) = value fun read(box: Annotated) = identity(box.item).extra read(Child(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.507: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.506: fun read(box: Annotated) = identity(box.item).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.503: class Parent(val item) +//│ ║ l.502: class Parent(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.507: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.506: fun read(box: Annotated) = identity(box.item).extra //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -524,16 +523,16 @@ class Box(val callback) fun invoke(box: Box) = box.callback() invoke(Box(() => 42)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.524: fun invoke(box: Box) = box.callback() +//│ ║ l.523: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.524: fun invoke(box: Box) = box.callback() +//│ ║ l.523: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^ //│ ╟── Constructor parameter 'callback' has no type annotation. -//│ ║ l.523: class Box(val callback) +//│ ║ l.522: class Box(val callback) //│ ║ ^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'callback'. -//│ ║ l.524: fun invoke(box: Box) = box.callback() +//│ ║ l.523: fun invoke(box: Box) = box.callback() //│ ╙── ^^^ //│ = 42 @@ -542,14 +541,14 @@ invoke(Box(() => 42)) class Box[X](val value: [X, X]) fun foo[A](x: Box[A]) = x.value.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.543: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.542: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.543: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.542: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.543: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.542: fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ // Concrete callers cannot hide the annotation responsible for an abstract tuple element. @@ -560,14 +559,14 @@ class Box[X](val value: [X, X]) private fun foo[A](x: Box[A]) = x.value.0.oops foo[Item](Box[Item]([new Item, new Item])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.560: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.559: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.560: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.559: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.560: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.559: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -577,28 +576,28 @@ class Box[X](val value: [X, X]) type Wrapped[T] = Box[T] fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.578: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.577: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.578: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.577: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.578: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.577: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╙── ^ // Tuple annotations do not need a nominal receiver to produce an abstract element. :ge fun foo[A](x: [A, A]) = x.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.592: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.591: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.592: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.591: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.592: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.591: fun foo[A](x: [A, A]) = x.0.oops //│ ╙── ^ // An unannotated field can be reached through a function result annotation and an alias. @@ -607,14 +606,14 @@ class Box(val value) type Wrapped = Box fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.608: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.607: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.606: class Box(val value) +//│ ║ l.605: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.608: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.607: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╙── ^^^^^^^ // Explicit ascription supplies the annotation even when its operand is a known value. @@ -624,14 +623,14 @@ class Item with class Box(val value) (Box(new Item) as Box).value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.625: (Box(new Item) as Box).value.oops +//│ ║ l.624: (Box(new Item) as Box).value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.624: class Box(val value) +//│ ║ l.623: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.625: (Box(new Item) as Box).value.oops +//│ ║ l.624: (Box(new Item) as Box).value.oops //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -639,14 +638,14 @@ class Box(val value) :ge fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.640: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.639: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.640: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.639: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.640: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.639: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╙── ^ // A concrete annotation supplies the nested member interface without losing tuple precision. @@ -663,11 +662,11 @@ foo(Box[Item]([new Item, new Item])) class Pair[A, B](val value: [A, B]) fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.664: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.663: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Type argument 'B' is omitted in this annotation, so its shape is unknown. -//│ ║ l.664: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.663: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╙── ^^^^ // An omitted alias parameter retains the annotation that left it unspecified. @@ -675,9 +674,9 @@ fun omittedSecond(x: Pair[Int]) = x.value.1.oops type HalfPair[T] = Pair[Int, T] fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.676: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.675: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. -//│ ║ l.676: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.675: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╙── ^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls new file mode 100644 index 0000000000..666691292c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls @@ -0,0 +1,255 @@ +:. + +// Mutable arrays expose their element union without retaining positions or length. +:expect [1, [1, 2]] +class Item(val value: Int) +let xs = mut [Item(1)] +xs.push(Item(2)) +[xs.0.value, xs.map((x, ...) => x.value)] +//│ = [1, [1, 2]] +//│ xs = [Item(1), Item(2)] + +// Empty literals acquire their element interface from writes through aliases. +:expect [3, 4] +class Item(val value: Int) +let xs = mut [] +let alias = xs +alias.push(Item(4)) +xs.unshift(Item(3)) +[xs.0.value, xs.1.value] +//│ = [3, 4] +//│ alias = [Item(3), Item(4)] +//│ xs = [Item(3), Item(4)] + +// Indexed writes contribute elements, including computed indices. +:expect [2, 3] +class Item(val value: Int) +let xs = mut [Item(1)] +set xs.0 = Item(2) +let index = 1 +set xs.[index] = Item(3) +[xs.0.value, xs.1.value] +//│ = [2, 3] +//│ index = 1 +//│ xs = [Item(2), Item(3)] + +// Separate allocations do not share inferred element interfaces. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +let xs = mut [First(1)] +let ys = mut [] +ys.push(Second(2)) +[xs.0.first, ys.0.second] +//│ = [1, 2] +//│ xs = [First(1)] +//│ ys = [Second(2)] + +// The same literal retains distinct element flow in different function calls. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +fun singleton(x) = mut [x] +let xs = singleton(First(1)) +let ys = singleton(Second(2)) +[xs.0.first, ys.0.second] +//│ = [1, 2] +//│ xs = [First(1)] +//│ ys = [Second(2)] + +// Every possible element is checked, including values inserted by methods. +:ge +class First(val first: Int) +class Second(val second: Int) +let xs = mut [First(1)] +xs.push(Second(2)) +xs.0.first +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.66: xs.0.first +//│ ║ ^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.65: xs.push(Second(2)) +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [First(1), Second(2)] + +// Nested arrays listen to later inner and outer writes. +:expect [[1], [2]] +class Item(val value: Int) +let rows = mut [mut []] +rows.0.push(Item(1)) +rows.push(mut [Item(2)]) +rows.map((row, ...) => row.map((item, ...) => item.value)) +//│ = [[1], [2]] +//│ rows = [[Item(1)], [Item(2)]] + +// Writes through a function parameter return to the same array context. +:expect 3 +class Item(val value: Int) +private fun append(xs, value) = xs.push(value) +let xs = mut [] +append(xs, Item(3)) +xs.0.value +//│ = 3 +//│ xs = [Item(3)] + +// Calls producing an empty literal stay independent after later writes. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +fun empty() = mut [] +let xs = empty() +let ys = empty() +xs.push(First(1)) +ys.push(Second(2)) +[xs.0.first, ys.0.second] +//│ = [1, 2] +//│ xs = [First(1)] +//│ ys = [Second(2)] + +// Already compiled blocks retain the live element flow for later reads. +let retained = mut [] +//│ retained = [] + +:expect 5 +class Item(val value: Int) +retained.push(Item(5)) +retained.0.value +//│ = 5 + +// Concrete annotations do not acquire the extra members of inserted values. +:ge +class Base +class Child extends Base with + val extra = 1 +let xs = (mut []: Array[Base]) +xs.push(new Child) +xs.0.extra +//│ ╔══[COMPILATION ERROR] Unexpected record key shape. +//│ ║ l.125: let xs = (mut []: Array[Base]) +//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.126: xs.push(new Child) +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'push' across a computed key or unknown record spread. +//│ ╟── This computed key can overwrite the selected member. +//│ ║ l.125: let xs = (mut []: Array[Base]) +//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.127: xs.0.extra +//│ ║ ^^^^ +//│ ╟── Cannot resolve member '0' across a computed key or unknown record spread. +//│ ╟── This computed key can overwrite the selected member. +//│ ║ l.125: let xs = (mut []: Array[Base]) +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = undefined + +// Indexed and fill writes must invalidate initializer-only member assumptions. +:ge +class First(val first: Int) +class Second(val second: Int) +let xs = mut [First(1)] +set xs.[0] = Second(2) +xs.0.first +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.154: xs.0.first +//│ ║ ^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.153: set xs.[0] = Second(2) +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [Second(2)] + +:ge +class First(val first: Int) +class Second(val second: Int) +let xs = mut [First(1)] +xs.fill(Second(2)) +xs.0.first +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.169: xs.0.first +//│ ║ ^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.168: xs.fill(Second(2)) +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [Second(2)] + +// A mutable array does not hide a closure exposed by a public result. +:ge +module Exposed with + fun make() = mut [x => x.missing] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ ^^^^^^^^^ +//│ ╟── Cannot resolve member 'missing' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ ^ +//│ ╟── This value is stored in this array. +//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── This value is returned here. +//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'make' is accessible here. +//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── 'Exposed' is exposed by this compilation unit. +//│ ║ l.181: module Exposed with +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// Explicit constructor type arguments remain opaque when methods receive subtypes. +:ge +class Base +class Child extends Base with + val extra = 1 +let xs = new mut Array[Base](0) +xs.push(new Child) +xs.map((x, ...) => x.extra) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.212: xs.map((x, ...) => x.extra) +//│ ║ ^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [Child { extra: 1 }] + +// Recursive element flow terminates without expanding a positional layout. +:expect true +let recursive = mut [] +recursive.push(recursive) +recursive.0.0 === recursive +//│ = true +//│ recursive = [ref'1] as ref'1 + +// TODO: splice's optional deletion count needs a separate interface from inserted elements. +:fixme +:expect 2 +class Item(val value: Int) +let xs = mut [] +xs.splice(0, 0, Item(2)) +xs.0.value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.234: xs.0.value +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' +//│ xs = [Item(2)] + +// TODO: generic Array[A] parameter bindings must carry writes back to the argument array. +:fixme +:expect 3 +class Item(val value: Int) +fun appendTyped[A](xs: Array[A], value: A) = xs.push(value) +let xs = mut [] +appendTyped(xs, Item(3)) +xs.0.value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.249: xs.0.value +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' +//│ xs = [Item(3)] From b46c194b4e03947b13840d0e9c580d1fa1faa144 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 20:42:48 +0800 Subject: [PATCH 087/228] docs: explain mutable array shape propagation with examples --- doc/new-resolution-design.md | 52 +++++++++++++++++++-------- doc/new-resolution-suite-migration.md | 10 +++--- doc/reference.md | 24 +++++++++---- 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index c1ecf27290..5d87c2eea7 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -105,20 +105,44 @@ across imports. See `newres/NamedFields.mls`, `ImportedNamedFields.mls`, and `RecordInterfaces.mls`. Tuples preserve zero-based projections and expose the builtin Array class as -their parent. The parent's element argument is the union of element shapes, -including annotations and call-context marks. Mutable literals instead bind the -same builtin `Array[T]` parameter to live element flow. An allocation mark separates -literals, and enclosing call marks separate activations. The consumer-owned cache -publishes the nominal interface before subscribing to initializer elements, so -empty arrays can receive writes and recursive arrays can refer to themselves. -Indexed writes, `fill`, and typed `push`/`unshift` rest arguments contribute to the -same parameter; numeric projections, spreads, patterns, and callbacks listen to it. -The union retains overwritten and removed elements and has no positional or length -precision. Written annotations remain opaque. Interface exposure follows element -bindings so arrays cannot hide escaping closures. `newres/MutableArrays.mls` and -`CompilerTest` cover element flow, capture contexts, and importer isolation. -Unknown layouts permit indexed access with an unknown result without authorizing -arbitrary members. +their parent. For example, `[First(1), Second(2)]` has a `First` value at index 0 +and a `Second` value at index 1. An Array operation such as `map` can read either +element, so its callback receives both candidate shapes. + +For a mutable literal, statically resolved element reads listen to the builtin +`Array[T]` type parameter's symbol. The initializer and later indexed assignments, `fill`, `push`, +and `unshift` send element shapes to that symbol. For example, starting with +`mut [First(1)]` and then pushing `Second(2)` makes both `First` and `Second` +candidates for each statically resolved element read. Overwriting or removing an element does not +remove its shape from these candidates. Resolution does not track which shape +belongs at which index or how long the array is. Each candidate is checked when +resolving an operation on an element; the presence of a `First` candidate cannot +justify accessing a `First`-only member when `Second` is also a candidate. + +The symbol is shared, but its shapes carry context marks that distinguish array +allocations and enclosing function calls. This keeps writes to separate arrays +from affecting each other's element reads. The cache in `NewResolverState` stores +the array's shape before subscribing to its initializer, allowing empty arrays to +receive writes and recursive arrays to refer to themselves. Numeric projections, +spreads, patterns, and callbacks all receive the element shapes through `T`. + +An explicit element type restricts which members resolution may use. If an array +is viewed as `Array[Base]`, inserting a `Child` that extends `Base` does not make +`Child`-only members accessible through that view. Resolution uses `Base`'s +declared members, regardless of the inserted value's more specific shape. In +`Array[A]`, where `A` is a type parameter, element reads instead receive the shapes +inferred for `A`. + +When an array is returned or otherwise exposed to callers, `InterfaceExposure` +also follows its element shapes. A function stored in that array must be checked +for calls from outside the compilation unit, just like a directly returned +function. `newres/MutableArrays.mls` and `CompilerTest` cover element flow, +separation between calls and importing compilation units, and exposed functions. +See the [migration worklist](new-resolution-suite-migration.md) for the remaining +`splice` and generic-parameter write propagation gaps. + +When an array's element shape is unknown, indexed access produces an unknown +shape; that does not authorize arbitrary member access on the result. Tuple-pattern transfer handles leading, trailing, and rest elements, including declared Array element types. See `newres/Arrays.mls` and `TuplePatternBindings.mls`. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index ab6cf1a941..67c329233a 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -83,11 +83,11 @@ The inventory below separates these from compiler and prelude gaps. treated as the generator body's return value, leaving `.next` unresolved. Model the iterator result produced by lowering; use `codegen/Generators` as the worksheet acceptance case. -- **Mutation and control flow:** mutable arrays use the coarse `Array[T]` element - union described in the [resolver notes](new-resolution-design.md); precise - positions and lengths are out of scope. Decide how reassignment affects the - inferred interface of mutable storage, including across already compiled worksheet - blocks. `newres/MutationFlow.mls` records a reassigned array still checked +- **Mutation and control flow:** mutable-array reads use the element shapes + collected from the initializer and subsequent writes, as described in the + [resolver notes](new-resolution-design.md). Tracking individual positions and + lengths is out of scope. Decide how reassignment affects the inferred interface + of mutable storage, including across already compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned array still checked against its initializer's tuple length; accumulating both shapes would still reject valid later indexing. `codegen/SetStmt` also lacks argument flow through its update callback. Generic `Array[A]` parameters currently receive element diff --git a/doc/reference.md b/doc/reference.md index ea9b9f4060..2bd6e2174a 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -723,12 +723,24 @@ f(1, 2, 3) // xs = [1, 2, 3] Under new resolution, an annotation on a rest parameter describes the whole rest array. Tuple values support precise zero-based projections such as `xs.0` and -inherit the declared Array interface. Mutable array literals use one `Array[T]` -element interface: initializer elements and subsequent writes contribute to `T`. Reads -see the accumulated element shapes, without tracking positions, length, or which -elements have been overwritten or removed. Array callbacks such as `map` -receive the element, index, and array; their declared function interfaces must -account for these arguments, for example with unused or rest parameters. +inherit the declared Array interface. + +Mutable array literals collect element shapes from their initializer and later +writes. Statically resolved element reads are checked against all those shapes. +For example, +after `let xs = mut [First(1)]` followed by `xs.push(Second(2))`, resolution +considers both `First` and `Second` when checking `xs.0`, even though the runtime +value at index 0 is still a `First`. Overwriting or removing an element does not +remove its shape from consideration. Resolution does not track individual +positions or the array's length. + +An explicit annotation such as `Array[Base]` makes element reads use the members +declared by `Base`. Storing a `Child` that extends `Base` does not permit access +to `Child`-only members through that annotation. + +Array callbacks such as `map` receive the element, index, and array; their +declared function interfaces must account for these arguments, for example with +unused or rest parameters. --- From ddf1fa439323e895e644a03e05cdf3baa1c66fff Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 21:14:50 +0800 Subject: [PATCH 088/228] docs: plan type-value flow distinct from type bounds --- doc/new-resolution-design.md | 5 + doc/new-resolution-suite-migration.md | 12 +- doc/new-resolution-type-value-flow.md | 228 ++++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 doc/new-resolution-type-value-flow.md diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 5d87c2eea7..29fca65e57 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -33,6 +33,11 @@ exported mapped arrays and imported generic functions and methods. ## Captures and generic flow +The planned distinction between passing type values and constraining ordinary +values is documented in [Type values and value constraints](new-resolution-type-value-flow.md). +The description below records the current implementation, which does not yet +preserve that distinction. + Class bodies introduce lexical captures, and a class and its constructor share one resolution boundary. A method's reference to an outer constructor must include the enclosing instance boundary as well as the method boundary. Consuming a diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 67c329233a..b8793c381e 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -87,13 +87,15 @@ The inventory below separates these from compiler and prelude gaps. collected from the initializer and subsequent writes, as described in the [resolver notes](new-resolution-design.md). Tracking individual positions and lengths is out of scope. Decide how reassignment affects the inferred interface - of mutable storage, including across already compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned array still checked + of mutable storage, including across already compiled worksheet blocks. + `newres/MutationFlow.mls` records a reassigned array still checked against its initializer's tuple length; accumulating both shapes would still reject valid later indexing. `codegen/SetStmt` also lacks argument flow through - its update callback. Generic `Array[A]` parameters currently receive element - flow one way; writes through them need a decision on two-way mutable element - constraints (`newres/MutableArrays.mls`). Member-variable definitions still need - work. Handler inference needs separate flows for the receiver, values + its update callback. Implement the [type-value flow design](new-resolution-type-value-flow.md) + so `Array[A]` parameters retain the actual element type's input and output uses; + the `appendTyped` case in `newres/MutableArrays.mls` records the missing write + propagation. Member-variable definitions still need work. Handler inference + needs separate flows for the receiver, values passed to resumptions, and abortive results (`newres/HandlerResults.mls` and `codegen/ScopedBlocksAndHandlers`). Agree these designs before implementation. Ordinary result-shape contracts are covered by `newres/ControlFlowResults.mls`; diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md new file mode 100644 index 0000000000..1fcd19f0ae --- /dev/null +++ b/doc/new-resolution-type-value-flow.md @@ -0,0 +1,228 @@ +# Type values and value constraints in new resolution + +This is the proposed design and implementation plan for separating type-value +flow from constraints on ordinary values. It is not implemented yet. The +[current resolver notes](new-resolution-design.md) describe the implementation; +the [migration worklist](new-resolution-suite-migration.md) lists remaining ports. +The flow rules below are the agreed design; the Scala representation and migration +steps are proposals for implementing it. + +## Type-value flow is different from bound propagation + +There are three distinct relationships. Here `L` and `U` denote bounds; an +ordinary value's shape can supply such a bound without first being converted into +an explicit type value. + +| Relationship | Meaning | +| --- | --- | +| `TypeValueShape(T)` flows into `A` | The type `T` supplies the meaning of `A` in both input and output uses. | +| `L <: A` | `L` is a lower bound of `A`: positive information about values supplied for `A`. | +| `A <: U` | `U` is an upper bound of `A`: a negative requirement on values described by `A`. | + +The first relationship is not a lower-bound or output flow. Once the type value +`Int` has reached `A`, both of the following rules apply: + +```text +TypeValueShape(Int) -> A + +L <: A produces L <: Int +A <: U produces Int <: U +``` + +This is about inputs and outputs of types, including positive and negative +positions inside compound types. In a function type `A -> B`, the parameter +position reverses polarity and the result position preserves it. The distinction +must therefore work through function parameters and results, callbacks, nominal +type arguments, and other declared interfaces, not just mutable storage. + +Arrays give one example. Matching `Array[Int]` against `Array[A]` sends the type +value `Int` into `A`. Inserting a string then supplies the lower-bound constraint +`Str <: A`, which reaches `Int` as `Str <: Int`. That operation must create a +constraint, rather than discard the string's contribution or treat `Str` as +another type value assigned to `A`. + +Creating the `Str <: Int` constraint does not require a new diagnostic in this +change. Concrete mismatches currently need not report a loud error. The required +change is that the constraint reaches the supplied type. + +## The role of TypeValueShape + +`TypeValueShape` represents a type being passed through the flow graph. It knows +how to turn ordinary shape flows involving that type into constraints with the +appropriate direction: + +| Operation involving `A`, after it receives `TypeValueShape(T)` | Required behavior | +| --- | --- | +| Propagate a lower bound `L <: A` | Constrain `L <: T`. | +| Propagate an upper bound `A <: U` | Constrain `T <: U`. | +| Forward the type value to another type parameter | Preserve the wrapper and both uses of `T`. | + +These operations describe the wrapper's contract, not final Scala method names. +Converting a type to its positive value shapes is still useful for resolving +operations on ordinary values. It is insufficient for passing that type as a +type argument, because that conversion loses its behavior in negative positions. +Likewise, an ordinary value's lower bound must not silently become a fixed type +argument. + +If one activation of `A` receives both `TypeValueShape(Int)` and +`TypeValueShape(Str)`, each bound is applied to both type values. For example, +`L <: A` produces `L <: Int` and `L <: Str`; `A <: U` produces `Int <: U` and +`Str <: U`. This is different from receiving the single type value +`TypeValueShape(Int | Str)`. In that case the constraints target the union type, +whose structure must be retained for the constraint implementation. + +The proposed payload is a live type reference, based on `DeclaredType`, together +with the context needed to interpret that reference. `DeclaredType` already +retains a `TypeResolution` and its parameter bindings. References to parameters +must also retain their originating inference hosts. The wrapper must preserve +aliases, type arguments, and captures. A snapshot of positive value shapes +cannot replace the type reference, since it cannot accept subsequent constraints +in both directions. + +For an inferred mutable array, call its element parameter `E`. This is a name for +the existing `Array` type-parameter symbol in that array's allocation context, +not a proposal for a new symbol per array. Matching that array against `Array[A]` +passes a type value referring to `E` into `A`. A subsequent bound on `A` is then +constrained through `E` in the appropriate direction. In particular, the lower +bound from an element insertion reaches `E` even if the array was initially empty. + +An explicit type argument such as `Base` continues to determine the interface +of values described by it. A `Child` value supplied against that type creates a +`Child <: Base` constraint; it does not replace the type value `Base` with `Child`. + +This replaces the earlier suggestion to connect the array's ordinary value +publishers in both directions. Type-value flow preserves both polarities; +ordinary bound propagation retains its direction. + +## Where the current implementation loses the distinction + +The relevant code is in +[`NewResolver.scala`](../hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala), +[`TypeShape.scala`](../hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala), and +[`NewResolverState.scala`](../hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala). + +- `listenTypeValues` currently produces **ordinary value shapes described by a + type**, despite its name. Both `Array[Int]`'s element interface and ordinary + integer values can consequently arrive as `TermShape`s. +- `applyTypeArguments` calls that operation and publishes the resulting value + shapes into the type-parameter host. The fact that a type was supplied is lost. +- The nominal-argument branch of `inferTypeArguments` also extracts value shapes + from the actual argument's type parameters before constraining the expected + parameters. Thus `Array[Int]` against `Array[A]` becomes a value-bound flow. +- The parameter branch of `inferTypeArguments` suppresses incoming constraints + for explicit type arguments. That protects the declared result interface, but + also discards the constraints that should reach the supplied type. +- `mutableArray` publishes initializer element shapes into `Array`'s parameter. + These are ordinary value lower bounds and must stay distinguishable from type + arguments. `TypeShape.Inferred` and `instanceBindings` need the same audit: + their current uses include both inferred elements and explicit type interfaces. + +Adding a wrapper only at the explicit type-application call site would therefore +be insufficient. Extracting nominal arguments and interpreting parameter uses +must preserve the distinction too. + +## Implementation plan + +1. **Define the wrapper and the flow operations.** Add `TypeValueShape` and explicit + operations for passing a type value and propagating lower and upper bounds. + Distinguish those operations from obtaining positive value shapes from a type. Keep wrapper transport distinct from runtime + value consumption. Audit `Shape`/`ShapeLike` and context-mark operations: today + their entry/exit results are restricted to `TermShape | NoShape`. Extend the + transport deliberately so type values retain context without masquerading as + array elements or callable runtime values. Rename or split `listenTypeValues` + so callers state which operation they need. + +2. **Implement constraint delivery through type values.** When a parameter receives + a type value, connect its existing and future bounds to that type. When a bound + arrives first, retain it so it is processed when the type value becomes known. + Follow aliases, captures, parameter bindings, and structural/function types + through one shared constraint implementation. Keep constraint direction + explicit, including when function parameter positions reverse it. Concrete + mismatches may remain quiet, but must reach this implementation. + +3. **Preserve type values at generic argument boundaries.** Change + `applyTypeArguments` and nominal argument matching in `inferTypeArguments` to + transport wrappers instead of the wrapped type's output value shapes. + `Array[Int]` against `Array[A]` must pass `Int` as a type value; an inferred + `Array[E]` must pass a live reference to `E`. Ordinary argument, field, and + callback-result values continue to create bounds. Preserve declared type + expressions such as `Int | Str` inside their wrapper instead of flattening + them into separate type-value deliveries. + +4. **Replace constraint suppression with constraint routing.** Explicit type + arguments must continue to determine the interface used for ordinary values. + Incoming value constraints must reach those types instead of being skipped by + `hasExplicitTypeArgument`. Audit other uses of that flag separately, including + detection of omitted constructor type arguments in `listenArrayElements`; + removing the constraint guard does not justify changing unrelated behavior. + Replace the positive-shape snapshot in `instanceBindings` with the wrapper's + lower- and upper-bound operations. Preserve the abstract activation used to + check generic bodies independently of their callers. + +5. **Apply the common operations to mutable-array interfaces.** Keep the existing + `Array` parameter symbol and allocation/call contexts. Initializer elements, + indexed assignments, `fill`, and `push`/`unshift` arguments create lower bounds. + Element uses receive the corresponding positive shapes and impose constraints + through their expected types. A generic helper taking `Array[A]` must propagate + these constraints through the wrapped reference to the caller's element + parameter. Reuse this machinery for nested arrays and declared member + signatures; do not add a special reverse edge just for arrays. + +6. **Preserve graph ownership and compiler boundaries.** Store bound listeners, + wrapper caches, and processed-constraint sets in `NewResolverState`, using the + existing consumer-local host mechanism. Include the kind of flow, referenced + hosts, and contexts in deduplication keys. Install edges before replay so cycles + terminate and subscription order does not change results. In particular, a + value bound arriving before a type value must not permanently publish a more + specific output interface that the supplied type does not expose. Resolve how + positive shape observation distinguishes bound-only inference from supplied + type values before wiring them into ordinary value listeners; do not rely on callback registration order. + Check `InterfaceExposure`, shape-pattern matches, and erasure/lowering consumers so + wrappers neither hide escaping functions nor become runtime shapes. Do not + mutate shared symbols or change completed member targets. + +7. **Add regressions, then remove the generic-array FIXME.** Test the semantic + distinctions below, run `ctest` before focused worksheets, and finish with + `hkmc2AllTests/test`. Review and commit all changed golden outputs. Update the + implementation notes and migration worklist only for behavior actually covered + by the completed implementation. + +## Acceptance tests + +- Supply `TypeValueShape(Int)` to `A`. Verify both that a lower bound `L <: A` + reaches `L <: Int` and that an upper bound `A <: U` reaches `Int <: U`. Check + delivery at the constraint layer, since loud concrete-mismatch diagnostics + are not required. +- Match `Array[Int]` against `Array[A]`, then supply a string to an element + parameter of type `A`. Verify that this creates `Str <: Int` while the positive + interface supplied by the type value remains `Int`. +- Repeat with the lower bound installed before the type value, and with a type + alias or captured parameter between them. Results must not depend on arrival + order, including when an output listener was registered first. +- Supply two type values to the same activation and verify that both receive + each bound. Separately supply `Int | Str` as one type value and verify that the + constraint targets the union type rather than each alternative independently. +- Distinguish supplying the type argument `Int` from inferring a lower bound from + an ordinary integer argument. Only the former supplies that type value. +- Make the `appendTyped` regression in `newres/MutableArrays.mls` pass: writing + through `Array[A]` into an initially empty mutable array must make the inserted + element available to reads of the original array. Also cover later writes, + nested arrays, and several generic forwarding helpers. +- Keep `Array[Base]` reads limited to `Base`'s declared members after a `Child` + insertion, for explicit constructor arguments and annotated parameters alike. +- Exercise both input and output uses in function types and generic callbacks, + along with ordinary explicit and inferred generic function calls. +- Check distinct allocations, distinct calls to the same function, recursive + propagation, later worksheet blocks, separate importers, and re-exports. + Preserve the `PublisherTest` and `CompilerTest` ownership assertions. +- Keep generic-body checks, unknown-versus-dynamic behavior, exposed-closure + checking, and completed-reference assertions intact. + +## Scope + +Precise array positions and lengths, a new policy for concrete type-mismatch +diagnostics, optional `splice` arguments, general storage reassignment, and +handler-result inference remain separate work. Rules for bounds or variance +beyond those needed by the existing interfaces require a separate design +decision; type-value flow must not be mislabeled as covariant lower-bound +propagation. From 4b418ae64aaa46a2e95e396bc976b2da8bbaff34 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 21:26:03 +0800 Subject: [PATCH 089/228] docs: compare type-value and instance shape representations --- doc/new-resolution-design.md | 2 +- doc/new-resolution-suite-migration.md | 5 +- doc/new-resolution-type-value-flow.md | 439 +++++++++--------- .../test/mlscript/newres/MutableArrays.mls | 49 +- 4 files changed, 244 insertions(+), 251 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 29fca65e57..6feb131891 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -34,7 +34,7 @@ exported mapped arrays and imported generic functions and methods. ## Captures and generic flow The planned distinction between passing type values and constraining ordinary -values is documented in [Type values and value constraints](new-resolution-type-value-flow.md). +values is documented in [Type values, instances, and bounds](new-resolution-type-value-flow.md). The description below records the current implementation, which does not yet preserve that distinction. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index b8793c381e..fce2c70c71 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -91,8 +91,9 @@ The inventory below separates these from compiler and prelude gaps. `newres/MutationFlow.mls` records a reassigned array still checked against its initializer's tuple length; accumulating both shapes would still reject valid later indexing. `codegen/SetStmt` also lacks argument flow through - its update callback. Implement the [type-value flow design](new-resolution-type-value-flow.md) - so `Array[A]` parameters retain the actual element type's input and output uses; + its update callback. Settle the representation in the + [type-value/instance design comparison](new-resolution-type-value-flow.md), then + implement it so `Array[A]` retains the actual element type's input and output uses; the `appendTyped` case in `newres/MutableArrays.mls` records the missing write propagation. Member-variable definitions still need work. Handler inference needs separate flows for the receiver, values diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 1fcd19f0ae..86825e7d2c 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -1,228 +1,233 @@ -# Type values and value constraints in new resolution +# Type values, instances, and bounds in new resolution -This is the proposed design and implementation plan for separating type-value -flow from constraints on ordinary values. It is not implemented yet. The -[current resolver notes](new-resolution-design.md) describe the implementation; +This note records the semantic requirements and compares possible representations. +The wrapper placement is undecided; the earlier plan to add `TypeValueShape` was +premature. No compiler implementation has been selected. The +[current resolver notes](new-resolution-design.md) describe existing behavior; the [migration worklist](new-resolution-suite-migration.md) lists remaining ports. -The flow rules below are the agreed design; the Scala representation and migration -steps are proposals for implementing it. -## Type-value flow is different from bound propagation +## The distinction to preserve -There are three distinct relationships. Here `L` and `U` denote bounds; an -ordinary value's shape can supply such a bound without first being converted into -an explicit type value. - -| Relationship | Meaning | -| --- | --- | -| `TypeValueShape(T)` flows into `A` | The type `T` supplies the meaning of `A` in both input and output uses. | -| `L <: A` | `L` is a lower bound of `A`: positive information about values supplied for `A`. | -| `A <: U` | `U` is an upper bound of `A`: a negative requirement on values described by `A`. | - -The first relationship is not a lower-bound or output flow. Once the type value -`Int` has reached `A`, both of the following rules apply: +Supplying a type argument and adding a bound are different operations. If `Int` +is supplied as the type argument for a contextual parameter `A`, it affects both +positive and negative uses of `A`: ```text -TypeValueShape(Int) -> A +A has the supplied type Int: L <: A produces L <: Int A <: U produces Int <: U ``` -This is about inputs and outputs of types, including positive and negative -positions inside compound types. In a function type `A -> B`, the parameter -position reverses polarity and the result position preserves it. The distinction -must therefore work through function parameters and results, callbacks, nominal -type arguments, and other declared interfaces, not just mutable storage. - -Arrays give one example. Matching `Array[Int]` against `Array[A]` sends the type -value `Int` into `A`. Inserting a string then supplies the lower-bound constraint -`Str <: A`, which reaches `Int` as `Str <: Int`. That operation must create a -constraint, rather than discard the string's contribution or treat `Str` as -another type value assigned to `A`. - -Creating the `Str <: Int` constraint does not require a new diagnostic in this -change. Concrete mismatches currently need not report a loud error. The required -change is that the constraint reaches the supplied type. - -## The role of TypeValueShape - -`TypeValueShape` represents a type being passed through the flow graph. It knows -how to turn ordinary shape flows involving that type into constraints with the -appropriate direction: - -| Operation involving `A`, after it receives `TypeValueShape(T)` | Required behavior | -| --- | --- | -| Propagate a lower bound `L <: A` | Constrain `L <: T`. | -| Propagate an upper bound `A <: U` | Constrain `T <: U`. | -| Forward the type value to another type parameter | Preserve the wrapper and both uses of `T`. | - -These operations describe the wrapper's contract, not final Scala method names. -Converting a type to its positive value shapes is still useful for resolving -operations on ordinary values. It is insufficient for passing that type as a -type argument, because that conversion loses its behavior in negative positions. -Likewise, an ordinary value's lower bound must not silently become a fixed type -argument. - -If one activation of `A` receives both `TypeValueShape(Int)` and -`TypeValueShape(Str)`, each bound is applied to both type values. For example, -`L <: A` produces `L <: Int` and `L <: Str`; `A <: U` produces `Int <: U` and -`Str <: U`. This is different from receiving the single type value -`TypeValueShape(Int | Str)`. In that case the constraints target the union type, -whose structure must be retained for the constraint implementation. - -The proposed payload is a live type reference, based on `DeclaredType`, together -with the context needed to interpret that reference. `DeclaredType` already -retains a `TypeResolution` and its parameter bindings. References to parameters -must also retain their originating inference hosts. The wrapper must preserve -aliases, type arguments, and captures. A snapshot of positive value shapes -cannot replace the type reference, since it cannot accept subsequent constraints -in both directions. - -For an inferred mutable array, call its element parameter `E`. This is a name for -the existing `Array` type-parameter symbol in that array's allocation context, -not a proposal for a new symbol per array. Matching that array against `Array[A]` -passes a type value referring to `E` into `A`. A subsequent bound on `A` is then -constrained through `E` in the appropriate direction. In particular, the lower -bound from an element insertion reaches `E` even if the array was initially empty. - -An explicit type argument such as `Base` continues to determine the interface -of values described by it. A `Child` value supplied against that type creates a -`Child <: Base` constraint; it does not replace the type value `Base` with `Child`. - -This replaces the earlier suggestion to connect the array's ordinary value -publishers in both directions. Type-value flow preserves both polarities; -ordinary bound propagation retains its direction. - -## Where the current implementation loses the distinction - -The relevant code is in -[`NewResolver.scala`](../hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala), -[`TypeShape.scala`](../hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala), and -[`NewResolverState.scala`](../hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala). - -- `listenTypeValues` currently produces **ordinary value shapes described by a - type**, despite its name. Both `Array[Int]`'s element interface and ordinary - integer values can consequently arrive as `TermShape`s. -- `applyTypeArguments` calls that operation and publishes the resulting value - shapes into the type-parameter host. The fact that a type was supplied is lost. -- The nominal-argument branch of `inferTypeArguments` also extracts value shapes - from the actual argument's type parameters before constraining the expected - parameters. Thus `Array[Int]` against `Array[A]` becomes a value-bound flow. -- The parameter branch of `inferTypeArguments` suppresses incoming constraints - for explicit type arguments. That protects the declared result interface, but - also discards the constraints that should reach the supplied type. -- `mutableArray` publishes initializer element shapes into `Array`'s parameter. - These are ordinary value lower bounds and must stay distinguishable from type - arguments. `TypeShape.Inferred` and `instanceBindings` need the same audit: - their current uses include both inferred elements and explicit type interfaces. - -Adding a wrapper only at the explicit type-application call site would therefore -be insufficient. Extracting nominal arguments and interpreting parameter uses -must preserve the distinction too. - -## Implementation plan - -1. **Define the wrapper and the flow operations.** Add `TypeValueShape` and explicit - operations for passing a type value and propagating lower and upper bounds. - Distinguish those operations from obtaining positive value shapes from a type. Keep wrapper transport distinct from runtime - value consumption. Audit `Shape`/`ShapeLike` and context-mark operations: today - their entry/exit results are restricted to `TermShape | NoShape`. Extend the - transport deliberately so type values retain context without masquerading as - array elements or callable runtime values. Rename or split `listenTypeValues` - so callers state which operation they need. - -2. **Implement constraint delivery through type values.** When a parameter receives - a type value, connect its existing and future bounds to that type. When a bound - arrives first, retain it so it is processed when the type value becomes known. - Follow aliases, captures, parameter bindings, and structural/function types - through one shared constraint implementation. Keep constraint direction - explicit, including when function parameter positions reverse it. Concrete - mismatches may remain quiet, but must reach this implementation. - -3. **Preserve type values at generic argument boundaries.** Change - `applyTypeArguments` and nominal argument matching in `inferTypeArguments` to - transport wrappers instead of the wrapped type's output value shapes. - `Array[Int]` against `Array[A]` must pass `Int` as a type value; an inferred - `Array[E]` must pass a live reference to `E`. Ordinary argument, field, and - callback-result values continue to create bounds. Preserve declared type - expressions such as `Int | Str` inside their wrapper instead of flattening - them into separate type-value deliveries. - -4. **Replace constraint suppression with constraint routing.** Explicit type - arguments must continue to determine the interface used for ordinary values. - Incoming value constraints must reach those types instead of being skipped by - `hasExplicitTypeArgument`. Audit other uses of that flag separately, including - detection of omitted constructor type arguments in `listenArrayElements`; - removing the constraint guard does not justify changing unrelated behavior. - Replace the positive-shape snapshot in `instanceBindings` with the wrapper's - lower- and upper-bound operations. Preserve the abstract activation used to - check generic bodies independently of their callers. - -5. **Apply the common operations to mutable-array interfaces.** Keep the existing - `Array` parameter symbol and allocation/call contexts. Initializer elements, - indexed assignments, `fill`, and `push`/`unshift` arguments create lower bounds. - Element uses receive the corresponding positive shapes and impose constraints - through their expected types. A generic helper taking `Array[A]` must propagate - these constraints through the wrapped reference to the caller's element - parameter. Reuse this machinery for nested arrays and declared member - signatures; do not add a special reverse edge just for arrays. - -6. **Preserve graph ownership and compiler boundaries.** Store bound listeners, - wrapper caches, and processed-constraint sets in `NewResolverState`, using the - existing consumer-local host mechanism. Include the kind of flow, referenced - hosts, and contexts in deduplication keys. Install edges before replay so cycles - terminate and subscription order does not change results. In particular, a - value bound arriving before a type value must not permanently publish a more - specific output interface that the supplied type does not expose. Resolve how - positive shape observation distinguishes bound-only inference from supplied - type values before wiring them into ordinary value listeners; do not rely on callback registration order. - Check `InterfaceExposure`, shape-pattern matches, and erasure/lowering consumers so - wrappers neither hide escaping functions nor become runtime shapes. Do not - mutate shared symbols or change completed member targets. - -7. **Add regressions, then remove the generic-array FIXME.** Test the semantic - distinctions below, run `ctest` before focused worksheets, and finish with - `hkmc2AllTests/test`. Review and commit all changed golden outputs. Update the - implementation notes and migration worklist only for behavior actually covered - by the completed implementation. - -## Acceptance tests - -- Supply `TypeValueShape(Int)` to `A`. Verify both that a lower bound `L <: A` - reaches `L <: Int` and that an upper bound `A <: U` reaches `Int <: U`. Check - delivery at the constraint layer, since loud concrete-mismatch diagnostics - are not required. -- Match `Array[Int]` against `Array[A]`, then supply a string to an element - parameter of type `A`. Verify that this creates `Str <: Int` while the positive - interface supplied by the type value remains `Int`. -- Repeat with the lower bound installed before the type value, and with a type - alias or captured parameter between them. Results must not depend on arrival - order, including when an output listener was registered first. -- Supply two type values to the same activation and verify that both receive - each bound. Separately supply `Int | Str` as one type value and verify that the - constraint targets the union type rather than each alternative independently. -- Distinguish supplying the type argument `Int` from inferring a lower bound from - an ordinary integer argument. Only the former supplies that type value. -- Make the `appendTyped` regression in `newres/MutableArrays.mls` pass: writing - through `Array[A]` into an initially empty mutable array must make the inserted - element available to reads of the original array. Also cover later writes, - nested arrays, and several generic forwarding helpers. -- Keep `Array[Base]` reads limited to `Base`'s declared members after a `Child` - insertion, for explicit constructor arguments and annotated parameters alike. -- Exercise both input and output uses in function types and generic callbacks, - along with ordinary explicit and inferred generic function calls. -- Check distinct allocations, distinct calls to the same function, recursive - propagation, later worksheet blocks, separate importers, and re-exports. - Preserve the `PublisherTest` and `CompilerTest` ownership assertions. -- Keep generic-body checks, unknown-versus-dynamic behavior, exposed-closure - checking, and completed-reference assertions intact. - -## Scope - -Precise array positions and lengths, a new policy for concrete type-mismatch -diagnostics, optional `splice` arguments, general storage reassignment, and -handler-result inference remain separate work. Rules for bounds or variance -beyond those needed by the existing interfaces require a separate design -decision; type-value flow must not be mislabeled as covariant lower-bound -propagation. +In contrast, inferring `Int <: A` from an ordinary integer argument only adds a +lower bound. It does not supply `Int` as the meaning of `A` in all positions. +The distinction applies to input and output positions generally. Function +parameters reverse polarity; function results preserve it. Array operations are +one example, not the organizing principle of the design. + +If two distinct type values, `Int` and `Str`, reach the same activation of `A`, +each bound is applied to both. A lower bound `L <: A` produces both `L <: Int` +and `L <: Str`; an upper bound `A <: U` produces both `Int <: U` and `Str <: U`. +This differs from supplying the single type value `Int | Str`, against which +constraints must retain the meaning of that union type. + +Concrete mismatches such as `Str <: Int` may remain quiet for now. Constraints +must still reach the appropriate types, even when no loud diagnostic is emitted. + +## What can be inferred at a generic call + +“Preserve wrappers through inferred generic arguments” conflates two cases. + +**Inferring bounds from ordinary arguments.** For a function with parameters +`x: A` and `y: A`, supplying an integer and a string contributes lower bounds +`Int <: A` and `Str <: A`. It must not manufacture two supplied type values, +`Int` and `Str`: under the rule above, that would require each argument to satisfy +both types. An ordinary argument's inferred bound is not an exact type argument. + +One possible representation introduces a type inference variable `alpha` for +this call. The formal parameter `A` denotes `alpha`, and ordinary arguments add +bounds to `alpha`. A wrapper, if used, would refer to `alpha`, not to each shape +that becomes a lower bound of it. This is a conceptual inference cell, not a +requirement for a fresh global `Symbol`; the existing symbol plus call context +might identify it. Such a cell can retain ordinary shape bounds; this does not +require reconstructing a complete type expression for every closure or value. +Choosing this representation requires an explicit design. + +**Matching an existing type argument.** In the motivating `Array[Int]` against +`Array[A]` example, the actual array already carries an element type. Matching +must preserve that type's meaning in both input and output uses of `A`, rather +than extract integer instance shapes and forward only those lower bounds. +Likewise, an array with an inferred element parameter `E` must retain a live +reference to `E`, including its future constraints. Merely copying `E`'s current +positive candidates loses this information. + +This does not establish a rule that every nominal type-argument match binds an +exact type. The treatment of a parameter used only covariantly or contravariantly +must follow the chosen matching/variance rules. Neither wrapper placement by +itself determines those rules. + +## Option 1: wrap type values + +Use `TypeValueShape(T)` for a type carried by the flow graph. Both ends of a +type-value constraint should be represented explicitly: + +```text +TypeValueShape(Int) <: TypeValueShape(A) +``` + +Here the outer `<:` is the graph's relation between type-valued shapes. Its +processing rule must preserve both uses of the supplied type. It must not simply +strip the wrappers and install the ordinary lower bound `Int <: A`. +The earlier notation `TypeValueShape(Int) -> A` described publication into a +parameter host while leaving the host's meaning implicit. It was not an adequate +specification of a constraint between shapes. + +The wrapper must refer to a type description or a contextual type parameter, +including its originating host. It needs an explicit interpretation on both +sides of a constraint. Ordinary values described by that type remain represented +separately, through the existing instance shapes or another explicit operation. +For example, a parameter annotated `x: A` expects an instance of `A`, not a type +value; wrapping the type argument alone does not represent that expectation. + +Advantages: + +- Type arguments have an explicit tag when sharing transport with ordinary value + shapes. This makes accidental publication of an instance as a type argument + detectable. +- Explicit type application and forwarding of existing type arguments have a + direct representation, including nested type expressions and captured type + parameters. +- The current graph uses `Publisher.Data[Shape]`, so a new type-valued alternative + can fit that transport, provided the context operations are extended as well. + +Costs: + +- A symmetric source/target interpretation is needed; a wrapper around published + candidates alone is insufficient. +- Ordinary annotated values still need a separate representation or conversion. + Function constraints, member selection, and mixed type/value constraints must + consistently distinguish the two levels. +- `ShapeLike.enter` and `exit` currently return `TermShape | NoShape`. Transporting + type-valued wrappers requires changing that contract or representing their + contexts separately. +- Ordinary inference still needs a model for bounds. Wrapping every observed + instance shape would incorrectly turn inferred lower bounds into supplied types. + +## Option 2: wrap instances described by types + +Keep type descriptions as types and use `InstanceShape(T)` for an ordinary value +view described by `T`. The wrapper can occur in positive or negative position; +its name does not restrict it to a producer. + +```text +shape of an integer <: InstanceShape(A) + contributes an integer lower bound to A + +InstanceShape(A) <: an expected value interface + constrains A in the corresponding output use +``` + +With `Int` supplied for `A`, these operations use `Int` in the appropriate +polarity. A value viewed through `InstanceShape(Base)` exposes `Base`'s interface; +a supplied `Child` produces a constraint against `Base` without replacing that +view with `Child`'s more specific interface. + +In this representation, matching +`InstanceShape(Array[Int])` against `InstanceShape(Array[A])` reaches a relation +between the element types. It must implement the agreed `Array` behavior above. +The instance wrapper alone does not decide whether a nested relation is a type +binding, a lower/upper bound, or both bounds for an invariant parameter. + +Advantages: + +- The conversion from a type description to an ordinary value use is explicit. + Parameter annotations, result annotations, and member interfaces all use the + same distinction, in either polarity. +- This is close to the existing split between `TypeShape`/`DeclaredType` and + `TermShape`. Despite its name, `NominalTypeShape` is already a `TermShape` + describing an instance; its description is “value of type ...”. +- A live `InstanceShape(A)` can retain the type reference while `A` is unresolved, + instead of immediately expanding it into whichever instance candidates happen + to be known. This may make delayed constraints easier to handle, though it + still requires an order-independent propagation rule. +- It provides a natural place to distinguish an annotated value's permitted + interface from a more specific implementation shape. + +Costs: + +- Type-argument flow still needs an explicit representation or API. Removing + `TypeValueShape` does not make ordinary `T <: A` lower-bound propagation stand + for type instantiation. +- `NominalTypeShape`, `CallableTypeShape`, `RecordTypeShape`, and annotated tuples + already provide specialized instance views. A general wrapper must organize or + reuse these views, rather than add a second path with different behavior. +- Contexts for the type reference and for the ordinary value must remain correct + when the type contains captured parameters or an instance is passed through a + function. Moving the wrapper does not remove this requirement. +- Positive and negative interpretation cannot use the same eager expansion for + every type constructor. Function parameters reverse direction; union and + intersection constraints need their own rules. + +## Comparison and current recommendation + +| Concern | Type-value wrapper | Instance wrapper | +| --- | --- | --- | +| Boundary made explicit | A type enters the shared shape graph. | A type describes an ordinary value use. | +| Existing type-argument matching | Directly carries type-valued shapes. | Uses type references and a separate matching operation. | +| Annotated term values | Retains existing instance views or adds a conversion. | Directly represented by the wrapper. | +| Ordinary generic inference | Needs bounds distinct from supplied type values. | Needs bounds distinct from supplied type values. | +| Fit with current representations | Extends the shared shape transport. | Builds on the current type-description/instance-view split. | + +The current recommendation is to explore the instance wrapper first, because it +makes the meaning of annotated term shapes explicit and fits the existing type +interpretation graph. This is a preference, not an implementation decision. +A type-value wrapper remains useful if representing types inside the same shape +constraint language is a deliberate goal. The representations can also coexist, +but adding both needs a demonstrated use for each boundary. + +The more fundamental unresolved choice is how a formal type parameter denotes an +inferred type. Two approaches merit a small comparison: + +- Keep supplied type values and ordinary lower/upper bounds as distinct kinds of + information on the contextual parameter. Constraints must be replayed as type + values arrive; positive observations must not become dependent on arrival order. +- Let an omitted argument denote an inference cell whose bounds are accumulated. + The formal parameter refers to that cell, while explicit arguments refer to + their supplied types. Matching an existing type argument then constrains or + binds that cell according to the matching rule. + +The second approach gives a clear meaning to a wrapper around an inferred type: +it wraps a reference to the cell. It does not establish that this is the smallest +or best change to this resolver. Neither approach should be selected merely to +make the current array regression pass. + +## Next design and implementation steps + +1. Specify the rules for supplying a type argument, adding lower/upper bounds, + and using a type to describe a value. Use separate operation names while + comparing the designs; choose any overloaded `<:` notation only after the + kinds of its operands determine the rule unambiguously. +2. Work through explicit `identity[Int](...)`, inference from two ordinary + arguments, `Array[Int]` against `Array[A]`, an initially empty mutable array, + and a callback using the same parameter in both polarities. Include constraints + arriving before and after the type argument and its observers. +3. Choose the inference-variable representation and wrapper placement from those + derivations before changing compiler code. Retain existing context identities + and consumer-owned hosts; a new symbol per mutable array is unnecessary. +4. Update `applyTypeArguments`, nominal argument matching in `inferTypeArguments`, + and annotation interpretation together. Split the ambiguously named + `listenTypeValues`, which currently returns positive instance shapes. Replace + constraint suppression for explicit arguments with delivery to the supplied + type. Audit the snapshot conversion in `instanceBindings` as well. +5. Verify constraints in both polarities, multiple supplied type values versus one + union type, ordinary inferred bounds, aliases, recursive propagation, separate + calls, and separate importers. Check constraint delivery directly when a loud + mismatch diagnostic is intentionally absent. Preserve generic-body checks, + exposed-function checking, and completed-reference targets. +6. Make `appendTyped` in `newres/MutableArrays.mls` pass using the common mechanism. + Run `ctest` before focused worksheets and `hkmc2AllTests/test` before completion; + review and commit the golden outputs. + +Precise array positions/lengths, loud concrete-mismatch diagnostics, optional +`splice` arguments, general storage reassignment, and handler inference remain +separate work. diff --git a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls index 666691292c..d4f61e5918 100644 --- a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls @@ -122,28 +122,15 @@ retained.0.value class Base class Child extends Base with val extra = 1 -let xs = (mut []: Array[Base]) +let xs = (mut [] as Array[Base]) xs.push(new Child) xs.0.extra -//│ ╔══[COMPILATION ERROR] Unexpected record key shape. -//│ ║ l.125: let xs = (mut []: Array[Base]) -//│ ╙── ^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.126: xs.push(new Child) -//│ ║ ^^^^^^^ -//│ ╟── Cannot resolve member 'push' across a computed key or unknown record spread. -//│ ╟── This computed key can overwrite the selected member. -//│ ║ l.125: let xs = (mut []: Array[Base]) -//│ ╙── ^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.127: xs.0.extra -//│ ║ ^^^^ -//│ ╟── Cannot resolve member '0' across a computed key or unknown record spread. -//│ ╟── This computed key can overwrite the selected member. -//│ ║ l.125: let xs = (mut []: Array[Base]) -//│ ╙── ^^ +//│ ║ ^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ xs = undefined +//│ xs = [Child { extra: 1 }] // Indexed and fill writes must invalidate initializer-only member assumptions. :ge @@ -153,10 +140,10 @@ let xs = mut [First(1)] set xs.[0] = Second(2) xs.0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.154: xs.0.first +//│ ║ l.141: xs.0.first //│ ║ ^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.153: set xs.[0] = Second(2) +//│ ║ l.140: set xs.[0] = Second(2) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -168,10 +155,10 @@ let xs = mut [First(1)] xs.fill(Second(2)) xs.0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.169: xs.0.first +//│ ║ l.156: xs.0.first //│ ║ ^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.168: xs.fill(Second(2)) +//│ ║ l.155: xs.fill(Second(2)) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -181,25 +168,25 @@ xs.0.first module Exposed with fun make() = mut [x => x.missing] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ l.169: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^ //│ ╟── Cannot resolve member 'missing' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ l.169: fun make() = mut [x => x.missing] //│ ║ ^ //│ ╟── This value is stored in this array. -//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ l.169: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── This value is returned here. -//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ l.169: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'make' is accessible here. -//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ l.169: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── 'Exposed' is exposed by this compilation unit. -//│ ║ l.181: module Exposed with +//│ ║ l.168: module Exposed with //│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ║ l.182: fun make() = mut [x => x.missing] +//│ ║ l.169: fun make() = mut [x => x.missing] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Explicit constructor type arguments remain opaque when methods receive subtypes. @@ -211,7 +198,7 @@ let xs = new mut Array[Base](0) xs.push(new Child) xs.map((x, ...) => x.extra) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.212: xs.map((x, ...) => x.extra) +//│ ║ l.199: xs.map((x, ...) => x.extra) //│ ║ ^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -233,7 +220,7 @@ let xs = mut [] xs.splice(0, 0, Item(2)) xs.0.value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.234: xs.0.value +//│ ║ l.221: xs.0.value //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' @@ -248,7 +235,7 @@ let xs = mut [] appendTyped(xs, Item(3)) xs.0.value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.249: xs.0.value +//│ ║ l.236: xs.0.value //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' From 6b0767292e9d5cd7d186e1c61ea1390485f462ab Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 21:53:59 +0800 Subject: [PATCH 090/228] refactor(resolution): retain instance type references and document constraint proposal --- doc/new-resolution-design.md | 11 +- doc/new-resolution-suite-migration.md | 7 +- doc/new-resolution-type-value-flow.md | 410 +++++++++--------- .../src/test/scala/hkmc2/CompilerTest.scala | 29 ++ .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../hkmc2/semantics/InterfaceExposure.scala | 10 +- .../scala/hkmc2/semantics/NewResolver.scala | 169 +++++--- .../hkmc2/semantics/NewResolverState.scala | 10 +- .../main/scala/hkmc2/semantics/Shape.scala | 24 +- .../test/mlscript/newres/GenericMethods.mls | 2 +- 10 files changed, 377 insertions(+), 297 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 6feb131891..c28f42a488 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -33,10 +33,13 @@ exported mapped arrays and imported generic functions and methods. ## Captures and generic flow -The planned distinction between passing type values and constraining ordinary -values is documented in [Type values, instances, and bounds](new-resolution-type-value-flow.md). -The description below records the current implementation, which does not yet -preserve that distinction. +`InstanceShape` retains a type reference at annotation boundaries; member lookup, +application, and destructuring obtain specialized interfaces through +`listenInstanceViews`. The distinction between supplying a type argument and +adding an ordinary bound, the agreed variance rules, and the pending constraint +representation are documented in [Instance types and parameter constraints](new-resolution-type-value-flow.md). +The description below records current generic inference, which still needs that +constraint extension. Class bodies introduce lexical captures, and a class and its constructor share one resolution boundary. A method's reference to an outer constructor must include diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index fce2c70c71..a35e0f8f08 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -91,9 +91,10 @@ The inventory below separates these from compiler and prelude gaps. `newres/MutationFlow.mls` records a reassigned array still checked against its initializer's tuple length; accumulating both shapes would still reject valid later indexing. `codegen/SetStmt` also lacks argument flow through - its update callback. Settle the representation in the - [type-value/instance design comparison](new-resolution-type-value-flow.md), then - implement it so `Array[A]` retains the actual element type's input and output uses; + its update callback. Instance wrappers are in place; the + [type-argument constraint proposal](new-resolution-type-value-flow.md) records + the agreed variance rules and the recursive capture-correlation issue that must + be resolved before `Array[A]` retains both input and output uses of its element type; the `appendTyped` case in `newres/MutableArrays.mls` records the missing write propagation. Member-variable definitions still need work. Handler inference needs separate flows for the receiver, values diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 86825e7d2c..50bb146f13 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -1,232 +1,226 @@ -# Type values, instances, and bounds in new resolution +# Instance types and parameter constraints -This note records the semantic requirements and compares possible representations. -The wrapper placement is undecided; the earlier plan to add `TypeValueShape` was -premature. No compiler implementation has been selected. The -[current resolver notes](new-resolution-design.md) describe existing behavior; -the [migration worklist](new-resolution-suite-migration.md) lists remaining ports. +`InstanceShape(T)` describes an instance of the type `T`. Types remain in the +`TypeShape`/`DeclaredType` graph. The instance wrapper replaces the earlier +nominal-only representation at annotation boundaries: parameter and result +annotations, ascriptions, generic arguments, and annotated tuple fields retain +a type reference until an operation requests its interface. -## The distinction to preserve +The wrapper refactor is implemented. Bidirectional type-argument constraints and +variance are the next step. The constraint representation proposed below still +needs agreement before implementation. See the [resolver notes](new-resolution-design.md) +for current behavior and the [migration worklist](new-resolution-suite-migration.md) +for remaining ports. -Supplying a type argument and adding a bound are different operations. If `Int` -is supplied as the type argument for a contextual parameter `A`, it affects both +## Type arguments are different from instance bounds + +Supplying `Int` as the type argument for a contextual parameter `A` affects both positive and negative uses of `A`: ```text A has the supplied type Int: -L <: A produces L <: Int -A <: U produces Int <: U +L <: A requires L <: Int +A <: U requires Int <: U ``` -In contrast, inferring `Int <: A` from an ordinary integer argument only adds a -lower bound. It does not supply `Int` as the meaning of `A` in all positions. -The distinction applies to input and output positions generally. Function -parameters reverse polarity; function results preserve it. Array operations are -one example, not the organizing principle of the design. +An ordinary integer argument to a parameter `x: A` contributes only a lower +bound. It does not supply `Int` as the meaning of every occurrence of `A`. +For `pair[A](x: A, y: A)`, an integer and a string therefore contribute two lower +bounds; neither argument must satisfy the other's type. If two distinct type values, `Int` and `Str`, reach the same activation of `A`, -each bound is applied to both. A lower bound `L <: A` produces both `L <: Int` -and `L <: Str`; an upper bound `A <: U` produces both `Int <: U` and `Str <: U`. -This differs from supplying the single type value `Int | Str`, against which -constraints must retain the meaning of that union type. - -Concrete mismatches such as `Str <: Int` may remain quiet for now. Constraints -must still reach the appropriate types, even when no loud diagnostic is emitted. - -## What can be inferred at a generic call - -“Preserve wrappers through inferred generic arguments” conflates two cases. - -**Inferring bounds from ordinary arguments.** For a function with parameters -`x: A` and `y: A`, supplying an integer and a string contributes lower bounds -`Int <: A` and `Str <: A`. It must not manufacture two supplied type values, -`Int` and `Str`: under the rule above, that would require each argument to satisfy -both types. An ordinary argument's inferred bound is not an exact type argument. - -One possible representation introduces a type inference variable `alpha` for -this call. The formal parameter `A` denotes `alpha`, and ordinary arguments add -bounds to `alpha`. A wrapper, if used, would refer to `alpha`, not to each shape -that becomes a lower bound of it. This is a conceptual inference cell, not a -requirement for a fresh global `Symbol`; the existing symbol plus call context -might identify it. Such a cell can retain ordinary shape bounds; this does not -require reconstructing a complete type expression for every closure or value. -Choosing this representation requires an explicit design. - -**Matching an existing type argument.** In the motivating `Array[Int]` against -`Array[A]` example, the actual array already carries an element type. Matching -must preserve that type's meaning in both input and output uses of `A`, rather -than extract integer instance shapes and forward only those lower bounds. -Likewise, an array with an inferred element parameter `E` must retain a live -reference to `E`, including its future constraints. Merely copying `E`'s current -positive candidates loses this information. - -This does not establish a rule that every nominal type-argument match binds an -exact type. The treatment of a parameter used only covariantly or contravariantly -must follow the chosen matching/variance rules. Neither wrapper placement by -itself determines those rules. - -## Option 1: wrap type values - -Use `TypeValueShape(T)` for a type carried by the flow graph. Both ends of a -type-value constraint should be represented explicitly: +each bound must apply to both. `L <: A` requires both `L <: Int` and `L <: Str`; +`A <: U` requires both `Int <: U` and `Str <: U`. Supplying the single type value +`Int | Str` is different: its complete type expression remains the constraint +target. Decomposing a union in positive position does not justify decomposing it +conjunctively in negative position. + +Concrete mismatches such as `Str <: Int` may remain quiet during this migration. +The absence of a loud diagnostic does not authorize dropping constraints before +they reach the relevant type or parameter. + +These rules concern inputs/outputs, or equivalently lower/upper bounds. Function +parameters reverse polarity and function results preserve it. Array operations +are one application of the rules. + +## Existing symbols and marks identify inference + +**Do not allocate fresh type variables for calls, arrays, or constraint matches.** +Fresh variables would prevent recursive inference from returning to existing +graph nodes and reaching a fixed point. A contextual reference uses the existing +parameter symbol, its originating inference host, and marks. In particular, a +mutable array uses the builtin `Array` element parameter with its allocation and +call contexts. Actual element shapes contribute bounds to that parameter. + +Keeping a reference to an unresolved parameter is essential. Copying its current +positive candidates loses its negative uses and its identity as a type argument. +Subscriptions must also account for constraints or candidates arriving later. +Graph caches and listeners belong to the consuming `NewResolverState`; extending +a consumer must not mutate a prelude or an exporter's inference graph. + +## Instance wrappers and interface observations + +`InstanceShape` holds a `DeclaredType`, including its lexical bindings. +`listenTypeInstances` supplies this wrapper; `listenInstanceViews` interprets it +for an operation such as selection, application, or destructuring. The cached +`listenTypeViews` observations reuse the specialized nominal, callable, record, +and tuple interfaces. `NominalInstanceView` is the nominal member-lookup view +previously called `NominalTypeShape`. + +For example, an instance viewed as `Base` exposes `Base`'s declarations even if +the implementation is a `Child`. Interpreting a function type exposes its declared +domain and result. Interpreting a union for member lookup can observe each +alternative, while transporting its wrapper preserves the original union node. + +This refactor alone does not implement the planned distinction between supplying +a type argument and adding an ordinary bound. In particular, the current +`inferTypeArguments` still expands wrappers when inferring from an instance, and +explicit arguments still suppress ordinary refinement. The proposed constraint +work must replace these behaviors together. + +## Variance rules to implement + +Follow InvalML's argument interpretation in +[`typeAndSubstType`](../hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala) +and argument comparison in +[`constrainArgs`](../hkmc2/shared/src/main/scala/hkmc2/invalml/ConstraintSolver.scala). +Its [`TypeArg`](../hkmc2/shared/src/main/scala/hkmc2/invalml/types.scala) exposes an +input part (`negPart`) and an output part (`posPart`): + +| Argument | Input part | Output part | +| --- | --- | --- | +| `S` | `S` | `S` | +| `in T` | `T` | `Any` | +| `out U` | `Nothing` | `U` | + +Thus `S` means `in S out S`. The internal pair can also represent a written +`in T out U`. For an unqualified argument, a declaration's `in` or `out` annotation +selects the corresponding form. An explicitly written use-site wildcard supplies +its own parts, as it does in InvalML; it is not combined by guessing a variance. + +For actual argument `a` and expected argument `b`, require: ```text -TypeValueShape(Int) <: TypeValueShape(A) +a.output <: b.output +b.input <: a.input ``` -Here the outer `<:` is the graph's relation between type-valued shapes. Its -processing rule must preserve both uses of the supplied type. It must not simply -strip the wrappers and install the ordinary lower bound `Int <: A`. -The earlier notation `TypeValueShape(Int) -> A` described publication into a -parameter host while leaving the host's meaning implicit. It was not an adequate -specification of a constraint between shapes. - -The wrapper must refer to a type description or a contextual type parameter, -including its originating host. It needs an explicit interpretation on both -sides of a constraint. Ordinary values described by that type remain represented -separately, through the existing instance shapes or another explicit operation. -For example, a parameter annotated `x: A` expects an instance of `A`, not a type -value; wrapping the type argument alone does not represent that expectation. - -Advantages: - -- Type arguments have an explicit tag when sharing transport with ordinary value - shapes. This makes accidental publication of an instance as a type argument - detectable. -- Explicit type application and forwarding of existing type arguments have a - direct representation, including nested type expressions and captured type - parameters. -- The current graph uses `Publisher.Data[Shape]`, so a new type-valued alternative - can fit that transport, provided the context operations are extended as well. - -Costs: - -- A symmetric source/target interpretation is needed; a wrapper around published - candidates alone is insufficient. -- Ordinary annotated values still need a separate representation or conversion. - Function constraints, member selection, and mixed type/value constraints must - consistently distinguish the two levels. -- `ShapeLike.enter` and `exit` currently return `TermShape | NoShape`. Transporting - type-valued wrappers requires changing that contract or representing their - contexts separately. -- Ordinary inference still needs a model for bounds. Wrapping every observed - instance shape would incorrectly turn inferred lower bounds into supplied types. - -## Option 2: wrap instances described by types - -Keep type descriptions as types and use `InstanceShape(T)` for an ordinary value -view described by `T`. The wrapper can occur in positive or negative position; -its name does not restrict it to a producer. +A plain invariant `Array[Int]` against `Array[A]` therefore retains both uses of +the element type. `Array[out A]` admits only the output connection; `Array[in A]` +admits only the input connection. Substitution into member types must select the +appropriate part at each polarity, including nested arrows. Copy InvalML's +variance semantics, not its fresh inference-variable implementation. + +## Why two independent candidate-copying edges are insufficient + +An experimental implementation made `appendTyped` propagate elements into an +initially empty mutable array and passed the existing declaration worksheets. +However, this recursive example exposed an activation-correlation failure: + +```mlscript +class Item(val value: Int) +fun append[A](xs: Array[A], value: A, n: Int) = + if n > 0 then append(xs, value, n - 1) else xs.push(value) +let xs = mut [] +append(xs, Item(5), 2) +xs.0.value +``` + +The experiment reported an unknown element type originating from the generic +body's abstract activation of `A`. That candidate must remain confined to that +abstract activation. The experimental bound-copying implementation is not part +of the retained compiler changes. + +The failure comes from independently applying an existing capture path and its +reverse to already expanded candidates. Write `enter(f, s)` and `exit(f, s)` for +marks at function `f` and site `s`, and `*` for a capture that matches any site. +A candidate starting at `enter(f, abstract)` can travel as follows: ```text -shape of an integer <: InstanceShape(A) - contributes an integer lower bound to A +forward: + enter(f, abstract) -- exit(f, *) --> no mark + -- enter(f, recursiveCall) --> enter(f, recursiveCall) -InstanceShape(A) <: an expected value interface - constrains A in the corresponding output use +independent reverse: + enter(f, recursiveCall) -- exit(f, recursiveCall) --> no mark + -- enter(f, *) --> enter(f, *) ``` -With `Int` supplied for `A`, these operations use `Int` in the appropriate -polarity. A value viewed through `InstanceShape(Base)` exposes `Base`'s interface; -a supplied `Child` produces a constraint against `Base` without replacing that -view with `Child`'s more specific interface. - -In this representation, matching -`InstanceShape(Array[Int])` against `InstanceShape(Array[A])` reaches a relation -between the element types. It must implement the agreed `Array` behavior above. -The instance wrapper alone does not decide whether a nested relation is a type -binding, a lower/upper bound, or both bounds for an invariant parameter. - -Advantages: - -- The conversion from a type description to an ordinary value use is explicit. - Parameter annotations, result annotations, and member interfaces all use the - same distinction, in either polarity. -- This is close to the existing split between `TypeShape`/`DeclaredType` and - `TermShape`. Despite its name, `NominalTypeShape` is already a `TermShape` - describing an instance; its description is “value of type ...”. -- A live `InstanceShape(A)` can retain the type reference while `A` is unresolved, - instead of immediately expanding it into whichever instance candidates happen - to be known. This may make delayed constraints easier to handle, though it - still requires an order-independent propagation rule. -- It provides a natural place to distinguish an annotated value's permitted - interface from a more specific implementation shape. - -Costs: - -- Type-argument flow still needs an explicit representation or API. Removing - `TypeValueShape` does not make ordinary `T <: A` lower-bound propagation stand - for type instantiation. -- `NominalTypeShape`, `CallableTypeShape`, `RecordTypeShape`, and annotated tuples - already provide specialized instance views. A general wrapper must organize or - reuse these views, rather than add a second path with different behavior. -- Contexts for the type reference and for the ordinary value must remain correct - when the type contains captured parameters or an instance is passed through a - function. Moving the wrapper does not remove this requirement. -- Positive and negative interpretation cannot use the same eager expansion for - every type constructor. Function parameters reverse direction; union and - intersection constraints need their own rules. - -## Comparison and current recommendation - -| Concern | Type-value wrapper | Instance wrapper | -| --- | --- | --- | -| Boundary made explicit | A type enters the shared shape graph. | A type describes an ordinary value use. | -| Existing type-argument matching | Directly carries type-valued shapes. | Uses type references and a separate matching operation. | -| Annotated term values | Retains existing instance views or adds a conversion. | Directly represented by the wrapper. | -| Ordinary generic inference | Needs bounds distinct from supplied type values. | Needs bounds distinct from supplied type values. | -| Fit with current representations | Extends the shared shape transport. | Builds on the current type-description/instance-view split. | - -The current recommendation is to explore the instance wrapper first, because it -makes the meaning of annotated term shapes explicit and fits the existing type -interpretation graph. This is a preference, not an implementation decision. -A type-value wrapper remains useful if representing types inside the same shape -constraint language is a deliberate goal. The representations can also coexist, -but adding both needs a demonstrated use for each boundary. - -The more fundamental unresolved choice is how a formal type parameter denotes an -inferred type. Two approaches merit a small comparison: - -- Keep supplied type values and ordinary lower/upper bounds as distinct kinds of - information on the contextual parameter. Constraints must be replayed as type - values arrive; positive observations must not become dependent on arrival order. -- Let an omitted argument denote an inference cell whose bounds are accumulated. - The formal parameter refers to that cell, while explicit arguments refer to - their supplied types. Matching an existing type argument then constrains or - binds that cell according to the matching rule. - -The second approach gives a clear meaning to a wrapper around an inferred type: -it wraps a reference to the cell. It does not establish that this is the smallest -or best change to this resolver. Neither approach should be selected merely to -make the current array regression pass. - -## Next design and implementation steps - -1. Specify the rules for supplying a type argument, adding lower/upper bounds, - and using a type to describe a value. Use separate operation names while - comparing the designs; choose any overloaded `<:` notation only after the - kinds of its operands determine the rule unambiguously. -2. Work through explicit `identity[Int](...)`, inference from two ordinary - arguments, `Array[Int]` against `Array[A]`, an initially empty mutable array, - and a callback using the same parameter in both polarities. Include constraints - arriving before and after the type argument and its observers. -3. Choose the inference-variable representation and wrapper placement from those - derivations before changing compiler code. Retain existing context identities - and consumer-owned hosts; a new symbol per mutable array is unnecessary. -4. Update `applyTypeArguments`, nominal argument matching in `inferTypeArguments`, - and annotation interpretation together. Split the ambiguously named - `listenTypeValues`, which currently returns positive instance shapes. Replace - constraint suppression for explicit arguments with delivery to the supplied - type. Audit the snapshot conversion in `instanceBindings` as well. -5. Verify constraints in both polarities, multiple supplied type values versus one - union type, ordinary inferred bounds, aliases, recursive propagation, separate - calls, and separate importers. Check constraint delivery directly when a loud - mismatch diagnostic is intentionally absent. Preserve generic-body checks, - exposed-function checking, and completed-reference targets. -6. Make `appendTyped` in `newres/MutableArrays.mls` pass using the common mechanism. - Run `ctest` before focused worksheets and `hkmc2AllTests/test` before completion; - review and commit the golden outputs. +The reverse traversal has forgotten `abstract`. Its wildcard can then match a +real caller's exit. This demonstrates loss in the candidate-copying approach; +it does not establish that the existing mark representation itself is inadequate. +Discarding the abstract candidate, suppressing reverse propagation for recursion, +or truncating paths would conceal the loss rather than preserve the constraint. + +## Proposed extension: retain both contextual type references + +Represent an argument relation as a persistent constraint between two type +references, each retaining its original host, bindings, and marks. Keep the +argument's input/output parts on this relation. Do not immediately replace it +with two unrelated subscriptions that copy positive candidates. + +A capture match must belong to the relation traversal. When a wildcard matches +`enter(f, abstract)`, retain that matched activation while transporting the bound +through the opposite endpoint. A reverse obligation uses the same match; it +must not recreate `enter(f, *)`. For a concrete caller, both endpoints instead +use that caller's match. Deferred propagation must retain this association too. + +Conceptually, the recursive relation is a family: + +```text +A at recursiveCall(caller) relates to A at caller +``` + +`caller` here names a shared capture match, not a fresh type variable or a new +runtime symbol. The current independent edges effectively erase it on the +recursive side. Retaining type references allows a constraint to postpone that +match until it has an activation to use, instead of forwarding every currently +observed lower bound through an unconditional reverse edge. + +Proposed implementation steps: + +1. Introduce immutable contextual type-reference endpoints and memoized argument + relations in `NewResolverState`. Reuse existing `TypeResolution` nodes and + parameter hosts. Keep supplied type references distinct from ordinary bounds. +2. Implement capture matching that records the normalized path matched by a + capture and reuses it across the relation's two endpoints. Keep this data in + constraint facts or their subscriptions, never mutable global symbol fields. +3. Deliver lower and upper obligations to each supplied type reference. A plain + inferred value adds a lower bound; explicit instantiation and invariant + argument matching must preserve the supplied type's uses in both positions. +4. Interpret declaration/use-site variance using the input/output parts above. + Use the same constraint mechanism for arrays, generic classes, and functions. +5. Only then replace `applyTypeArguments`, nominal inference, explicit-argument + suppression, and the positive-only conversion in `instanceBindings` together. + +The unresolved implementation question is the finite representation of capture +matches under recursive relation composition. Merely storing an ever-growing +history of matches would repeat the fresh-variable termination problem. Before +landing this extension, demonstrate that recursive processing revisits memoized +relations over existing nodes and normalized contexts, including nested type +applications and mutually recursive calls. If that requires changing mark +normalization or the domain of contexts, report that design separately. + +## Acceptance checks + +- An initially empty mutable array receives an element through `Array[A]`. +- Two calls using different arrays and incompatible element interfaces remain + independent, including two callers of the recursive example above. +- Recursive input/output relations terminate without fresh type variables, + repeated-boundary paths, or unbounded binding-environment construction. +- Declaration-site `in`/`out`, use-site `in`/`out`, their overrides, and nested + function polarity agree with InvalML. +- Explicit function type arguments constrain callback inputs even when the + function never calls the callback locally. +- Two supplied types at one activation each receive every relevant bound; one + supplied union remains a single negative constraint target. Inspect delivery + directly where concrete mismatch diagnostics are intentionally quiet. +- Candidates arriving before or after relations produce the same result. + Separate importers leave the exporter and prelude hosts unchanged. +- Generic-body checking, interface exposure, and completed member targets remain + intact. Run `ctest` before focused worksheets and `hkmc2AllTests/test` before + completion; review and commit the generated golden outputs. Precise array positions/lengths, loud concrete-mismatch diagnostics, optional `splice` arguments, general storage reassignment, and handler inference remain diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index 10f484cfa4..85450c14aa 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -345,6 +345,35 @@ class CompilerTest extends AnyFunSuite: assert(parameters.map(p => (p.shapes.toVector, p.shapeListeners.toVector)) == before, "A consumer must not change the cached definition's inference graph") + test("a supplied compound type stays inside one instance wrapper"): + import semantics.{InstanceShape, Marked, TermShape, TypeShape} + val fs = new InMemoryFileSystem(loadStandardLibrary()) + given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace = false + given Raise = diagnostic => fail(diagnostic.toString) + fs.write("/Types.mls", """#lang(0.3.x, strictResolution: true) + |module Types with + | fun identity[A](x: A) = x + |Types.identity[Int | Str](1) + |""".stripMargin) + val compiler = new MLsCompiler(_ => summon[Raise]) + compiler.compileModule(Path("/Types.mls")) + val prelude = cctx.getPrelude(paths.preludeFile).ctx + val unit = cctx.getElaboratedBlock(io.Path("/Types.mls"), prelude) + val module = unit.compilationUnit.defaultExport.get.asModOrObj.get.defn.get + val parameter = module.body.members("identity").asTrm.get.defn.get.tparams.get.head.sym + val supplied = parameter.shapes.toList.flatMap: + case value: TermShape => value match + case Marked(instance: InstanceShape, _) => instance.tpe.resolution :: Nil + case _ => Nil + case _ => Nil + assert(supplied.length == 1, "A union argument must not become two supplied type arguments") + assert(supplied.head.shapes.toList match + case (_: TypeShape.Union) :: Nil => true + case _ => false) + test("mutable array parameter flow stays private to each importer"): val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 774239bab6..1cb4e7580a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -2658,7 +2658,7 @@ extends Importer: val p = Param(flg, sym, sig, mfn) sym.decl = S(p) if newResolution then sig.foreach: sign => - listenTypeValues(sign): shape => + listenTypeInstances(sign): shape => if sym.currentShapes.add(shape) then sym.notifyShapeListeners(shape) (p, spd, aliases) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index e15f18d7e2..c5d8157d1f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -32,7 +32,7 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) */ private def watch(key: Any)(connect: Listener => Unit)(receive: Listener)(using state: NewResolverState): Unit = if watched.add((key, state)) then - val host = new TypeValues + val host = new TermShapeHost connect(host.publish) detach += host.inferenceHost.observe: shape => val current = rstate @@ -104,12 +104,14 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) val declared = resolver.declaredType(resolver.typeResolution(sign), Map.empty) watch((new Identity(body), declared, marks))(resolver.listenTerm(body)): shape => resolver.constrainFunction(declared, shape, marks) - watch((declared, marks))(resolver.listenTypeValues(declared)): shape => + watch((declared, marks))(resolver.listenTypeInstances(declared)): shape => emit(shape.exit(marks), path) private def value(shape: TermShape, path: Path)(using NewResolverState): Unit = val (head, marks) = shape.applicationHead head match + case _: InstanceShape => + watch((shape, "instance view"))(resolver.listenInstanceViews(shape))(emit(_, path)) case ds: DefnShape if ds.defn.sym.getState is rstate.owner => parameters(shape.unappliedParams, path) ds.clsDef match @@ -142,9 +144,9 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) case base: BaseShape => members(base.defn, marks, path) case callable: CallableTypeShape => callable.result.foreach: result => - watch((result, marks))(resolver.listenTypeValues(result)): shape => + watch((result, marks))(resolver.listenTypeInstances(result)): shape => emit(shape.exit(marks), path) - case nominal: NominalTypeShape => + case nominal: NominalInstanceView => // Array element bindings may contain escaping closures. Follow only the // declared binding; concrete annotations still hide implementation shapes. resolver.arrayElementType(nominal).foreach: binding => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 2d560d9ded..80bb02ccf6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -165,7 +165,7 @@ class NewResolver: case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => bindings(symbol) case _ => DeclaredType(resolution, bindings) - private def typeValues(using rs: NewResolverState) = rs.typeValues + private def typeViews(using rs: NewResolverState) = rs.typeViews private def abstractTypes(using rs: NewResolverState) = rs.abstractTypes // An omitted argument in an annotation must not subscribe to constructor // inference. Keep its parameter and annotation as a diagnostic witness: the @@ -183,15 +183,33 @@ class NewResolver: * available. Subscribe to the type graph, never to values flowing into it. * Shared hosts retain subscriptions for forward and recursive type references. */ - def listenTypeValues(sign: Term)(listener: Listener)(using NewResolverState): Unit = - listenTypeValues(declaredType(typeResolution(sign), Map.empty))(listener) + def listenTypeInstances(sign: Term)(listener: Listener)(using NewResolverState): Unit = + listenTypeInstances(declaredType(typeResolution(sign), Map.empty))(listener) - private[semantics] def listenTypeValues(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = - typeValues.get(tpe) match + private[semantics] def listenTypeInstances(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = + listener(InstanceShape(tpe)) + + /** Expanding a type is an observation, not a type-argument constraint. Cache + * observations before following parameters so recursive interfaces reach the + * same graph nodes instead of allocating fresh inference variables. + */ + private[semantics] def listenInstanceViews(value: TermShape)(listener: Listener)(using NewResolverState): Unit = value match + case Marked(instance: InstanceShape, marks) => + listenTypeViews(instance.tpe): shape => + shape.exit(marks) match + case value: TermShape => listener(value) + case NoShape => () + case _ => listener(value) + + private def listenTermViews(term: Term)(listener: Listener)(using NewResolverState): Unit = + listenTerm(term)(shape => listenInstanceViews(shape)(listener)) + + private def listenTypeViews(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = + typeViews.get(tpe) match case S(host) => host.listen(listener) case N => - val host = new TypeValues - typeValues(tpe) = host + val host = new TermShapeHost + typeViews(tpe) = host host.listen(listener) def follow(current: DeclaredType, args: Ls[DeclaredType], aliases: Set[TypeResolution], publish: Listener)(using NewResolverState): Unit = current.resolution.listen: shape => @@ -201,16 +219,16 @@ class NewResolver: def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current.bindings), Nil, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) - case TypeShape.Inferred(value) => publish(value) + case TypeShape.Inferred(value) => listenInstanceViews(value)(publish) case TypeShape.Nominal(defn) => val bindings = bind(defn.tparams) defn.ext match - case N => publish(NominalTypeShape(defn, bindings, implicitParent(defn))(S(tpe.resolution.source))(this)) + case N => publish(NominalInstanceView(defn, bindings, implicitParent(defn))(S(tpe.resolution.source))(this)) case S(parent) => // Only the parent's declared type is relevant here. Evaluating // its constructor arguments would reintroduce implementation flow. - listenTypeValues(declaredType(typeResolution(parent.cls), bindings)): ext => - publish(NominalTypeShape(defn, bindings, S(ext))(S(tpe.resolution.source))(this)) + listenTypeViews(declaredType(typeResolution(parent.cls), bindings)): ext => + publish(NominalInstanceView(defn, bindings, S(ext))(S(tpe.resolution.source))(this)) case TypeShape.Alias(symbol, rhs) => // Revisiting the same alias reference without reaching an outer // nominal/function shape supplies no additional interface. Track @@ -236,7 +254,7 @@ class NewResolver: msg"This type annotation supplies the value's shape." -> tpe.resolution.source.toLoc)).exit(marks) case _ => value annotated match - case value: TermShape => publish(value) + case value: TermShape => listenInstanceViews(value)(publish) case NoShape => () case TypeShape.Captured(base, thru) => follow(declaredType(base, current.bindings), args, aliases, @@ -269,13 +287,13 @@ class NewResolver: def registerParameterSignature(paramLists: Ls[ParamList], sign: Term)(using NewResolverState): Unit = def bind(paramLists: Ls[ParamList], tpe: DeclaredType)(using NewResolverState): Unit = paramLists match case Nil => () - case ps :: tail => listenTypeValues(tpe): + case ps :: tail => listenTypeViews(tpe): case callable: CallableTypeShape => val declared = callable.paramLists.head if !declared.hasRest then ps.params.zip(declared.params).foreach: (param, sign) => if param.sign.isEmpty then sign.foreach: tpe => signatureParameters(param.sym) = tpe - listenTypeValues(tpe): shape => + listenTypeInstances(tpe): shape => if param.sym.currentShapes.add(shape) then param.sym.notifyShapeListeners(shape) callable.result.foreach(bind(tail, _)) case _ => () @@ -327,13 +345,13 @@ class NewResolver: declaredType(typeResolution(sign), scopedBindings) val result = resultSignature(td).map(signature) if td.sign.nonEmpty && (td.k is syntax.Fun) && !td.flags.hasResultAnnotation then - listenTypeValues(signature(td.sign.get))(publish) + listenTypeViews(signature(td.sign.get))(publish) else if td.params.nonEmpty then publish(CallableTypeShape(source, td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature))), result, Nil)) else result match - case S(tpe) => listenTypeValues(tpe)(publish) + case S(tpe) => listenTypeInstances(tpe)(publish) case N => // Keep the missing constructor annotation on the unknown value so // later selections and calls can explain why argument flow is hidden. @@ -397,7 +415,7 @@ class NewResolver: "argument".pluralized(params.length)}, but got ${args.length}" -> callee.toLoc :: Nil) params.zip(args).foreach: (param, arg) => rstate.markExplicitTypeArgument(param.symbol, marks) - listenTypeValues(arg): tpe => + listenTypeInstances(arg): tpe => publishParameter(param.host, tpe.enter(marks)) // Install each constraint edge before subscribing: callback parameter/result @@ -405,6 +423,11 @@ class NewResolver: private def typeConstraints(using rs: NewResolverState) = rs.typeConstraints private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = if !typeConstraints.add((tpe, value, marks)) then return + value match + case Marked(_: InstanceShape, _) => + listenInstanceViews(value)(inferTypeArguments(tpe, _, marks)) + return + case _ => () def follow(tpe: DeclaredType, args: Ls[DeclaredType], captures: Ls[Marks], seen: Set[TypeResolution])(using NewResolverState): Unit = if !seen(tpe.resolution) then val next = seen + tpe.resolution @@ -446,16 +469,16 @@ class NewResolver: shape.exit(context) match case actual: TermShape => inferTypeArguments(pattern, actual, marks) case NoShape => () - def constrainNominal(nominal: NominalTypeShape)(using NewResolverState): Unit = + def constrainNominal(nominal: NominalInstanceView)(using NewResolverState): Unit = if nominal.defn is cls then cls.tparams.zip(args).foreach: (param, pattern) => nominal.bindings.get(param.sym).foreach: bound => - listenTypeValues(bound): shape => + listenTypeInstances(bound): shape => shape.exit(context) match case actual: TermShape => inferTypeArguments(pattern, actual, marks) case NoShape => () head match case ds: DefnShape if ds.clsDef.contains(cls) => cls.tparams.zip(args).foreach(constrain) - case nominal: NominalTypeShape => constrainNominal(nominal) + case nominal: NominalInstanceView => constrainNominal(nominal) case tuple: TupleShape => constrainNominal(tuple.arrayParent) case _ => () case _ => () // Concrete annotations are opaque, irrespective of the argument value. @@ -492,6 +515,11 @@ class NewResolver: * calls through that interface continue to expose only the declared result. */ private[semantics] def constrainFunction(expected: DeclaredType, actual: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = + actual match + case Marked(_: InstanceShape, _) => + listenInstanceViews(actual)(constrainFunction(expected, _, marks)) + return + case _ => () val context = actual.applicationHead._2 def results(listener: Listener)(using NewResolverState): Unit = actual.applicationHead._1 match case intro: IntroShape => intro.trm match @@ -505,7 +533,7 @@ class NewResolver: case _ => () case _ => () def matchLists(expected: DeclaredType, remaining: Ls[(ParamList, Ls[Marks])])(using NewResolverState): Unit = - listenTypeValues(expected): + listenTypeViews(expected): case Marked(callable: CallableTypeShape, expectedContext) => remaining match case (params, _) :: tail => val declared = callable.paramLists.head @@ -516,7 +544,7 @@ class NewResolver: else params.params.zip(declared.params).foreach: (param, sign) => sign.foreach: tpe => - listenTypeValues(tpe): shape => + listenTypeInstances(tpe): shape => constrainParameter(param, shape.exit(expectedContext).enter(context), context) params.restParam.foreach: rest => val fields = declared.params.drop(params.params.length).map: @@ -540,7 +568,7 @@ class NewResolver: case _ => () actual match case Marked(givenType: CallableTypeShape, actualContext) => - listenTypeValues(expected): + listenTypeViews(expected): case Marked(wanted: CallableTypeShape, expectedContext) => val domain = givenType.paramLists.head val expectedDomain = wanted.paramLists.head @@ -549,7 +577,7 @@ class NewResolver: else domain.params.zip(expectedDomain.params).foreach: case (S(givenParam), S(wantedParam)) => - listenTypeValues(wantedParam): shape => + listenTypeInstances(wantedParam): shape => shape.exit(expectedContext).enter(actualContext) match case value: TermShape => inferTypeArguments(givenParam, value, context) case NoShape => () @@ -559,14 +587,14 @@ class NewResolver: case value: TermShape => inferTypeArguments(ret, value, marks) case NoShape => () givenType.paramLists.tail match - case Nil => givenType.result.foreach(listenTypeValues(_)(receive)) + case Nil => givenType.result.foreach(listenTypeInstances(_)(receive)) case tail => receive(givenType.copy(paramLists = tail)) case _ => () case _ => matchLists(expected, actual.unappliedParams) private def listenSignatureResult(tpe: DeclaredType, count: Int)(listener: Listener)(using NewResolverState): Unit = - if count == 0 then listenTypeValues(tpe)(listener) - else listenTypeValues(tpe): + if count == 0 then listenTypeInstances(tpe)(listener) + else listenTypeViews(tpe): case Marked(callable: CallableTypeShape, _) => callable.result.foreach(listenSignatureResult(_, count - 1)(listener)) case _ => () @@ -576,14 +604,14 @@ class NewResolver: * Cache the parent before following fields: a recursive tuple can require its * own Array interface while one of those fields is still being resolved. */ - private[semantics] def tupleArrayParent(tuple: TupleShape)(using NewResolverState): NominalTypeShape = + private[semantics] def tupleArrayParent(tuple: TupleShape)(using NewResolverState): NominalInstanceView = tupleArrayParents.get(new Identity(tuple)) match case S(parent) => parent case N => val cls = prelude.builtins.Array.defn.get softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") val elements = new TypeResolution(tuple.source, messages => resolError(tuple.source, messages)) - val parent = NominalTypeShape(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), implicitParent(cls))(N)(this) + val parent = NominalInstanceView(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), implicitParent(cls))(N)(this) tupleArrayParents(new Identity(tuple)) = parent tuple.segments.foreach: case field: TupleShape.Fixed => listenTupleField(field): value => @@ -609,7 +637,7 @@ class NewResolver: val context = ExitMark(ResolutionBoundary(cls.sym), S(site), NoMarks) val elements = new TypeResolution(source, messages => resolError(source, messages)) elements.publish(TypeShape.Parameter(param, param.inferenceHost)) - val array = NominalTypeShape(cls, Map(param -> declaredType(elements, Map.empty)), implicitParent(cls))(N)(this) + val array = NominalInstanceView(cls, Map(param -> declaredType(elements, Map.empty)), implicitParent(cls))(N)(this) val shape = MarkedShape(array, context) rstate.mutableArrays(new Identity(source)) = shape listenTerm(underlying): tuple => @@ -617,7 +645,7 @@ class NewResolver: publishParameter(param, element.enter(context :: Nil)) shape - private[semantics] def arrayElementType(array: NominalTypeShape): Opt[DeclaredType] = + private[semantics] def arrayElementType(array: NominalInstanceView): Opt[DeclaredType] = val cls = prelude.builtins.Array.defn.get array.ancestor(cls).flatMap(_.bindings.get(cls.tparams.head.sym)) @@ -630,10 +658,10 @@ class NewResolver: case DynSel(prefix, _, true) => S(prefix) case _ => N receiver.foreach: prefix => - listenTerm(prefix): array => + listenTermViews(prefix): array => val (head, context) = array.applicationHead head match - case nominal: NominalTypeShape => arrayElementType(nominal).foreach: element => + case nominal: NominalInstanceView => arrayElementType(nominal).foreach: element => listenTerm(rhs): value => value.enter(context) match case value: TermShape => inferTypeArguments(element, value, context) @@ -646,7 +674,7 @@ class NewResolver: case NoShape => () field match case TupleShape.Field(field, _) => listenTerm(field.term)(receive) - case TupleShape.TypedField(tpe, _) => listenTypeValues(tpe)(receive) + case TupleShape.TypedField(tpe, _) => listenTypeInstances(tpe)(receive) case TupleShape.UnknownField(source, _) => receive(UnknownValueShape.at(source)) case TupleShape.ValueField(value, _) => receive(value) @@ -670,11 +698,11 @@ class NewResolver: head match case tuple: TupleShape => listenArrayElements(tuple.arrayParent)(receive) - case nominal: NominalTypeShape => nominal.ancestor(cls) match + case nominal: NominalInstanceView => nominal.ancestor(cls) match case S(array) => array.bindings.get(param) match case S(bound) => - listenTypeValues(bound)(receive) + listenTypeInstances(bound)(receive) true case N => false case N => false @@ -762,7 +790,7 @@ class NewResolver: private def checkArgumentArity(args: Term, expected: Int, rest: Bool, src: Term, callable: TermShape)(matched: ShapeListener[(TupleShape, Marks)])(using NewResolverState): Unit = val reportKey = new Object - listenTerm(args): + listenTermViews(args): case Marked(tuple: TupleShape, marks) => val known = tuple.segments.count(_.isInstanceOf[TupleShape.Fixed]) val unknown = known != tuple.segments.length @@ -862,6 +890,11 @@ class NewResolver: * nominal class; guards and literal tests may conservatively retain shapes. * Both scrutinee and constructor shapes can arrive after this registration. */ def matchShapePat(shape: Shape, pattern: Pattern)(matched: ShapeListener[Shape])(using NewResolverState): Unit = + shape match + case value @ Marked(_: InstanceShape, _) if pattern.isInstanceOf[Pattern.Constructor] || pattern.isInstanceOf[Pattern.Tuple] => + listenInstanceViews(value)(matchShapePat(_, pattern)(matched)) + return + case _ => () pattern match case al @ Pattern.Alias(pat, _) => matchShapePat(shape, pat): sh => @@ -893,17 +926,18 @@ class NewResolver: matchShapePat(field, pat)(_ => ()) case MemberLookup.Declared(member, bindings, marks, annotation) => listenDeclaredMember(member, bindings, resSym, ctor.target, annotation, _ => (), false): field => - field.exit(marks) match - case value: TermShape => - val field = (value.applicationHead._1, unknown) match - case (_: UnknownValueShape, S(Marked(origin: UnknownValueShape, marks))) => - UnknownValueShape(origin.source)(origin.provenance.via( - msg"This constructor field may contain a value of unknown shape." -> member.toLoc)).exit(marks) - case _ => value - field match - case field: TermShape => matchShapePat(field, pat)(_ => ()) - case NoShape => () - case NoShape => () + listenInstanceViews(field): field => + field.exit(marks) match + case value: TermShape => + val field = (value.applicationHead._1, unknown) match + case (_: UnknownValueShape, S(Marked(origin: UnknownValueShape, marks))) => + UnknownValueShape(origin.source)(origin.provenance.via( + msg"This constructor field may contain a value of unknown shape." -> member.toLoc)).exit(marks) + case _ => value + field match + case field: TermShape => matchShapePat(field, pat)(_ => ()) + case NoShape => () + case NoShape => () case _ => // classPattern only publishes fields present in cls.body. // The nominal test above admits only that class's instances, @@ -926,12 +960,12 @@ class NewResolver: arg.publish(TypeShape.Inferred(sh)) val applied = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) applied.publish(TypeShape.Applied(base, cls.tparams.map(_ => arg))) - listenTypeValues(declaredType(applied, Map.empty))(check(_, S(sh))) + listenTypeViews(declaredType(applied, Map.empty))(check(_, S(sh))) case Marked(opaque: OpaqueTypeShape, marks) => UnknownValueShape.at(opaque.source).exit(marks) match case value: TermShape => narrow(value) case NoShape => () - case Marked(nominal: NominalTypeShape, _) if !nominal.isInstanceOfClass(cls) => + case Marked(nominal: NominalInstanceView, _) if !nominal.isInstanceOfClass(cls) => // A base-class annotation can contain a subclass instance. Its // constructor test exposes that subclass's declarations; omitted // subclass type arguments remain abstract, never inferred from @@ -943,7 +977,7 @@ class NewResolver: resolution.publish(TypeShape.Nominal(cls)) declaredType(resolution, Map.empty) }) - listenTypeValues(tested): candidate => + listenTypeViews(tested): candidate => if candidate.isInstanceOfClass(nominal.defn) then check(candidate, N) case _ => check(sh, N) shape match @@ -1006,10 +1040,10 @@ class NewResolver: case Marked(unknown: UnknownValueShape, marks) => tupleBindings(TupleShape(unknown.source, TupleShape.Unknown(unknown.source, Nil, unknown) :: Nil)(this), marks) - case Marked(nominal: NominalTypeShape, marks) => + case Marked(nominal: NominalInstanceView, marks) => nominal.ancestor(prelude.builtins.Array.defn.get).foreach: array => array.bindings.get(prelude.builtins.Array.defn.get.tparams.head.sym).foreach: element => - listenTypeValues(element): + listenTypeViews(element): case Marked(shape, inner) => tupleBindings(TupleShape(element.resolution.source, TupleShape.Unknown(element.resolution.source, inner :: Nil, shape) :: Nil)(this), marks) @@ -1024,6 +1058,11 @@ class NewResolver: listenTerm(scrutinee)(sh => matchShapePat(sh, pattern)(_ => ())) def appShape(lhs: TermShape, args: Term, res: App)(using NewResolverState): Unit = + lhs match + case Marked(_: InstanceShape, _) => + listenInstanceViews(lhs)(appShape(_, args, res)) + return + case _ => () // Only explicit dynamic values authorize calls without a known interface. // An unknown alternative must invalidate the call even if other candidates // happen to be callable in this compilation unit. @@ -1046,7 +1085,7 @@ class NewResolver: case NoShape => () callable.paramLists.tail match case Nil => callable.result match - case S(tpe) => listenTypeValues(tpe)(publish) + case S(tpe) => listenTypeInstances(tpe)(publish) case N => publish(UnknownValueShape.at(callable.source)) case tail => publish(callable.copy(paramLists = tail)) return @@ -1275,9 +1314,9 @@ class NewResolver: bms.onComplete: () => bms.asModOrObj.orElse(bms.asTrm).orElse(bms.asCls).foreach: sym => rstate.recordResolution(sel, sel.resolvedTargets.contains(sym))(sel.resolvedTargets ::= sym) - listenTerm(sel.prefix): receiver => + listenTermViews(sel.prefix): receiver => val selected = receiver match - case Marked(nominal: NominalTypeShape, context) => + case Marked(nominal: NominalInstanceView, context) => nominal.ancestor(cd) match case S(view) => view.getMember(sel.id.name).withAnnotation(nominal.annotation).withMarks(context :: Nil) case N => @@ -1383,7 +1422,7 @@ class NewResolver: // Classes without an explicit parent expose Object's declarations. Share this // root interface between inferred instances, nominal annotations, and this-values; // Object itself must terminate the chain. This does not add a runtime constructor call. - private lazy val objectShape = NominalTypeShape(prelude.builtins.Object.defn.get, Map.empty, N)(N)(this) + private lazy val objectShape = NominalInstanceView(prelude.builtins.Object.defn.get, Map.empty, N)(N)(this) private def implicitParent(defn: ClassLikeDef): Opt[TermShape] = if defn.sym is prelude.builtins.Object then N else S(objectShape) @@ -1455,7 +1494,7 @@ class NewResolver: case S(td: TermDefinition) if td.params.isEmpty => log(s"listenTerm: td.body = ${td.body.fold("N")(_.showDbg)}") resultSignature(td) match - case S(sign) => listenTypeValues(sign): shape => + case S(sign) => listenTypeInstances(sign): shape => td.tsym.decl match case S(_: Param) => wrappedListener(MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N)) case _ => wrappedListener(shape) @@ -1562,7 +1601,7 @@ class NewResolver: listenValue(trm, false)(listener) def listenReceiver(trm: Term)(listener: Listener)(using NewResolverState): Unit = - listenValue(trm, true)(listener) + listenValue(trm, true)(shape => listenInstanceViews(shape)(listener)) private def listenValue(trm: Term, receiver: Bool)(listener: Listener)(using NewResolverState): Unit = trm match case Capture(base, thru) => @@ -1601,7 +1640,7 @@ class NewResolver: trm match case _: SynthSel => lastWords("Synthetic selections must not enter new resolution") - case Asc(_, sign) => listenTypeValues(sign)(listener) + case Asc(_, sign) => listenTypeInstances(sign)(listener) case _: DynSel | _: DynNew => listener(DynShape()) case TyApp(underlying, args) => listen(underlying, discardMarks): shape => @@ -1645,7 +1684,7 @@ class NewResolver: case (field: Fld) :: rest => expand(rest, TupleShape.Field(field, Nil) :: reversed) case Spd(_, term) :: rest => val spreadKey = new Object - listenTerm(term): sh => + listenTermViews(term): sh => if rstate.spreadInputs.getOrElseUpdate(spreadKey, mutable.Set.empty).add(sh) then sh match case Marked(shape: TupleShape, marks) => // Widen an incoming candidate that already contains its own @@ -1682,7 +1721,7 @@ class NewResolver: case (field: RcdField) :: rest => expand(rest, RecordShape.Field(field) :: reversed) case RcdSpread(term) :: rest => val spreadKey = new Object - listenTerm(term): shape => + listenTermViews(term): shape => if rstate.spreadInputs.getOrElseUpdate(spreadKey, mutable.Set.empty).add(shape) then shape match case Marked(shape: RecordShape, marks) => // Bound recursive record producers just as for tuple spreads. @@ -1704,7 +1743,7 @@ class NewResolver: case Lit(_: Tree.DecLit) => S(prelude.builtins.Num) case Lit(_: Tree.BoolLit) => S(prelude.builtins.Bool) case _ => N - def publish(parent: Opt[NominalTypeShape])(using NewResolverState): Unit = + def publish(parent: Opt[NominalInstanceView])(using NewResolverState): Unit = listener(introShapes.getOrElseUpdate(new Identity(intro), { log(s"introShape: intro = $intro") IntroShape(intro, parent) @@ -1717,8 +1756,8 @@ class NewResolver: resolution.publish(TypeShape.Nominal(cls.defn.get)) declaredType(resolution, Map.empty) }) - listenTypeValues(tpe): - case nominal: NominalTypeShape => publish(S(nominal)) + listenTypeViews(tpe): + case nominal: NominalInstanceView => publish(S(nominal)) case _ => softAssert(false, "Primitive class must expose a nominal interface") case Ref(sym) if sym is sym.getState.globalThisSymbol => listener(DynShape()) case SelfRef(sym) if sym is sym.getState.globalThisSymbol => listener(DynShape()) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 3039d94b93..638fd68104 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -185,8 +185,8 @@ final class NewResolverState private ( new Cache(source.map(_.defnShapes), identity) val typeInterpretations: Cache[Identity[Term], TypeResolution] = new Cache(source.map(_.typeInterpretations), identity) - val typeValues: Cache[DeclaredType, TypeValues] = - new Cache(source.map(_.typeValues), identity) + val typeViews: Cache[DeclaredType, TermShapeHost] = + new Cache(source.map(_.typeViews), identity) val patternTypes: Cache[(Identity[Pattern.Constructor], InnerSymbol), DeclaredType] = new Cache(source.map(_.patternTypes), identity) val primitiveTypes: Cache[ClassSymbol, DeclaredType] = @@ -197,7 +197,7 @@ final class NewResolverState private ( new Cache(source.map(_.signatureParameters), identity) val capturedTypes: Cache[(DeclaredType, TermSymbol), DeclaredType] = new Cache(source.map(_.capturedTypes), identity) - val tupleArrayParents: Cache[Identity[TupleShape], NominalTypeShape] = + val tupleArrayParents: Cache[Identity[TupleShape], NominalInstanceView] = new Cache(source.map(_.tupleArrayParents), identity) val instanceParameterTypes: Cache[(VarSymbol, Bool), DeclaredType] = new Cache(source.map(_.instanceParameterTypes), identity) @@ -219,8 +219,8 @@ final class NewResolverState private ( val typeConstraints: Seen[(DeclaredType, TermShape, Ls[Marks])] = new Seen(source.map(_.typeConstraints)) -private[semantics] final class TypeValues extends Host[TermShape]: - def showDbg(using DebugPrinter): Str = "declared type values" +private[semantics] final class TermShapeHost extends Host[TermShape]: + def showDbg(using DebugPrinter): Str = "instance views" def publish(shape: TermShape)(using NewResolverState): Unit = if currentShapes.add(shape) then notifyShapeListeners(shape) def listen(listener: NewResolverState.Listener)(using NewResolverState): Unit = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 0a28682ad9..347cd9c8ef 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -26,7 +26,8 @@ sealed trait Shape extends ShapeLike: case ts: TupleShape => s"TupleShape(${ts.source.showDbg})" case rs: RecordShape => s"RecordShape(${rs.source.showDbg})" case rs: RecordTypeShape => s"RecordTypeShape(${rs.source.showDbg})" - case ts: NominalTypeShape => s"NominalTypeShape(${ts.defn.sym.showDbg})" + case ts: InstanceShape => s"InstanceShape(${ts.tpe.resolution.source.showDbg})" + case ts: NominalInstanceView => s"NominalInstanceView(${ts.defn.sym.showDbg})" case ts: OpaqueTypeShape => s"OpaqueTypeShape(${ts.source.showDbg})" case ts: CallableTypeShape => s"CallableTypeShape(${ts.source.showDbg})" case bs: BaseShape => s"BaseShape(${bs.defn.sym.showDbg})" @@ -375,21 +376,32 @@ final case class RecordTypeShape(source: Term.Rcd, fields: Ls[(RcdField, TypeRes case (field, _) if field.sym.nme == name => MemberLookup.Declared(field.sym, bindings, Nil, S(source)) }.getOrElse(MemberLookup.Missing) +/** An instance described by a type, in either position of a constraint. Keep the + * type reference intact during transport: expanding it to its current positive + * candidates would lose negative uses of generic arguments and compound types. + * Operations obtain concrete member/call views through listenInstanceViews. + */ +final case class InstanceShape(tpe: DeclaredType) extends NonAppTermShape: + def describe: Str = "value with a declared type" + def toLoc: Opt[Loc] = tpe.resolution.source.toLoc + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = + lastWords("Instance member lookup requires interpreting its type first") + /** A nominal annotation exposes only declarations, including inherited declarations. * In particular, selecting an unannotated field does not inspect its initializer. * The annotation is a diagnostic witness, excluded from shape equality; synthesized * interfaces such as a tuple's Array parent have no written annotation. */ -final case class NominalTypeShape(defn: ClassLikeDef, bindings: Map[VarSymbol, DeclaredType], +final case class NominalInstanceView(defn: ClassLikeDef, bindings: Map[VarSymbol, DeclaredType], parent: Opt[TermShape])(val annotation: Opt[Term])(resolver: NewResolver) extends NonAppTermShape: def describe: Str = s"value of type '${defn.sym.nme}'" def toLoc: Opt[Loc] = defn.toLoc override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = (defn is cls) || parent.exists(_.isInstanceOfClass(cls)) - def ancestor(cls: ClassLikeDef): Opt[NominalTypeShape] = + def ancestor(cls: ClassLikeDef): Opt[NominalInstanceView] = if defn is cls then S(this) else parent.flatMap: - case Marked(parent: NominalTypeShape, _) => parent.ancestor(cls) + case Marked(parent: NominalInstanceView, _) => parent.ancestor(cls) case _ => N protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = defn.body.members.get(name) match @@ -481,7 +493,7 @@ class RefinedShape(val base: TermShape, val refinements: Ls[Str -> Term]) extend * them instead of discarding either those fields or the unresolved possibilities. */ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element])(resolver: NewResolver) extends NonAppTermShape: - def arrayParent(using NewResolverState): NominalTypeShape = resolver.tupleArrayParent(this) + def arrayParent(using NewResolverState): NominalInstanceView = resolver.tupleArrayParent(this) override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = arrayParent.isInstanceOfClass(cls) lazy val segments: Ls[TupleShape.Segment] = elements.flatMap: case segment: TupleShape.Segment => segment :: Nil @@ -645,7 +657,7 @@ object RecordShape: type IntroTerm = Term.Lit | Term.UnitVal | Term.Lam //| Term.New -class IntroShape(val trm: IntroTerm, val primitive: Opt[NominalTypeShape]) extends NonAppTermShape: +class IntroShape(val trm: IntroTerm, val primitive: Opt[NominalInstanceView]) extends NonAppTermShape: def describe: Str = trm.describe protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = trm match case _: Term.Lit | _: Term.UnitVal => primitive.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)) diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 1cf6f47a3d..cb7340ecce 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -135,4 +135,4 @@ module Local with class Known(val known: Int) val mapped = [Known(4)].map((item, ...) => Known(item.known + 1)) Local.mapped.map((item, ...) => item.known) -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: term:Local⁰/mapped⁰ vs term:Array⁰/map⁰ for NominalTypeShape(class:Int⁰) +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: term:Local⁰/mapped⁰ vs term:Array⁰/map⁰ for InstanceShape(member:Int⁰^class:Known⁰) From 7b98db737d152513ba83acc1f0c6ea2f349090dc Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 21:57:53 +0800 Subject: [PATCH 091/228] test(resolution): retain recursive generic array regression --- doc/new-resolution-type-value-flow.md | 11 +++++++++-- .../src/test/mlscript/newres/MutableArrays.mls | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 50bb146f13..746ffc7b46 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -116,7 +116,10 @@ variance semantics, not its fresh inference-variable implementation. An experimental implementation made `appendTyped` propagate elements into an initially empty mutable array and passed the existing declaration worksheets. -However, this recursive example exposed an activation-correlation failure: +However, this recursive example exposed an activation-correlation failure. +It is retained as a `:fixme` regression in +[`newres/MutableArrays.mls`](../hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls) +(the recursive `append` block, immediately after `appendTyped`): ```mlscript class Item(val value: Int) @@ -130,7 +133,11 @@ xs.0.value The experiment reported an unknown element type originating from the generic body's abstract activation of `A`. That candidate must remain confined to that abstract activation. The experimental bound-copying implementation is not part -of the retained compiler changes. +of the retained compiler changes. On the committed resolver, the regression +currently fails earlier: `xs.0.value` has no resolved target because generic +array input propagation is still missing. Its expected result is `5`; the +current golden output does not reproduce the discarded prototype's unknown-type +diagnostic. The failure comes from independently applying an existing capture path and its reverse to already expanded candidates. Write `enter(f, s)` and `exit(f, s)` for diff --git a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls index d4f61e5918..56ab04726d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls @@ -240,3 +240,20 @@ xs.0.value //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' //│ xs = [Item(3)] + +// TODO: recursive generic input/output constraints must preserve caller activations. +// This also needs the element propagation exercised by appendTyped above. +:fixme +:expect 5 +class Item(val value: Int) +fun append[A](xs: Array[A], value: A, n: Int) = + if n > 0 then append(xs, value, n - 1) else xs.push(value) +let xs = mut [] +append(xs, Item(5), 2) +xs.0.value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.253: xs.0.value +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '5', got: 'undefined' +//│ xs = [Item(5)] From de8ea642d8caeb692abe4cbc48105f8645fed8ac Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 22:17:03 +0800 Subject: [PATCH 092/228] Document finite call-site instantiation and partial signature inference --- doc/new-resolution-design.md | 5 +- doc/new-resolution-suite-migration.md | 11 +- doc/new-resolution-type-value-flow.md | 264 ++++++++++++++---- .../test/mlscript/newres/MutableArrays.mls | 23 ++ .../mlscript/newres/PartialSignatures.mls | 22 ++ 5 files changed, 264 insertions(+), 61 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index c28f42a488..7c8d40b288 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -36,8 +36,9 @@ exported mapped arrays and imported generic functions and methods. `InstanceShape` retains a type reference at annotation boundaries; member lookup, application, and destructuring obtain specialized interfaces through `listenInstanceViews`. The distinction between supplying a type argument and -adding an ordinary bound, the agreed variance rules, and the pending constraint -representation are documented in [Instance types and parameter constraints](new-resolution-type-value-flow.md). +adding an ordinary bound, the agreed variance rules, and the proposal to instantiate +declared type parameters once per definition and syntactic call site are documented +in [Instance types and parameter constraints](new-resolution-type-value-flow.md). The description below records current generic inference, which still needs that constraint extension. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index a35e0f8f08..766d9cfd8b 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -93,11 +93,12 @@ The inventory below separates these from compiler and prelude gaps. reject valid later indexing. `codegen/SetStmt` also lacks argument flow through its update callback. Instance wrappers are in place; the [type-argument constraint proposal](new-resolution-type-value-flow.md) records - the agreed variance rules and the recursive capture-correlation issue that must - be resolved before `Array[A]` retains both input and output uses of its element type; - the `appendTyped` case in `newres/MutableArrays.mls` records the missing write - propagation. Member-variable definitions still need work. Handler inference - needs separate flows for the receiver, values + the agreed variance rules and once-per-definition/call-site instantiation of + declared type parameters. Review its integration with marks and partial + signatures before implementing both input and output constraints for `Array[A]`; + `appendTyped` and the recursive cases in `newres/MutableArrays.mls` record the + missing propagation and required isolation between callers. Member-variable + definitions still need work. Handler inference needs separate flows for the receiver, values passed to resumptions, and abortive results (`newres/HandlerResults.mls` and `codegen/ScopedBlocksAndHandlers`). Agree these designs before implementation. Ordinary result-shape contracts are covered by `newres/ControlFlowResults.mls`; diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 746ffc7b46..83f19d919c 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -6,9 +6,11 @@ nominal-only representation at annotation boundaries: parameter and result annotations, ascriptions, generic arguments, and annotated tuple fields retain a type reference until an operation requests its interface. -The wrapper refactor is implemented. Bidirectional type-argument constraints and -variance are the next step. The constraint representation proposed below still -needs agreement before implementation. See the [resolver notes](new-resolution-design.md) +The wrapper refactor is implemented. Instantiation of declared type parameters +once per definition and call site is the agreed direction. The integration with +marks and type constraints proposed below needs review before implementation; +bidirectional constraints and variance are also still pending. +See the [resolver notes](new-resolution-design.md) for current behavior and the [migration worklist](new-resolution-suite-migration.md) for remaining ports. @@ -44,14 +46,35 @@ These rules concern inputs/outputs, or equivalently lower/upper bounds. Function parameters reverse polarity and function results preserve it. Array operations are one application of the rules. -## Existing symbols and marks identify inference +## Finite call-site instantiation and marks -**Do not allocate fresh type variables for calls, arrays, or constraint matches.** -Fresh variables would prevent recursive inference from returning to existing -graph nodes and reaching a fixed point. A contextual reference uses the existing -parameter symbol, its originating inference host, and marks. In particular, a -mutable array uses the builtin `Array` element parameter with its allocation and -call contexts. Actual element shapes contribute bounds to that parameter. +**Instantiate each definition's explicitly declared type parameters once per +syntactic call site.** This includes binders in inline annotations and separate +type signatures, whether the call supplies type arguments or infers them. A call +`f(x)` therefore instantiates a declared `f[A]` just as `f[Int](x)` does. + +Memoize a complete group of parameter symbols using this key: + +```text +(original definition, syntactic application site) + -> {original parameter -> instantiated parameter} +``` + +Different sites receive different symbols. Revisiting the same definition at the +same site reuses its group, including during recursion. The key must not contain +the supplied types, incoming bounds, marks, or an already instantiated definition. +Install the group before subscribing to constraints so reentrant propagation +finds it. The only permitted allocation resembling fresh inference variables is +this bounded instantiation; do not allocate another variable per flow, constraint +match, or recursive visit. + +Symbols identify these instances; marks still identify the contexts in which +their bounds flow. A single call inside a helper shares its parameter instances +across the helper's callers, while enclosing marks distinguish those callers. +Keep the original lexical resolution boundaries and existing normalization; +instantiated symbols must not introduce additional scope boundaries. Mutable +array literals retain the builtin `Array` element parameter with allocation marks; +actual element shapes contribute bounds there. They need no fresh element variable. Keeping a reference to an unresolved parameter is essential. Copying its current positive candidates loses its negative uses and its identity as a type argument. @@ -59,6 +82,14 @@ Subscriptions must also account for constraints or candidates arriving later. Graph caches and listeners belong to the consuming `NewResolverState`; extending a consumer must not mutate a prelude or an exporter's inference graph. +This follows the finite-allocation idea in §3 of +[Tate's type-outference paper](https://rosstate.org/publications/outference/outference-tate-oopsla25.pdf): +label listeners introduce signature unknowns once per invocation site and nominal +label, reusing them as bounds arrive rather than expanding every concrete type. +Here the proposed key uses the original definition and application site. The +paper's formal calculus has monomorphic methods; its termination result does not +directly establish termination for our generic functions and marks. + ## Instance wrappers and interface observations `InstanceShape` holds a `DeclaredType`, including its lexical bindings. @@ -160,62 +191,187 @@ it does not establish that the existing mark representation itself is inadequate Discarding the abstract candidate, suppressing reverse propagation for recursion, or truncating paths would conceal the loss rather than preserve the constraint. -## Proposed extension: retain both contextual type references +## Proposed integration with the resolver -Represent an argument relation as a persistent constraint between two type -references, each retaining its original host, bindings, and marks. Keep the -argument's input/output parts on this relation. Do not immediately replace it -with two unrelated subscriptions that copy positive candidates. +For the recursive example above there are three relevant versions of `A`: -A capture match must belong to the relation traversal. When a wildcard matches -`enter(f, abstract)`, retain that matched activation while transporting the bound -through the opposite endpoint. A reverse obligation uses the same match; it -must not recreate `enter(f, *)`. For a concrete caller, both endpoints instead -use that caller's match. Deferred propagation must retain this association too. +```text +A_body original binder used to check the generic definition +A_outer instance for append(xs, Item(5), 2) +A_recursive instance for append(xs, value, n - 1) +``` -Conceptually, the recursive relation is a family: +Further recursive visits reuse `A_recursive`; they do not create +`A_recursive_recursive`. A second external application gets its own instance of +`A`, but still reaches the same recursive application site. Its enclosing marks +must keep the two external contexts distinct. The original abstract candidate of +`A_body` must not be copied into the call instances: instantiate the declared +constraints, not the generic body's accumulated unknown-value candidates. -```text -A at recursiveCall(caller) relates to A at caller +This replaces the earlier proposal to retain wildcard matches across paired +candidate-copying edges. It does not yet demonstrate that the recursive example +works: the compiler still needs consistent substitution of the call instances +through deferred type observations and body-result flow. + +### Definition and application identities + +Use the stable `Term.App.resSym` as application identity, rather than taking a +site from the callee's marks. Marks can identify a function reference shared by +several applications: + +```mlscript +let g = append +g(xs, First(1), 2) +g(ys, Second(2), 2) +``` + +These applications need distinct parameter instances. The two-array regression +following the recursive `append` block in `newres/MutableArrays.mls` checks this +case with different element interfaces. + +For construction, use `Term.New.resSym` and canonicalize the constructor and +class to the same original owner. For an anonymous polymorphic annotation, the +proposed owner is its original quantified declaration node. Neither imported +views nor instantiated callable views should manufacture a new original owner. + +### Binder substitution before observation + +Normalize binders from inline declarations and separate signatures into a +reusable scheme with its parameter bounds, declared type fragments, and links to +inference for unannotated parts. Do not require a complete signature. Currently, +`typeResolution` strips `Forall` to its body; that loses the information needed +to instantiate separate polymorphic signatures. Preserve the quantifiers first. + +An instantiated callable must retain one immutable substitution from that +scheme's own binders to the site's parameter symbols. Use it for parameter +annotations, results, nested callback types, bounds, and type arguments before +`listenInstanceViews` expands a parameter. Enclosing class or function parameters +remain captures; they are not binders of the nested definition being instantiated. + +Argument matching alone is insufficient. The generic body was elaborated using +the original binders, and a returned tuple or closure can defer observing an +annotated value until after the call. Carry the same substitution through those +deferred observations. Keep generic-body checking on the original abstract +activation; do not mutate the body or unconditionally connect its inference host +to each call instance. The representation of these substituted body-result views +is the main integration question to resolve before implementation. + +### Partial signatures and inference holes + +Instantiation applies to explicit type binders even when the rest of a definition +is only partially annotated. The recursive `append[A]` example already has an +inferred result. A more direct example mixes annotated and unannotated inputs: + +```mlscript +private fun pair[A](x: A, y) = [x, y] ``` -`caller` here names a shared capture match, not a fresh type variable or a new -runtime symbol. The current independent edges effectively erase it on the -recursive side. Retaining type references allows a constraint to postpone that -match until it has an activation to use, instead of forwarding every currently -observed lower bound through an unconditional reverse edge. - -Proposed implementation steps: - -1. Introduce immutable contextual type-reference endpoints and memoized argument - relations in `NewResolverState`. Reuse existing `TypeResolution` nodes and - parameter hosts. Keep supplied type references distinct from ordinary bounds. -2. Implement capture matching that records the normalized path matched by a - capture and reuses it across the relation's two endpoints. Keep this data in - constraint facts or their subscriptions, never mutable global symbol fields. -3. Deliver lower and upper obligations to each supplied type reference. A plain - inferred value adds a lower bound; explicit instantiation and invariant - argument matching must preserve the supplied type's uses in both positions. -4. Interpret declaration/use-site variance using the input/output parts above. - Use the same constraint mechanism for arrays, generic classes, and functions. -5. Only then replace `applyTypeArguments`, nominal inference, explicit-argument - suppression, and the positive-only conversion in `instanceBindings` together. - -The unresolved implementation question is the finite representation of capture -matches under recursive relation composition. Merely storing an ever-growing -history of matches would repeat the fresh-variable termination problem. Before -landing this extension, demonstrate that recursive processing revisits memoized -relations over existing nodes and normalized contexts, including nested type -applications and mutually recursive calls. If that requires changing mark -normalization or the domain of contexts, report that design separately. +At an application site `s`, instantiate `A` as `A_s`. The annotation of `x` +becomes `InstanceShape(A_s)`. The unannotated `y` retains its existing value-flow +symbol and obtains argument shapes under the call's marks. Infer the result from +the body: its first field retains the reference to `A_s`, and its second field +retains the marked flow from `y`. Neither `y` nor the result needs an invented +quantified parameter or a fresh type variable at the call. + +The substitution must survive delayed tuple-field observations. Likewise, in +`private fun apply[A](x: A, f) = f(x)`, the unannotated callback and its result +must remain connected to the contextual `A`. Both examples, called with distinct +element interfaces, currently pass in +[`newres/PartialSignatures.mls`](../hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls). +The instantiation refactor must preserve that behavior. + +Represent a partial signature position by position: + +| Part of the definition | Source of its interface at a call | +| --- | --- | +| Explicit type binder `A` | The memoized call-site instance `A_s` | +| Written type fragment | Its instance wrapper, using the call's substitution | +| Unannotated value parameter | Its existing inference host and marks | +| Unannotated result | The body's flow graph, retaining substitution and marks | +| Inferable hole within a type | A stable inference node for that source hole, under marks | + +**Inferable holes use ordinary marked inference, not anonymous quantified +parameters.** This is the agreed semantics; hole syntax is not selected here. +For schematic `Array[?]`, retain the declared `Array` structure and connect its +element position to the hole's inferred flow. Reuse the source hole's node across +visits and distinguish contexts with marks. No call-site symbol copy is allocated +for the hole. Constraints follow the position's input/output polarity as usual. +If no useful shape reaches a hole, it supplies no member interface; it does not +grant dynamic access. + +An inference hole must be distinguishable from an intentionally abstract type, +an `in`/`out` wildcard, and an unresolved or invalid annotation. These must not +all collapse to `TypeShape.Abstract`, which the current interpreter uses for +several unsupported forms. Inferring one missing piece must not add members to +an explicitly written concrete interface elsewhere in the annotation. + +Whether omitted generic arguments also denote inferable holes is still a design +question. Current `DeclaredTypes.mls` tests treat missing arguments in annotations +such as `Pair[Int]` for `Pair[A, B]` as unknown interfaces. Similarly, an unknown +member type exposed through a nominal annotation is not automatically an +inference hole. Preserve those distinctions until their intended behavior is +decided; the agreement about explicit holes alone does not resolve them. + +The scheme therefore cannot be a closed type synthesized from whatever shapes +happen to be available first. It must retain live links to inferred portions of +the definition, including bounds arriving later or through recursion. Existing +exposure checks for unannotated parameters and generic-body checking still apply; +partial annotations must not silently disable either. + +### Partial application and explicit specialization + +For curried calls, the proposed rule is to instantiate when the first parameter +list is consumed and retain that substitution in the partially applied callable. +Later lists reuse it. A separately quantified returned callable has its own +binders to instantiate at its later application. A standalone specialization such +as `let g = f[Int]` can retain its supplied type arguments until application; +each application of `g` then binds its own site instances to `Int`. This avoids +introducing a second allocation policy at type-application nodes. + +### Constraint propagation and implementation order + +1. Preserve partial declaration/signature schemes and their original identities, + including links to inferred portions. Distinguish inference holes from + intentionally abstract types and variance wildcards. Add a + consumer-owned cache of complete call-site instances, with origin links back + to the declared binders. Do not add mutable caches to symbols. +2. Transport the memoized substitution through callable and result views before + expanding instance wrappers. Keep the existing marks for value flow and + lexical captures. Verify the abstract generic-body candidate stays isolated. +3. Relate the resulting type references in both input and output positions, + retaining their hosts, substitutions, and marks. Ordinary arguments contribute + bounds; supplied types receive all obligations at their applicable polarity. + Apply the InvalML variance rules above. +4. Replace `applyTypeArguments`, nominal inference, explicit-argument suppression, + and the positive-only conversion in `instanceBindings` together. Reuse the + same mechanism for functions, constructors, and declared array interfaces. + +There are at most as many allocated parameter instances as the sum of each +reachable definition's binder count over its syntactic call sites. This bounds +symbol allocation, not every possible implementation of the solver. Recursive +constraints must return to shared nodes and deduplicated relations; expanding +substitution environments or nested types afresh could still fail to terminate. +Demonstrate fixed points with recursive and mutually recursive calls before +landing the propagation changes. Any needed change to mark normalization or to +the context domain must be proposed separately. ## Acceptance checks - An initially empty mutable array receives an element through `Array[A]`. - Two calls using different arrays and incompatible element interfaces remain independent, including two callers of the recursive example above. -- Recursive input/output relations terminate without fresh type variables, - repeated-boundary paths, or unbounded binding-environment construction. +- A repeated visit to the same definition/site reuses its parameter symbols; + two application sites through one alias obtain distinct instances. An inner + site shared by different enclosing activations remains distinguished by marks. +- Inline and separate polymorphic signatures instantiate consistently, with or + without explicit call-site type arguments. Partial applications retain their + substitutions and captured outer binders retain their original contexts. +- Mixed annotated/unannotated inputs and inferred results preserve the passing + tuple and callback cases in `newres/PartialSignatures.mls`. Nested inference + holes retain written structure and use marked inference without extra + call-site symbols; known annotation fragments still restrict the interface. +- Recursive input/output relations terminate with bounded call-site instances, + no repeated-boundary paths, and no unbounded binding-environment construction. - Declaration-site `in`/`out`, use-site `in`/`out`, their overrides, and nested function polarity agree with InvalML. - Explicit function type arguments constrain callback inputs even when the diff --git a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls index 56ab04726d..ef5bbfd82f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls @@ -257,3 +257,26 @@ xs.0.value //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '5', got: 'undefined' //│ xs = [Item(5)] + +// TODO: instantiate declared A separately at each application through the alias. +// The recursive site is shared; enclosing marks must still separate its callers. +:fixme +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +fun append[A](xs: Array[A], value: A, n: Int) = + if n > 0 then append(xs, value, n - 1) else xs.push(value) +let g = append +let xs = mut [] +let ys = mut [] +g(xs, First(1), 2) +g(ys, Second(2), 2) +[xs.0.first, ys.0.second] +//│ ╔══[COMPILATION ERROR] This selection of member 'first' has no resolved target +//│ ║ l.274: [xs.0.first, ys.0.second] +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[1, 2]', got: 'undefined' +//│ g = fun append +//│ xs = [First(1)] +//│ ys = [Second(2)] diff --git a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls new file mode 100644 index 0000000000..5a1074db0f --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls @@ -0,0 +1,22 @@ +:. + +// An inferred result combines a declared type parameter and ordinary value flow. +// Both calls must preserve their own annotations and unannotated argument shapes. +:expect [1, 2, 3, 4] +class First(val first: Int) +class Second(val second: Int) +private fun pair[A](x: A, y) = [x, y] +let left = pair(First(1), Second(2)) +let right = pair(Second(3), First(4)) +[left.0.first, left.1.second, right.0.second, right.1.first] +//│ = [1, 2, 3, 4] +//│ left = [First(1), Second(2)] +//│ right = [Second(3), First(4)] + +// An unannotated callback and inferred result retain their dependence on A. +:expect [5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun apply[A](x: A, f) = f(x) +[apply(First(5), x => x.first), apply(Second(6), x => x.second)] +//│ = [5, 6] From a70fd8e477d44d5186ecd5566640c7c50dfae046 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 22:27:44 +0800 Subject: [PATCH 093/228] Complete call-site instantiation and partial signature design review --- doc/new-resolution-design.md | 8 +- doc/new-resolution-suite-migration.md | 7 +- doc/new-resolution-type-value-flow.md | 283 ++++++++++++++---- .../test/mlscript/newres/DeclaredTypes.mls | 179 ++++++----- .../mlscript/newres/PartialSignatures.mls | 104 +++++++ 5 files changed, 431 insertions(+), 150 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 7c8d40b288..463faa801d 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -40,7 +40,9 @@ adding an ordinary bound, the agreed variance rules, and the proposal to instant declared type parameters once per definition and syntactic call site are documented in [Instance types and parameter constraints](new-resolution-type-value-flow.md). The description below records current generic inference, which still needs that -constraint extension. +constraint extension. In particular, the implementation still treats omitted +generic arguments as unknown interfaces; the reviewed design makes them inference +holes and retains live inference for the missing parts of a partial signature. Class bodies introduce lexical captures, and a class and its constructor share one resolution boundary. A method's reference to an outer constructor must include @@ -78,6 +80,10 @@ Generic aliases and inherited declared interfaces substitute their arguments; declared member selections carry those contexts without reading implementation value flow. An unannotated member read through a declared interface produces an unknown shape, rather than consulting its initializer or method body. +This is current behavior pending the reviewed partial-signature design: missing +member types will use the selected declaration's contextual inference graph, +while nominal annotations continue to restrict the visible member set. Override +compatibility must be checked before exposing inferred dispatch results. Callback parameter types constrain implementation parameters, and callback results constrain inferred type arguments. `newres/DeclaredTypes.mls` covers these paths, separate signatures, tuple constraints, and distinct generic instantiations. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 766d9cfd8b..25ef5f822a 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -94,8 +94,11 @@ The inventory below separates these from compiler and prelude gaps. its update callback. Instance wrappers are in place; the [type-argument constraint proposal](new-resolution-type-value-flow.md) records the agreed variance rules and once-per-definition/call-site instantiation of - declared type parameters. Review its integration with marks and partial - signatures before implementing both input and output constraints for `Array[A]`; + declared type parameters. Its design review specifies contextual views with + marks, partial signatures, and inference holes for omitted generic arguments. + Selected members also infer missing types through nominal annotations, with + receiver contexts and override constraints preserved. + Implement both input and output constraints for `Array[A]` together; `appendTyped` and the recursive cases in `newres/MutableArrays.mls` record the missing propagation and required isolation between callers. Member-variable definitions still need work. Handler inference needs separate flows for the receiver, values diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 83f19d919c..92213f75b4 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -6,10 +6,12 @@ nominal-only representation at annotation boundaries: parameter and result annotations, ascriptions, generic arguments, and annotated tuple fields retain a type reference until an operation requests its interface. -The wrapper refactor is implemented. Instantiation of declared type parameters -once per definition and call site is the agreed direction. The integration with -marks and type constraints proposed below needs review before implementation; -bidirectional constraints and variance are also still pending. +The wrapper refactor is implemented. The design below specifies the next +implementation: finite instantiation of explicit binders, contextual views of +partial signatures, inference for explicit and omitted holes, and bidirectional +constraints with variance. These changes are not implemented yet. The representation +and implementation order below are the outcome of the design review, not evidence +that the recursive acceptance cases already work. See the [resolver notes](new-resolution-design.md) for current behavior and the [migration worklist](new-resolution-suite-migration.md) for remaining ports. @@ -143,12 +145,9 @@ admits only the input connection. Substitution into member types must select the appropriate part at each polarity, including nested arrows. Copy InvalML's variance semantics, not its fresh inference-variable implementation. -## Why two independent candidate-copying edges are insufficient +## Recursive acceptance example -An experimental implementation made `appendTyped` propagate elements into an -initially empty mutable array and passed the existing declaration worksheets. -However, this recursive example exposed an activation-correlation failure. -It is retained as a `:fixme` regression in +This example is retained as a `:fixme` regression in [`newres/MutableArrays.mls`](../hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls) (the recursive `append` block, immediately after `appendTyped`): @@ -161,37 +160,16 @@ append(xs, Item(5), 2) xs.0.value ``` -The experiment reported an unknown element type originating from the generic -body's abstract activation of `A`. That candidate must remain confined to that -abstract activation. The experimental bound-copying implementation is not part -of the retained compiler changes. On the committed resolver, the regression -currently fails earlier: `xs.0.value` has no resolved target because generic -array input propagation is still missing. Its expected result is `5`; the -current golden output does not reproduce the discarded prototype's unknown-type -diagnostic. +The committed resolver cannot resolve `xs.0.value` because generic array input +propagation is missing. Its expected result is `5`. A discarded implementation +using two independent candidate-copying edges reached a different failure: +transporting an expanded abstract candidate through a wildcard capture and back +lost its original activation, letting it contaminate a real caller. The retained +golden output does not reproduce that prototype's diagnostic. Both failures +motivate the acceptance case: preserve type references and their contexts before +observation, rather than trying to recover them from expanded candidates. -The failure comes from independently applying an existing capture path and its -reverse to already expanded candidates. Write `enter(f, s)` and `exit(f, s)` for -marks at function `f` and site `s`, and `*` for a capture that matches any site. -A candidate starting at `enter(f, abstract)` can travel as follows: - -```text -forward: - enter(f, abstract) -- exit(f, *) --> no mark - -- enter(f, recursiveCall) --> enter(f, recursiveCall) - -independent reverse: - enter(f, recursiveCall) -- exit(f, recursiveCall) --> no mark - -- enter(f, *) --> enter(f, *) -``` - -The reverse traversal has forgotten `abstract`. Its wildcard can then match a -real caller's exit. This demonstrates loss in the candidate-copying approach; -it does not establish that the existing mark representation itself is inadequate. -Discarding the abstract candidate, suppressing reverse propagation for recursion, -or truncating paths would conceal the loss rather than preserve the constraint. - -## Proposed integration with the resolver +## Resolver integration For the recursive example above there are three relevant versions of `A`: @@ -208,10 +186,10 @@ must keep the two external contexts distinct. The original abstract candidate of `A_body` must not be copied into the call instances: instantiate the declared constraints, not the generic body's accumulated unknown-value candidates. -This replaces the earlier proposal to retain wildcard matches across paired -candidate-copying edges. It does not yet demonstrate that the recursive example -works: the compiler still needs consistent substitution of the call instances -through deferred type observations and body-result flow. +Use constraints over these references, rather than restoring wildcard matches +after copying expanded candidates. The argument endpoint retains its caller's +substitution and context; the parameter endpoint uses the callee site's instances. +An application must not overwrite both endpoints with the callee substitution. ### Definition and application identities @@ -231,7 +209,10 @@ case with different element interfaces. For construction, use `Term.New.resSym` and canonicalize the constructor and class to the same original owner. For an anonymous polymorphic annotation, the -proposed owner is its original quantified declaration node. Neither imported +owner is its original quantified declaration node. An overload resolving to more +than one definition instantiates each original definition at that site. Where +the language implicitly invokes a getter, its source reference/selection is the +invocation site; it is not a fresh site per resolver visit. Neither imported views nor instantiated callable views should manufacture a new original owner. ### Binder substitution before observation @@ -254,7 +235,79 @@ annotated value until after the call. Carry the same substitution through those deferred observations. Keep generic-body checking on the original abstract activation; do not mutate the body or unconditionally connect its inference host to each call instance. The representation of these substituted body-result views -is the main integration question to resolve before implementation. +is described below. + +### Shared graph and contextual views + +Retain one source graph per definition. Its nodes describe written type structure, +references to binders, and inferred flows from parameters, expressions, and holes. +Give references to that graph an explicit view consisting of: + +```text +source node + originating inference host +substitution of this node's free explicit binders +existing normalized marks +``` + +The binder substitution selects original binders or memoized call-site instances. +It is a flat mapping over the source node's free binders, not a stack of past +substitutions. Keep compound supplied types as graph references connected by +constraints; do not insert their successive expansions into the substitution. +Alias and nominal argument bindings likewise retain references to argument nodes, +with recursive bindings represented by back-edges rather than nested map copies. +This requires replacing the recursive structural use of `DeclaredType.bindings` +where it would otherwise grow a new environment on every recursive visit. + +Memoize each view before observing its source node. Observing an inferred flow +subscribes to its existing host in the consuming resolver and applies the view +to arriving references. Observing a compound value exposes its outer shape while +giving its deferred children the corresponding view. This applies to tuple and +record fields, callable results, nested annotations, and closure captures; merely +rewriting the outer `InstanceShape` is insufficient. Compose views by replacing +bindings for the callee's own binders and retaining lexical captures, then discard +bindings unused by the observed source node. Do not wrap a view in another view. + +For `pair[A](x: A, y) = [x, y]`, the result's first field observes the annotation +node under `A -> A_s`; the second subscribes to `y` under the call's marks. A +closure returning `x` keeps the first reference after the enclosing call returns. +The body, field nodes, and `y` symbol are shared; only their views differ. + +Generic-body checking observes the source graph with its original abstract +binders. Call inference observes it with the site's substitution. The abstract +unknown produced to check operations on a rigid binder is a checking witness, +not a lower bound to copy into the instantiated graph. Preserve the existing +diagnostics for operations unsupported by `A`; do not suppress all unknown +values or remove ordinary exposure-checking constraints. Keep the symbolic +binder dependency until an observer has chosen its view. In particular, +`registerTypeParameters` cannot keep seeding a host whose expanded candidates are +then blindly reused as a call's inferred result. + +### Constraints and marks + +Memoize a directed constraint by its two contextual type references, including +their originating hosts. Keep three operations distinct: + +- An ordinary instance contributes a lower-bound obligation to its expected type. +- Supplying a type reference retains that reference and delivers lower and upper + obligations to it, including obligations that arrive later. +- Comparing compound types follows their structure and argument variance, using + the same contextual references at every recursive step. + +Register listeners before replaying existing facts. Adding either a bound or a +supplied type must process the other facts already present, so event order does +not matter. For invariant arguments, install both directed constraints between +the retained endpoints; do not obtain the second by reversing a path already +applied to expanded candidates. Preserve a supplied union as a whole in negative +position. Deduplicate relations and delivered obligations by semantic references, +not by diagnostic witnesses. + +Existing marks still transport instance flow through lexical scopes and distinguish +enclosing activations sharing a static inner call. Apply their current entry/exit +operations to each endpoint in its own view. A callee's symbol allocation key +does not include marks, and a caller's view is not replaced by a wildcard when +forming the reverse constraint. The proposal adds no mark syntax, path truncation, +or cloned lexical boundaries. Enable the existing path invariants in focused +solver tests to catch a mistaken context composition rather than masking it. ### Partial signatures and inference holes @@ -296,8 +349,11 @@ For schematic `Array[?]`, retain the declared `Array` structure and connect its element position to the hole's inferred flow. Reuse the source hole's node across visits and distinguish contexts with marks. No call-site symbol copy is allocated for the hole. Constraints follow the position's input/output polarity as usual. -If no useful shape reaches a hole, it supplies no member interface; it does not -grant dynamic access. +An empty hole is pending inference, not an `UnknownValueShape` candidate to emit +immediately: later evidence may resolve it. At completion, a hole without usable +evidence supplies no member interface and does not grant dynamic access. An +unknown alternative actually contributed by exposure checking is different and +must remain even if some concrete candidates arrive too. An inference hole must be distinguishable from an intentionally abstract type, an `in`/`out` wildcard, and an unresolved or invalid annotation. These must not @@ -305,12 +361,28 @@ all collapse to `TypeShape.Abstract`, which the current interpreter uses for several unsupported forms. Inferring one missing piece must not add members to an explicitly written concrete interface elsewhere in the annotation. -Whether omitted generic arguments also denote inferable holes is still a design -question. Current `DeclaredTypes.mls` tests treat missing arguments in annotations -such as `Pair[Int]` for `Pair[A, B]` as unknown interfaces. Similarly, an unknown -member type exposed through a nominal annotation is not automatically an -inference hole. Preserve those distinctions until their intended behavior is -decided; the agreement about explicit holes alone does not resolve them. +**Omitted generic arguments are inference holes too.** For `Pair[A, B]`, a written +`Pair[Int]` supplies `Int` for `A` and a hole for `B`; bare `Pair` has two distinct +holes. Identify each omission by its source type-use occurrence and missing formal +parameter. Two occurrences do not share a hole just because they name the same +class. Reinterpreting an occurrence, following an alias, or observing another +member reuses its contextual hole reference. Apply declaration-site variance to +the missing argument just as to an unqualified written argument. Explicit +`in`/`out` arguments retain their stated defaults; their absent parts are not holes. + +For `type HalfPair[T] = Pair[Int, T]`, a bare `HalfPair` introduces a hole for +`T` at that use, and the alias body forwards that reference into `Pair`'s second +argument. An omission written inside an alias body has its own source node and +is transported with the alias use's context; expansion must not allocate more +hole symbols. Excess arguments still indicate an arity error. + +The positive `Pair[Int]`, `HalfPair`, and bare `Array` cases in +`newres/PartialSignatures.mls` record this missing behavior as `:fixme`s. Existing +`DeclaredTypes.mls` examples selecting members from omitted arguments without any +supporting flow remain negative tests: a hole is inferable, not evidence of an +arbitrary member. Replace the current unconditional `abstractType` treatment of +omissions; report insufficient inferred information when a genuinely unfilled +hole is observed. The scheme therefore cannot be a closed type synthesized from whatever shapes happen to be available first. It must retain live links to inferred portions of @@ -318,15 +390,57 @@ the definition, including bounds arriving later or through recursion. Existing exposure checks for unannotated parameters and generic-body checking still apply; partial annotations must not silently disable either. +### Inferred member signatures through nominal annotations + +The agreed partial-signature semantics also apply to a selected member's missing +types through a nominal annotation. For example: + +```mlscript +class Item(val value: Int) +class Box with + fun item() = Item(1) +private fun read(x: Box) = x.item().value +read(new Box) +``` + +Use `Box.item`'s inferred result here, consistently with partial function +signatures. Missing parameter types, constructor-field types, and value-member +types likewise link to the selected declaration's inference graph. Keep the +receiver's context on those links; using the class's unmarked global inference +would mix distinct instances. A nominal constraint must retain the actual +receiver's contextual flow as an input to these missing member types. The current +wrapper containing only a closed declared interface cannot recover an unannotated +constructor field after that receiver flow has been discarded. Keep the receiver +reference on the inferred member connection, while using the written nominal +type for lookup; do not replace the annotation's view with the actual receiver's +whole shape. Explicitly annotated member portions still use their written types. +Class-qualified selections and overloaded names +use the selected member's own scheme, including the term alternative of an +overloaded name. The corresponding examples in `DeclaredTypes.mls` are now +`:fixme` acceptance cases instead of requirements to reject inference. + +The nominal member set stays fixed: a `Base` annotation must not expose members +found only on `Child`. Overrides must satisfy the selected declaration's input/output +constraints. It is insufficient to assume that the base method's body describes +every dynamic dispatch result. In particular, a base method returning `Item` and +an override returning a type without `Item`'s members cannot justify selecting +those members from the dispatched result. `PartialSignatures.mls` retains this +negative regression. Connect override signatures to the inherited interface +before completing member targets, following function input/output variance; +do not expose an unchecked base implementation shape as a dispatch guarantee. +Preserve the existing member-target completion checks for imported code as well. + ### Partial application and explicit specialization -For curried calls, the proposed rule is to instantiate when the first parameter +For curried calls, instantiate when the first parameter list is consumed and retain that substitution in the partially applied callable. Later lists reuse it. A separately quantified returned callable has its own binders to instantiate at its later application. A standalone specialization such -as `let g = f[Int]` can retain its supplied type arguments until application; +as `let g = f[Int]` retains its supplied type arguments until application; each application of `g` then binds its own site instances to `Int`. This avoids -introducing a second allocation policy at type-application nodes. +introducing a second allocation policy at type-application nodes. Check argument +arity against the retained scheme immediately, and allow observations of the +specialized interface to use its supplied type references before a term call. ### Constraint propagation and implementation order @@ -334,10 +448,12 @@ introducing a second allocation policy at type-application nodes. including links to inferred portions. Distinguish inference holes from intentionally abstract types and variance wildcards. Add a consumer-owned cache of complete call-site instances, with origin links back - to the declared binders. Do not add mutable caches to symbols. + to the declared binders. Keep omissions source-owned; do not add mutable caches + to symbols. Preserve `Forall` binders and their bounds instead of erasing them. 2. Transport the memoized substitution through callable and result views before expanding instance wrappers. Keep the existing marks for value flow and - lexical captures. Verify the abstract generic-body candidate stays isolated. + lexical captures. Separate generic-body checking witnesses from the symbolic + constraints replayed in a call view. Verify delayed fields and returned closures. 3. Relate the resulting type references in both input and output positions, retaining their hosts, substitutions, and marks. Ordinary arguments contribute bounds; supplied types receive all obligations at their applicable polarity. @@ -345,15 +461,48 @@ introducing a second allocation policy at type-application nodes. 4. Replace `applyTypeArguments`, nominal inference, explicit-argument suppression, and the positive-only conversion in `instanceBindings` together. Reuse the same mechanism for functions, constructors, and declared array interfaces. +5. Route declared member lookup to partial member schemes, retaining receiver + contexts for inferred fields and results. Check override compatibility before + completing member targets. Convert the member-inference `:fixme`s together; + keep tests rejecting subclass-only members and unsupported override results. + +Keep this work inside resolution and type interpretation. Lowering still consumes +completed member targets and value shapes; runtime values acquire no type-argument +objects. The main source changes are in `TypeShape.scala` (schemes and contextual +references), `NewResolverState.scala` (consumer-owned memo tables), +`NewResolver.scala` (view observation and constraint delivery), and `Shape.scala` +(transporting views through deferred children). Elaborator signature registration +must preserve binders and missing positions before the body is observed. + +### Fixed points and implementation checks There are at most as many allocated parameter instances as the sum of each -reachable definition's binder count over its syntactic call sites. This bounds -symbol allocation, not every possible implementation of the solver. Recursive -constraints must return to shared nodes and deduplicated relations; expanding -substitution environments or nested types afresh could still fail to terminate. -Demonstrate fixed points with recursive and mutually recursive calls before -landing the propagation changes. Any needed change to mark normalization or to -the context domain must be proposed separately. +reachable definition's binder count over its syntactic call sites. Source hole +nodes and written type/term nodes are finite independently of recursive visits. +View substitutions range over the finite original and instantiated binders in +lexical scope. With the existing no-repeated-boundary mark invariant, their +normalized contexts also range over a finite domain. These facts bound the set +of memoized views and endpoint pairs **provided** compound arguments remain shared +graph edges and no cache key includes a growing substitution or capture history. +Monotone, deduplicated propagation then reaches a fixed point. + +Check this at the graph layer as well as with worksheets. Replaying an existing +definition/site must leave the instance count unchanged; replaying the same view +or relation must add neither a listener nor a node. Include mutually recursive +definitions and changing arguments such as a recursive call with `Array[A]`: +the recursive site's parameter must receive a cyclic reference to the source +`Array[A]` expression, not generate `Array[Array[...]]` nodes on every pass. +Verify obligations delivered before and after edge creation, and two callers of +one inner site with different enclosing marks. Count cache growth until saturation; +passing a shallow recursion test is not sufficient evidence of termination. + +During implementation, assert that a call instance's origin is an original binder, +all of one scheme's binders are allocated before its constraints are activated, +views are composed rather than nested, and generic checking witnesses never become +ordinary instantiated lower bounds. Retain consumer-private host copies and the +existing prohibition on changing completed member targets. The design review does +not license changing mark normalization if these checks fail; any such change +requires a separate concrete example and proposal. ## Acceptance checks @@ -370,6 +519,10 @@ the context domain must be proposed separately. tuple and callback cases in `newres/PartialSignatures.mls`. Nested inference holes retain written structure and use marked inference without extra call-site symbols; known annotation fragments still restrict the interface. +- Selected members infer missing types through nominal annotations, including + constructor fields, method results, and overloaded value members. Receiver + contexts remain distinct; subclass-only members and unsafe override results + remain rejected. Deferred closure observations preserve the outer substitution. - Recursive input/output relations terminate with bounded call-site instances, no repeated-boundary paths, and no unbounded binding-environment construction. - Declaration-site `in`/`out`, use-site `in`/`out`, their overrides, and nested diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 3605cbb5d3..49d332190c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -66,27 +66,30 @@ box.extra //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Child { extra: 42 } -// An annotated receiver does not expose constructor-argument shapes for unannotated fields. -:ge +// TODO: infer the selected declaration's missing field type in the receiver's context. +:fixme +:expect 42 class Item with val extra = 42 class Box(val item) fun read(box: Box) = box.item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.74: fun read(box: Box) = box.item.extra +//│ ║ l.75: fun read(box: Box) = box.item.extra //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.73: class Box(val item) +//│ ║ l.74: class Box(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.74: fun read(box: Box) = box.item.extra +//│ ║ l.75: fun read(box: Box) = box.item.extra //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' -// An unannotated method result cannot reveal the method body either. -:ge +// TODO: infer the selected method's missing result through a nominal annotation. +:fixme +:expect 42 class Item with val extra = 42 class Box with @@ -94,13 +97,14 @@ class Box with fun read(box: Box) = box.item().extra read(new Box) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.94: fun read(box: Box) = box.item().extra +//│ ║ l.97: fun read(box: Box) = box.item().extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.94: fun read(box: Box) = box.item().extra +//│ ║ l.97: fun read(box: Box) = box.item().extra //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' // Inherited declared fields retain their type through curried constructors. :expect 42 @@ -144,10 +148,10 @@ read(() => Item(42)) :ge fun unused(factory: () -> Int) = factory(1) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.145: fun unused(factory: () -> Int) = factory(1) +//│ ║ l.149: fun unused(factory: () -> Int) = factory(1) //│ ║ ^^^^^^^^^^ //│ ╟── Function with a declared signature expected 0 arguments, but got 1 -//│ ║ l.145: fun unused(factory: () -> Int) = factory(1) +//│ ║ l.149: fun unused(factory: () -> Int) = factory(1) //│ ╙── ^^^^^^^^^ // A callable annotation also hides extra members of the concrete result. @@ -158,7 +162,7 @@ class Child extends Base with fun read(factory: () -> Base) = factory().extra read(() => new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.158: fun read(factory: () -> Base) = factory().extra +//│ ║ l.162: fun read(factory: () -> Base) = factory().extra //│ ║ ^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -172,7 +176,7 @@ fun hidden: (Base) -> Int fun hidden(base) = base.extra hidden(new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.172: fun hidden(base) = base.extra +//│ ║ l.176: fun hidden(base) = base.extra //│ ║ ^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -186,7 +190,7 @@ fun make: () -> Base fun make() = new Child make().extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.187: make().extra +//│ ║ l.191: make().extra //│ ║ ^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -211,11 +215,11 @@ class Box[T] type Loop[T] = Loop[Box[T]] fun unused(value: Loop[Int]) = value.field //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.212: fun unused(value: Loop[Int]) = value.field +//│ ║ l.216: fun unused(value: Loop[Int]) = value.field //│ ║ ^^^^^^^^^^^ //│ ╟── Cannot resolve member 'field' of a value with unknown shape. //│ ╟── This abstract type does not specify a member interface. -//│ ║ l.212: fun unused(value: Loop[Int]) = value.field +//│ ║ l.216: fun unused(value: Loop[Int]) = value.field //│ ╙── ^^^^^^^^^ // A concrete instantiation cannot authorize calls through an opaque type parameter. @@ -223,37 +227,39 @@ fun unused(value: Loop[Int]) = value.field private fun invoke[T](value: T) = value() invoke[() -> Int](() => 42) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.223: private fun invoke[T](value: T) = value() +//│ ║ l.227: private fun invoke[T](value: T) = value() //│ ║ ^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.223: private fun invoke[T](value: T) = value() +//│ ║ l.227: private fun invoke[T](value: T) = value() //│ ║ ^ //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.223: private fun invoke[T](value: T) = value() +//│ ║ l.227: private fun invoke[T](value: T) = value() //│ ║ ^ //│ ╟── Type parameter 'T' does not specify a member interface. -//│ ║ l.223: private fun invoke[T](value: T) = value() +//│ ║ l.227: private fun invoke[T](value: T) = value() //│ ╙── ^ //│ = 42 -// A class-qualified selection still respects an annotated receiver's field boundary. -:ge +// TODO: class qualification preserves inference for a selected constructor field. +:fixme +:expect 42 class Item with val extra = 42 class Box(val item) fun read(box: Box) = box.Box#item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.244: fun read(box: Box) = box.Box#item.extra +//│ ║ l.249: fun read(box: Box) = box.Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.243: class Box(val item) +//│ ║ l.248: class Box(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.244: fun read(box: Box) = box.Box#item.extra +//│ ║ l.249: fun read(box: Box) = box.Box#item.extra //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' // A signature alias supplies the interface to both the body and callers. :expect 42 @@ -281,7 +287,7 @@ class Child extends Base with fun identity[T](x: T): T = x identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.282: identity[Base](new Child).extra +//│ ║ l.288: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -323,11 +329,11 @@ class Box[T](val value: T) (new Box[Base](new Child)).value.extra Box[Base](new Child).value.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.323: (new Box[Base](new Child)).value.extra +//│ ║ l.329: (new Box[Base](new Child)).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.324: Box[Base](new Child).value.extra +//│ ║ l.330: Box[Base](new Child).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -362,24 +368,27 @@ fun test[A](x: Foo[A]) = x.f test(new Foo(new Box(Second(2)))).get().second] //│ = [1, 2] -// Class qualification does not undo an explicit ascription. -:ge +// TODO: a class-qualified selection also infers the selected value member's type. +:fixme +:expect 42 class Item with val extra = 42 class Box with val item = new Item (new Box as Box).Box#item.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.371: (new Box as Box).Box#item.extra +//│ ║ l.378: (new Box as Box).Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.371: (new Box as Box).Box#item.extra +//│ ║ l.378: (new Box as Box).Box#item.extra //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' -// An overloaded type name must not expose its value initializer through an interface. -:ge +// TODO: infer the selected term alternative of an overloaded member's missing type. +:fixme +:expect 42 class Secret with val extra = 42 module Types with @@ -388,13 +397,14 @@ module Types with fun hidden(module types: Types) = types.Item.extra hidden(Types) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.388: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.397: fun hidden(module types: Types) = types.Item.extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.388: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.397: fun hidden(module types: Types) = types.Item.extra //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' // Callback inputs and outputs share inferred type arguments at the application. :expect 43 @@ -411,7 +421,7 @@ class Child extends Base with fun apply[A](f: A -> A, x: A): A = f(x) apply[Base](arg => (arg.extra; arg), new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.412: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ l.422: apply[Base](arg => (arg.extra; arg), new Child) //│ ║ ^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -434,7 +444,7 @@ class Child extends Base with fun read(xs: [Base, Int]) = xs.0.extra read([new Child, 0]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.434: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ l.444: fun read(xs: [Base, Int]) = xs.0.extra //│ ║ ^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -455,10 +465,10 @@ let xs = mut [First(1)] set xs.0 = Second(2) [...xs].0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.456: [...xs].0.first +//│ ║ l.466: [...xs].0.first //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.455: set xs.0 = Second(2) +//│ ║ l.465: set xs.0 = Second(2) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -467,10 +477,10 @@ set xs.0 = Second(2) :ge [1, 2].2 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.468: [1, 2].2 +//│ ║ l.478: [1, 2].2 //│ ║ ^^^^^^^^ //│ ╟── Tuple literal does not contain member '2' -//│ ║ l.468: [1, 2].2 +//│ ║ l.478: [1, 2].2 //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -495,8 +505,9 @@ xs.1 //│ = 42 //│ xs = [1, 42] -// An inherited field retains its missing-annotation diagnostic after value forwarding. -:ge +// TODO: preserve inferred inherited field types through aliases and value forwarding. +:fixme +:expect 42 class Item with val extra = 42 class Parent(val item) @@ -506,33 +517,35 @@ private fun identity(value) = value fun read(box: Annotated) = identity(box.item).extra read(Child(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.506: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.517: fun read(box: Annotated) = identity(box.item).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.502: class Parent(val item) +//│ ║ l.513: class Parent(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.506: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.517: fun read(box: Annotated) = identity(box.item).extra //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' -// Calling an unannotated field reports the same declared-interface restriction. -:ge +// TODO: infer a selected callback field's callable interface from its receiver. +:fixme +:expect 42 class Box(val callback) fun invoke(box: Box) = box.callback() invoke(Box(() => 42)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.523: fun invoke(box: Box) = box.callback() +//│ ║ l.536: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.523: fun invoke(box: Box) = box.callback() +//│ ║ l.536: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^ //│ ╟── Constructor parameter 'callback' has no type annotation. -//│ ║ l.522: class Box(val callback) +//│ ║ l.535: class Box(val callback) //│ ║ ^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'callback'. -//│ ║ l.523: fun invoke(box: Box) = box.callback() +//│ ║ l.536: fun invoke(box: Box) = box.callback() //│ ╙── ^^^ //│ = 42 @@ -541,14 +554,14 @@ invoke(Box(() => 42)) class Box[X](val value: [X, X]) fun foo[A](x: Box[A]) = x.value.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.542: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.555: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.542: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.555: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.542: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.555: fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ // Concrete callers cannot hide the annotation responsible for an abstract tuple element. @@ -559,14 +572,14 @@ class Box[X](val value: [X, X]) private fun foo[A](x: Box[A]) = x.value.0.oops foo[Item](Box[Item]([new Item, new Item])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.559: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.572: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.559: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.572: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.559: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.572: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -576,28 +589,28 @@ class Box[X](val value: [X, X]) type Wrapped[T] = Box[T] fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.577: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.590: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.577: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.590: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.577: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.590: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╙── ^ // Tuple annotations do not need a nominal receiver to produce an abstract element. :ge fun foo[A](x: [A, A]) = x.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.591: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.604: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.591: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.604: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.591: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.604: fun foo[A](x: [A, A]) = x.0.oops //│ ╙── ^ // An unannotated field can be reached through a function result annotation and an alias. @@ -606,46 +619,48 @@ class Box(val value) type Wrapped = Box fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.607: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.620: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.605: class Box(val value) +//│ ║ l.618: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.607: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.620: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╙── ^^^^^^^ -// Explicit ascription supplies the annotation even when its operand is a known value. -:ge +// TODO: ascription preserves receiver flow for the selected field's missing type. +:fixme +:expect 42 class Item with val oops = 42 class Box(val value) (Box(new Item) as Box).value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.624: (Box(new Item) as Box).value.oops +//│ ║ l.638: (Box(new Item) as Box).value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.623: class Box(val value) +//│ ║ l.637: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.624: (Box(new Item) as Box).value.oops +//│ ║ l.638: (Box(new Item) as Box).value.oops //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' // An abstract function result remains unknown after a nested tuple projection. :ge fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.639: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.654: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.639: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.654: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.639: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.654: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╙── ^ // A concrete annotation supplies the nested member interface without losing tuple precision. @@ -657,26 +672,26 @@ fun foo(x: Box[Item]) = x.value.0.oops foo(Box[Item]([new Item, new Item])) //│ = 42 -// Omitted type arguments are unknown; the enclosing class still has its interface. +// Without supporting flow, an omitted argument cannot justify an arbitrary member. :ge class Pair[A, B](val value: [A, B]) fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.663: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.678: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Type argument 'B' is omitted in this annotation, so its shape is unknown. -//│ ║ l.663: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.678: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╙── ^^^^ -// An omitted alias parameter retains the annotation that left it unspecified. +// An unfilled omitted alias argument likewise cannot justify an arbitrary member. :ge type HalfPair[T] = Pair[Int, T] fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.675: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.690: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. -//│ ║ l.675: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.690: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╙── ^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls index 5a1074db0f..d3c4a15aaa 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls @@ -20,3 +20,107 @@ class Second(val second: Int) private fun apply[A](x: A, f) = f(x) [apply(First(5), x => x.first), apply(Second(6), x => x.second)] //│ = [5, 6] + +// TODO: an omitted trailing type argument is a hole inferred from each caller. +// The written Int argument and the Pair interface remain in place. +:fixme +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Pair[A, B](val value: [A, B]) +private fun second(pair: Pair[Int]) = pair.value.1 +[second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.32: [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'first' of a value with unknown shape. +//│ ╟── Type argument 'B' is omitted in this annotation, so its shape is unknown. +//│ ║ l.31: private fun second(pair: Pair[Int]) = pair.value.1 +//│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.32: [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'second' of a value with unknown shape. +//│ ╟── Type argument 'B' is omitted in this annotation, so its shape is unknown. +//│ ║ l.31: private fun second(pair: Pair[Int]) = pair.value.1 +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[1, 2]', got: 'undefined' + +// TODO: bare alias uses infer their omitted argument without losing the alias body. +:fixme +:expect [3, 4] +class First(val first: Int) +class Second(val second: Int) +class Pair[A, B](val value: [A, B]) +type HalfPair[T] = Pair[Int, T] +private fun second(pair: HalfPair) = pair.value.1 +[second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.58: [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'first' of a value with unknown shape. +//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. +//│ ║ l.57: private fun second(pair: HalfPair) = pair.value.1 +//│ ╙── ^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.58: [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'second' of a value with unknown shape. +//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. +//│ ║ l.57: private fun second(pair: HalfPair) = pair.value.1 +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[3, 4]', got: 'undefined' + +// TODO: a bare Array annotation has an inferred element hole in both positions. +// This case has no declared type binder to instantiate at either call. +:fixme +:expect [5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun append(xs: Array, value) = xs.push(value) +let xs = mut [] +let ys = mut [] +append(xs, First(5)) +append(ys, Second(6)) +[xs.0.first, ys.0.second] +//│ ╔══[COMPILATION ERROR] This selection of member 'first' has no resolved target +//│ ║ l.87: [xs.0.first, ys.0.second] +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[5, 6]', got: 'undefined' +//│ xs = [First(5)] +//│ ys = [Second(6)] + +// A returned closure observes annotated and inferred fields after the outer call. +:expect [7, 8, 9, 10] +class First(val first: Int) +class Second(val second: Int) +private fun delayed[A](x: A, y) = () => [x, y] +let left = delayed(First(7), Second(8)) +let right = delayed(Second(9), First(10)) +[left().0.first, left().1.second, right().0.second, right().1.first] +//│ = [7, 8, 9, 10] +//│ left = fun +//│ right = fun + +// Inferring a base member's result must not assume every override returns that shape. +// Either an incompatible override or the unsupported selection must be rejected. +:ge +class Item(val value: Int) +class Other(val other: Int) +class Base with + fun item() = Item(1) +class Child extends Base with + fun item() = Other(2) +private fun read(x: Base) = x.item().value +read(new Child) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.117: private fun read(x: Base) = x.item().value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── The shape of this value is unknown. +//│ ║ l.117: private fun read(x: Base) = x.item().value +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 831c0c586b6e31c86056111c7c44899fc24d82cb Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 24 Sep 2026 22:56:43 +0800 Subject: [PATCH 094/228] Preserve quantified resolution signatures and bound instance allocation --- doc/new-resolution-type-value-flow.md | 20 +++-- .../test/scala/hkmc2/CompilerCacheTest.scala | 38 ++++++++ .../scala/hkmc2/TypeInstantiationTest.scala | 64 ++++++++++++++ .../main/scala/hkmc2/codegen/Erasure.scala | 2 + .../scala/hkmc2/semantics/Elaborator.scala | 4 +- .../scala/hkmc2/semantics/NewResolver.scala | 86 +++++++++++++++---- .../hkmc2/semantics/NewResolverState.scala | 34 ++++++++ .../main/scala/hkmc2/semantics/Shape.scala | 2 +- .../main/scala/hkmc2/semantics/Symbol.scala | 9 ++ .../scala/hkmc2/semantics/TypeShape.scala | 16 ++++ .../mlscript/newres/PartialSignatures.mls | 44 ++++++++++ 11 files changed, 288 insertions(+), 31 deletions(-) create mode 100644 hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 92213f75b4..ffcb2de718 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -6,12 +6,13 @@ nominal-only representation at annotation boundaries: parameter and result annotations, ascriptions, generic arguments, and annotated tuple fields retain a type reference until an operation requests its interface. -The wrapper refactor is implemented. The design below specifies the next -implementation: finite instantiation of explicit binders, contextual views of -partial signatures, inference for explicit and omitted holes, and bidirectional -constraints with variance. These changes are not implemented yet. The representation -and implementation order below are the outcome of the design review, not evidence -that the recursive acceptance cases already work. +Instance wrappers and preservation of quantified signature binders and bounds +are implemented. Explicit type application also observes annotated polymorphic +values. The bounded binder-instance cache is implemented and tested independently; +calls do not yet use it. Contextual views, bidirectional constraints, variance, +hole inference, and inferred member interfaces remain to be connected to it. +The representation and implementation order below are the outcome of the design +review, not evidence that the recursive acceptance cases already work. See the [resolver notes](new-resolution-design.md) for current behavior and the [migration worklist](new-resolution-suite-migration.md) for remaining ports. @@ -219,9 +220,10 @@ views nor instantiated callable views should manufacture a new original owner. Normalize binders from inline declarations and separate signatures into a reusable scheme with its parameter bounds, declared type fragments, and links to -inference for unannotated parts. Do not require a complete signature. Currently, -`typeResolution` strips `Forall` to its body; that loses the information needed -to instantiate separate polymorphic signatures. Preserve the quantifiers first. +inference for unannotated parts. Do not require a complete signature. +`TypeShape.Polymorphic` retains `Forall` binders and bound references; +`DeclaredTypeParameter` carries their interpreted bounds on callable views. +These original identities must be used when instantiating separate signatures. An instantiated callable must retain one immutable substitution from that scheme's own binders to the site's parameter symbols. Use it for parameter diff --git a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala index e0a2a344b4..eab7d613ee 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala @@ -78,6 +78,44 @@ class CompilerCacheTest extends AnyFunSuite: graph.withDependency(fileC, fileA)(cycle => assert(cycle == List(fileC, fileA, fileB, fileC))): fail("The dependency closing the cycle should not be requested") + test("legacy artifacts bind unused syntax hosts before consumers observe them"): + import io.PlatformPath.given + import semantics.* + import utils.* + + val tempDir = os.temp.dir(prefix = "compiler-cache-ownership-") + val file: io.Path = tempDir / "Library.mls" + os.write(tempDir / "Library.mls", + """|module Library with... + |fun identity(x) = x + |""".stripMargin) + try + val paths = TestFolders.compilerPaths(os.pwd) + val cfg = Config.default(TestFolders.mainTestDir(os.pwd)) + assert(!cfg.language.useNewResolution) + val compiler = CompilerCtx.fresh(io.FileSystem.default, paths, cfg) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace: Boolean = false + given Raise = diagnostic => fail(diagnostic.theMsg) + val prelude = compiler.getPrelude(paths.preludeFile) + val artifact = compiler.getElaboratedBlock(file, prelude.ctx) + val consumers = List.fill(2)(new Elaborator.State().newResolverState) + def check(statement: Statement, source: NewResolverState): Unit = + statement match + case term: Term => + assert(term.originalData.owner eq source) + assert(term.originalData.completed) + val before = term.shapeListeners.size + consumers.foreach: consumer => + term.inferenceHost(using consumer).subscribe(_ => ())(using consumer) + assert(term.shapeListeners.size == before) + case _ => () + statement.subStatements.foreach(check(_, source)) + check(prelude.term, prelude.state.newResolverState) + check(artifact.term, artifact.state.newResolverState) + finally os.remove.all(tempDir) + test("active dependency graph permits shared acyclic dependencies"): val graph = new ActiveDependencyGraph val fileA = io.Path("/A.mls") diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala new file mode 100644 index 0000000000..86842cf096 --- /dev/null +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala @@ -0,0 +1,64 @@ +package hkmc2 + +import org.scalatest.funsuite.AnyFunSuite + +import hkmc2.utils.* +import hkmc2.semantics.* + + +class TypeInstantiationTest extends AnyFunSuite: + test("a definition and static site allocate their binder group once"): + given owner: Elaborator.State = new Elaborator.State + val state = owner.newResolverState + val scheme = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val otherScheme = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val a = new VarSymbol(new syntax.Tree.Ident("A")) + val b = new VarSymbol(new syntax.Tree.Ident("B")) + val firstSite = FlowSymbol.app() + val secondSite = FlowSymbol.app() + val first = state.instantiateTypeParameters(scheme, firstSite, List(a, b)) + (1 to 1000).foreach: _ => + val repeated = state.instantiateTypeParameters(scheme, firstSite, List(a, b)) + assert(repeated(a) eq first(a)) + assert(repeated(b) eq first(b)) + assert(state.allocatedTypeInstanceCount == 2) + val second = state.instantiateTypeParameters(scheme, secondSite, List(a, b)) + val other = state.instantiateTypeParameters(otherScheme, firstSite, List(a, b)) + assert(!(second(a) eq first(a))) + assert(!(other(a) eq first(a))) + assert(first(a).origin eq a) + assert(first(b).origin eq b) + assert(state.allocatedTypeInstanceCount == 6) + intercept[AssertionError]: + state.instantiateTypeParameters(scheme, firstSite, List(a)) + + test("instances do not copy checking candidates or listeners and cannot be reinstantiated"): + given owner: Elaborator.State = new Elaborator.State + given state: NewResolverState = owner.newResolverState + val scheme = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val parameter = new VarSymbol(new syntax.Tree.Ident("A")) + parameter.inferenceHost.publish(DynShape()) + parameter.subscribeToShapes(_ => ()) + val instance = state.instantiateTypeParameters(scheme, FlowSymbol.app(), List(parameter))(parameter) + assert(instance.currentShapes.isEmpty) + assert(instance.shapeListeners.isEmpty) + assert(parameter.currentShapes.nonEmpty) + assert(parameter.shapeListeners.nonEmpty) + intercept[IllegalArgumentException]: + state.instantiateTypeParameters(scheme, FlowSymbol.app(), List(instance)) + + test("consumer instantiation leaves another consumer and the source untouched"): + given owner: Elaborator.State = new Elaborator.State + val source = owner.newResolverState + val first = new Elaborator.State().newResolverState + val second = new Elaborator.State().newResolverState + val scheme = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val parameter = new VarSymbol(new syntax.Tree.Ident("A")) + val site = FlowSymbol.app() + val left = first.instantiateTypeParameters(scheme, site, List(parameter))(parameter) + val right = second.instantiateTypeParameters(scheme, site, List(parameter))(parameter) + left.inferenceHost(using first).publish(DynShape())(using first) + assert(left.currentShapes(using first).nonEmpty) + assert(right.currentShapes(using second).isEmpty) + assert(parameter.currentShapes(using source).isEmpty) + assert(source.allocatedTypeInstanceCount == 0) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala index a9bb7355b6..9a4e932bb1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala @@ -33,6 +33,7 @@ final class Erasure private (using Config, Ctx, State): case Captured(base, _) :: Nil => erase(base, next) case Applied(base, _) :: Nil => erase(base, next) case Function(_, _) :: Nil => ErasedType.Function(S(false)) + case Polymorphic(_, _, body) :: Nil => erase(body, next) case Unit :: Nil => ErasedType.Unit case _ => ErasedType.Unknown @@ -80,6 +81,7 @@ final class Erasure private (using Config, Ctx, State): case TypeShape.Alias(_, S(rhs)) :: Nil => fullResult(rhs, count, seen + res) case TypeShape.Captured(base, _) :: Nil => fullResult(base, count, seen + res) case TypeShape.Applied(base, _) :: Nil => fullResult(base, count, seen + res) + case TypeShape.Polymorphic(_, _, body) :: Nil => fullResult(body, count, seen + res) case TypeShape.Function(_, ret) :: Nil => fullResult(ret, count - 1, seen + res) case _ => ErasedType.Unknown val resultType = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 1cb4e7580a..f012d87d69 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -2956,7 +2956,7 @@ extends Importer: // TODO handle name clashes if newResolution then InterfaceExposure(this).check(exports, Nil) - rstate.completeBlock(res) + rstate.completeBlock(res) (res, newCtx) def topLevel(sts: Block): Ctxl[(Blk, Ctx)] = @@ -2970,7 +2970,7 @@ extends Importer: val values = res.stats.collect: case DefineVar(sym: LocalVarSymbol, rhs) => Term.SimpleRef(sym)(new Ident(sym.nme).withLocOf(rhs)) InterfaceExposure(this).check(exports, res.res :: values) - rstate.completeBlock(res) + rstate.completeBlock(res) (res, ctx) def computeVariances(s: Statement): Unit = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 80bb02ccf6..ad8ecba2e5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -106,7 +106,11 @@ class NewResolver: result.publish(TypeShape.Abstract) case Capture(base, thru) => result.publish(TypeShape.Captured(typeResolution(base), thru)) case TyApp(base, args) => result.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) - case Forall(_, _, body) => typeResolution(body).listen(result.publish) + case Forall(params, outer, body) => + result.publish(TypeShape.Polymorphic(params.map: param => + TypeQuantifier(TypeShape.Parameter(param.sym, param.sym.inferenceHost), + param.lb.map(typeResolution), param.ub.map(typeResolution)) + , outer, typeResolution(body))) case CompType(left, right, union) => val l = typeResolution(left) val r = typeResolution(right) @@ -146,7 +150,11 @@ class NewResolver: def registerSignature(sign: Term)(using NewResolverState): Unit = sign.typeInterpretation = S(typeResolution(sign)) sign match - case Forall(_, _, body) => registerSignature(body) + case Forall(params, _, body) => + params.foreach: param => + param.lb.foreach(registerSignature) + param.ub.foreach(registerSignature) + registerSignature(body) case FunTy(lhs, rhs, _) => lhs match case Tup(fields) => fields.foreach: @@ -165,6 +173,20 @@ class NewResolver: case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => bindings(symbol) case _ => DeclaredType(resolution, bindings) + /** A parameterless generic declaration returns a polymorphic value. Keep its + * binders around the annotation until the value's interface is observed. + */ + private def quantifiedType(tpe: DeclaredType, parameters: Ls[VarSymbol])(using NewResolverState): DeclaredType = + if parameters.isEmpty then tpe else + val resolution = rstate.quantifiedTypes.getOrElseUpdate((tpe.resolution, parameters), { + val result = new TypeResolution(tpe.resolution.source, tpe.resolution.fail) + result.publish(TypeShape.Polymorphic(parameters.map: parameter => + TypeQuantifier(TypeShape.Parameter(parameter, parameter.inferenceHost), N, N) + , N, tpe.resolution)) + result + }) + declaredType(resolution, tpe.bindings) + private def typeViews(using rs: NewResolverState) = rs.typeViews private def abstractTypes(using rs: NewResolverState) = rs.abstractTypes // An omitted argument in an annotation must not subscribe to constructor @@ -271,6 +293,17 @@ class NewResolver: case single => DeclaredParams(S(declaredType(typeResolution(single), current.bindings)) :: Nil, false, N) publish(CallableTypeShape(tpe.resolution.source, ps :: Nil, S(declaredType(ret, current.bindings)), Nil)) + case TypeShape.Polymorphic(params, _, body) => + val binders = params.map: param => + DeclaredTypeParameter(param.parameter, + param.lower.map(declaredType(_, current.bindings)), + param.upper.map(declaredType(_, current.bindings))) + follow(declaredType(body, current.bindings), args, aliases, shape => shape match + case Marked(callable: CallableTypeShape, marks) => + callable.copy(tparams = binders ::: callable.tparams).exit(marks) match + case value: TermShape => publish(value) + case NoShape => () + case _ => publish(shape)) case TypeShape.Tuple(fields) => publish(TupleShape(current.resolution.source, fields.map(field => TupleShape.TypedField(declaredType(field, current.bindings), Nil)))(this)) @@ -321,7 +354,10 @@ class NewResolver: val td = symbol.defn.get def publish(shape: TermShape)(using NewResolverState): Unit = val generic = shape match - case callable: CallableTypeShape => callable.copy(tparams = td.tparams.toList.flatten.map(p => new TypeShape.Parameter(p.sym, p.sym.inferenceHost))) + case callable: CallableTypeShape => + callable.copy(tparams = td.tparams.toList.flatten.map(p => declaredParameter(p.sym)) ::: callable.tparams) + case instance: InstanceShape => + InstanceShape(quantifiedType(instance.tpe, td.tparams.toList.flatten.map(_.sym))) case _ => shape // A synthesized field's signature is in the constructor parameter's // scope. Written member signatures are in their own definition scope. @@ -403,20 +439,28 @@ class NewResolver: case _ => () private def applyTypeArguments(callee: DefnShape | CallableTypeShape, marks: Ls[Marks], args: Ls[Term], source: Term)(using NewResolverState): Unit = - val params = callee match - case callable: CallableTypeShape => callable.tparams + def apply(params: Ls[DeclaredTypeParameter])(using NewResolverState): Unit = + if params.length != args.length then + resolError(source, msg"${callee.describe.capitalize} expected ${params.length} type ${ + "argument".pluralized(params.length)}, but got ${args.length}" -> callee.toLoc :: Nil) + params.zip(args).foreach: (param, arg) => + rstate.markExplicitTypeArgument(param.parameter.symbol, marks) + listenTypeInstances(arg): tpe => + publishParameter(param.parameter.host, tpe.enter(marks)) + callee match + case callable: CallableTypeShape => apply(callable.tparams) case defn: DefnShape => defn.clsDef match - case S(cls) => cls.tparams.map(p => new TypeShape.Parameter(p.sym, p.sym.inferenceHost)) + case S(cls) => apply(cls.tparams.map(p => declaredParameter(p.sym))) case N => defn.defn match - case td: TermDefinition => td.tparams.toList.flatten.map(p => new TypeShape.Parameter(p.sym, p.sym.inferenceHost)) - case _ => Nil - if params.length != args.length then - resolError(source, msg"${callee.describe.capitalize} expected ${params.length} type ${ - "argument".pluralized(params.length)}, but got ${args.length}" -> callee.toLoc :: Nil) - params.zip(args).foreach: (param, arg) => - rstate.markExplicitTypeArgument(param.symbol, marks) - listenTypeInstances(arg): tpe => - publishParameter(param.host, tpe.enter(marks)) + case td: TermDefinition if td.tparams.isEmpty && !td.flags.hasResultAnnotation && td.sign.nonEmpty => + listenTypeViews(declaredType(typeResolution(td.sign.get), Map.empty)): + case Marked(callable: CallableTypeShape, _) => apply(callable.tparams) + case _ => apply(Nil) + case td: TermDefinition => apply(td.tparams.toList.flatten.map(p => declaredParameter(p.sym))) + case _ => apply(Nil) + + private def declaredParameter(symbol: VarSymbol)(using NewResolverState): DeclaredTypeParameter = + DeclaredTypeParameter(TypeShape.Parameter(symbol, symbol.inferenceHost), N, N) // Install each constraint edge before subscribing: callback parameter/result // flow can revisit it immediately. Distinct instantiations retain their marks. @@ -462,6 +506,8 @@ class NewResolver: case actual: TermShape => constrainFunction(declaredType(tpe.resolution, tpe.bindings), actual, marks) case NoShape => () + case TypeShape.Polymorphic(_, _, body) => + follow(declaredType(body, tpe.bindings), args, captures, next) case TypeShape.Nominal(cls) if args.nonEmpty => val (head, context) = value.applicationHead def constrain(param: TyParam, pattern: DeclaredType)(using NewResolverState): Unit = @@ -1494,7 +1540,8 @@ class NewResolver: case S(td: TermDefinition) if td.params.isEmpty => log(s"listenTerm: td.body = ${td.body.fold("N")(_.showDbg)}") resultSignature(td) match - case S(sign) => listenTypeInstances(sign): shape => + case S(sign) => listenTypeInstances(quantifiedType( + declaredType(typeResolution(sign), Map.empty), td.tparams.toList.flatten.map(_.sym))): shape => td.tsym.decl match case S(_: Param) => wrappedListener(MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N)) case _ => wrappedListener(shape) @@ -1644,9 +1691,10 @@ class NewResolver: case _: DynSel | _: DynNew => listener(DynShape()) case TyApp(underlying, args) => listen(underlying, discardMarks): shape => - def instantiate(value: TermShape)(using NewResolverState): Unit = value.applicationHead match - case (callee: (DefnShape | CallableTypeShape), marks) => applyTypeArguments(callee, marks, args, trm) - case _ => () + def instantiate(value: TermShape)(using NewResolverState): Unit = listenInstanceViews(value): viewed => + viewed.applicationHead match + case (callee: (DefnShape | CallableTypeShape), marks) => applyTypeArguments(callee, marks, args, trm) + case _ => () shape match case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => (), false) case value: TermShape => instantiate(value) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 638fd68104..01a61e2e58 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -104,6 +104,8 @@ final class NewResolverState private ( * inference graph keeps private, live host data and all its listeners, so * later calls can still transport arguments and results through these nodes. * This boundary is per block, even when a worksheet reuses its symbol state. + * Legacy blocks also need sealing: new-resolution consumers may import their + * syntax concurrently, so no consumer may claim an unowned original host. */ def completeBlock(block: Statement): Unit = val visited = mutable.Set.empty[Identity[Statement]] @@ -137,6 +139,10 @@ final class NewResolverState private ( case _ => () case IfLike(_, _, branches) => split(branches) case Rcd(_, stats) => stats.foreach(visit) + case Forall(params, _, _) => + params.foreach: param => + param.lb.foreach(visit) + param.ub.foreach(visit) case New(_, _, refinement) => refinement.foreach(r => visit(r._2.blk)) case _ => () statement.subStatements.foreach(visit) @@ -185,6 +191,34 @@ final class NewResolverState private ( new Cache(source.map(_.defnShapes), identity) val typeInterpretations: Cache[Identity[Term], TypeResolution] = new Cache(source.map(_.typeInterpretations), identity) + val quantifiedTypes: Cache[(TypeResolution, Ls[VarSymbol]), TypeResolution] = + new Cache(source.map(_.quantifiedTypes), identity) + // A scheme is owned by its source definition, or by the original interpretation + // of an anonymous quantified annotation. Neither a view nor an instance is an owner. + private val typeInstances: Cache[(AnyDefinitionSymbol | TypeResolution, FlowSymbol), Map[VarSymbol, TypeParameterInstance]] = + new Cache(source.map(_.typeInstances), identity) + private var allocatedTypeInstances: Int = 0 + private[hkmc2] def allocatedTypeInstanceCount: Int = root.allocatedTypeInstances + private[hkmc2] def instantiateTypeParameters(scheme: AnyDefinitionSymbol | TypeResolution, + site: FlowSymbol, parameters: Ls[VarSymbol]): Map[VarSymbol, TypeParameterInstance] = + require(parameters.distinct.length == parameters.length, "A scheme cannot bind a parameter twice") + val origin = scheme match + case constructor: ClassCtorSymbol => constructor.associatedCls + case original => original + val key = (origin, site) + // Graph views in one consumer must agree even when they reach the same + // source definition through different imports. Adopt an inherited group + // before allocating, and put either result in the consumer's canonical cache. + val instances = root.typeInstances.getOrElseUpdate(key, typeInstances.get(key).getOrElse { + // Construct the complete group without activating constraints. Recursive + // subscribers may use it only after the cache contains every binder. + val result = parameters.map: parameter => + parameter -> new TypeParameterInstance(parameter)(using owner) + root.allocatedTypeInstances += result.length + result.toMap + }) + assert(instances.keySet == parameters.toSet, "A source scheme's binders must remain stable") + instances val typeViews: Cache[DeclaredType, TermShapeHost] = new Cache(source.map(_.typeViews), identity) val patternTypes: Cache[(Identity[Pattern.Constructor], InnerSymbol), DeclaredType] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 347cd9c8ef..4416a5be27 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -423,7 +423,7 @@ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool, re * applications constrain the same parameters as inferred arguments. */ final case class CallableTypeShape(source: Term, paramLists: Ls[DeclaredParams], - result: Opt[DeclaredType], tparams: Ls[TypeShape.Parameter]) extends NonAppTermShape: + result: Opt[DeclaredType], tparams: Ls[DeclaredTypeParameter]) extends NonAppTermShape: require(paramLists.nonEmpty) def describe: Str = "function with a declared signature" def toLoc: Opt[Loc] = source.toLoc diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 713f5b33bb..9dc3f42205 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -284,6 +284,15 @@ class VarSymbol(val id: Ident)(using State) // override def toString: Str = s"$name@$uid" override def subst(using s: SymbolSubst): VarSymbol = s.mapVarSym(this) +/** A source type binder instantiated at a static application site. Its inference + * host starts empty: the source binder's checking witnesses and subscriptions + * belong to the generic definition, not to any of its applications. + */ +final class TypeParameterInstance(val origin: VarSymbol)(using State) extends VarSymbol(origin.id): + require(!origin.isInstanceOf[TypeParameterInstance], "Instantiate original type binders only") + decl = origin.decl + val reference: Term.SimpleRef = Term.SimpleRef(this)(origin.id) + class BuiltinSymbol (val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool, val functionLike: Bool, val isPure: Bool)(using State) extends Symbol: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 239dce233c..21acae630e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -15,6 +15,7 @@ enum TypeShape: case Tuple(fields: Ls[TypeResolution]) case Record(source: Term.Rcd, fields: Ls[(RcdField, TypeResolution)]) case Function(params: Term, result: TypeResolution) + case Polymorphic(params: Ls[TypeQuantifier], outer: Opt[VarSymbol], body: TypeResolution) case Applied(base: TypeResolution, args: Ls[TypeResolution]) // The same third-party symbol can have different inference in two exporters. // Retain its originating host so importing a result needs no whole-state copy. @@ -65,6 +66,11 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case Captured(base, _) => base.validate(next) case Applied(base, args) => base.validate(next); args.foreach(_.validate(next)) case Function(_, result) => result.validate(next) + case Polymorphic(params, _, body) => + params.foreach: param => + param.lower.foreach(_.validate(next)) + param.upper.foreach(_.validate(next)) + body.validate(next) case Tuple(fields) => fields.foreach(_.validate(next)) case Record(_, fields) => fields.foreach(_._2.validate(next)) case Union(left, right) => left.validate(next); right.validate(next) @@ -75,3 +81,13 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => * interfaces, not constructor arguments or value-flow capture paths. */ final case class DeclaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType]) + +/** A quantified binder belongs to the source scheme; its bounds retain graph links + * so mutually dependent bounds do not require expanding or copying their types. + */ +final case class TypeQuantifier(parameter: TypeShape.Parameter, + lower: Opt[TypeResolution], upper: Opt[TypeResolution]) + +/** A callable's binder and bounds interpreted in the surrounding lexical bindings. */ +final case class DeclaredTypeParameter(parameter: TypeShape.Parameter, + lower: Opt[DeclaredType], upper: Opt[DeclaredType]) diff --git a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls index d3c4a15aaa..f14b692268 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls @@ -124,3 +124,47 @@ read(new Child) //│ ║ l.117: private fun read(x: Base) = x.item().value //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A separate polymorphic signature retains binders for explicit type application. +:expect [11, 12] +class First(val first: Int) +class Second(val second: Int) +private fun identity: [A] -> A -> A +private fun identity(x) = x +[identity[First](First(11)).first, identity[Second](Second(12)).second] +//│ = [11, 12] + +// A polymorphic value annotation also retains its own binders. +:expect 13 +class Item(val value: Int) +let identity = (x => x) as [A] -> A -> A +identity[Item](Item(13)).value +//│ = 13 +//│ identity = fun identity + +// Explicit arguments of an annotated polymorphic value restrict its result interface. +:ge +class Base +class Child extends Base with + val extra = 1 +let identity = (x => x) as [A] -> A -> A +identity[Base](new Child).extra +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.151: identity[Base](new Child).extra +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ identity = fun identity + +// Type application checks the retained quantified binder count. +:ge +let identity = (x => x) as [A] -> A -> A +identity[Int, Int](1) +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.162: identity[Int, Int](1) +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Function with a declared signature expected 1 type argument, but got 2 +//│ ║ l.161: let identity = (x => x) as [A] -> A -> A +//│ ╙── ^^^^^^ +//│ = 1 +//│ identity = fun identity From 11459e444880bd3a48086d8732d38fa252c97446 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 00:36:11 +0800 Subject: [PATCH 095/228] Instantiate callable signatures at application sites --- doc/new-resolution-type-value-flow.md | 21 ++- .../scala/hkmc2/TypeInstantiationTest.scala | 26 ++++ .../scala/hkmc2/semantics/Elaborator.scala | 1 + .../scala/hkmc2/semantics/NewResolver.scala | 147 +++++++++++++----- .../hkmc2/semantics/NewResolverState.scala | 2 + .../main/scala/hkmc2/semantics/Shape.scala | 8 +- .../scala/hkmc2/semantics/TypeShape.scala | 16 +- .../test/mlscript/newres/GenericMethods.mls | 9 +- .../test/mlscript/newres/ObjectMethods.mls | 7 +- .../mlscript/newres/PartialSignatures.mls | 66 ++++++++ 10 files changed, 251 insertions(+), 52 deletions(-) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index ffcb2de718..0c00a6b2ce 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -8,9 +8,14 @@ a type reference until an operation requests its interface. Instance wrappers and preservation of quantified signature binders and bounds are implemented. Explicit type application also observes annotated polymorphic -values. The bounded binder-instance cache is implemented and tested independently; -calls do not yet use it. Contextual views, bidirectional constraints, variance, -hole inference, and inferred member interfaces remain to be connected to it. +values. Calls through complete callable signatures use the bounded binder-instance +cache. `DeclaredType.instances` carries a flat substitution through their declared +components; curried tails retain it, and independently quantified returned callables +instantiate their own binders. Stored specializations of these callable signatures +retain supplied arguments until application. Partial and inferred bodies still use +the earlier inference path. Contextual views of those bodies, bidirectional +constraints, variance, hole inference, and inferred member interfaces remain to be +connected to call-site instantiation. The representation and implementation order below are the outcome of the design review, not evidence that the recursive acceptance cases already work. See the [resolver notes](new-resolution-design.md) @@ -498,6 +503,16 @@ Verify obligations delivered before and after edge creation, and two callers of one inner site with different enclosing marks. Count cache growth until saturation; passing a shallow recursion test is not sufficient evidence of termination. +`TypeInstantiationTest` checks that repeated scheme/site allocation reuses the +whole group, distinct applications through a stored declaration allocate distinct +groups, and consumers do not copy generic checking candidates into their instances. +For the implemented signature views, the substitution map contains only original +binder keys and canonical instance-symbol values. Composition cannot add a nested +environment or a compound type to that map. Instantiated callable views are cached +before installing supplied-argument listeners. This bounds this part of the graph; +the remaining inferred-body views and recursive alias bindings still need the +corresponding convergence checks before the full design is complete. + During implementation, assert that a call instance's origin is an original binder, all of one scheme's binders are allocated before its constraints are activated, views are composed rather than nested, and generic checking witnesses never become diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala index 86842cf096..cf56c26e10 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala @@ -7,6 +7,32 @@ import hkmc2.semantics.* class TypeInstantiationTest extends AnyFunSuite: + test("polymorphic declaration calls allocate by application rather than shared reference"): + import io.PlatformPath.given + val directory = os.temp.dir(prefix = "call-site-instances-") + val file: io.Path = directory / "Library.mls" + os.write(directory / "Library.mls", + """|#lang(0.3.x, strictResolution: true) + |module Library with + | fun identity[A](x: A): A = x + | val shared = identity + | val first = shared(1) + | val second = shared("two") + |""".stripMargin) + try + val paths = TestFolders.compilerPaths(os.pwd) + val compiler = CompilerCtx.fresh(io.FileSystem.default, paths, + Config.default(TestFolders.mainTestDir(os.pwd))) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace: Boolean = false + given Raise = diagnostic => fail(diagnostic.theMsg) + val prelude = compiler.getPrelude(paths.preludeFile) + val artifact = compiler.getElaboratedBlock(file, prelude.ctx) + assert(artifact.state.newResolverState.allocatedTypeInstanceCount == 2) + assert(prelude.state.newResolverState.allocatedTypeInstanceCount == 0) + finally os.remove.all(directory) + test("a definition and static site allocate their binder group once"): given owner: Elaborator.State = new Elaborator.State val state = owner.newResolverState diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f012d87d69..c969e7cebb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -2229,6 +2229,7 @@ extends Importer: TermDefFlags.empty.copy(isMethod = isMethod, hasResultAnnotation = td.annotatedResultType.isDefined), mfn, annotations, N).withLocOf(td) sym.tsym = S(tsym) tsym.defn = S(tdf) + if newResolution then checkDeclaredResult(tdf) tdf go(sts, Nil, tdf :: acc) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index ad8ecba2e5..b26313be1c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -171,7 +171,17 @@ class NewResolver: private[semantics] def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType])(using NewResolverState): DeclaredType = resolution.currentShapes.toList match case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => bindings(symbol) - case _ => DeclaredType(resolution, bindings) + case _ => DeclaredType(resolution, bindings, Map.empty) + + private def declaredType(resolution: TypeResolution, context: DeclaredType)(using NewResolverState): DeclaredType = + declaredType(resolution, context.bindings).instantiate(context.instances) + + private def instanceType(instance: TypeParameterInstance)(using NewResolverState): DeclaredType = + declaredType(typeResolution(instance.reference), Map.empty) + + private def effectiveBindings(tpe: DeclaredType)(using NewResolverState): Map[VarSymbol, DeclaredType] = + tpe.instances.map((parameter, instance) => parameter -> instanceType(instance)) ++ + tpe.bindings.map((parameter, bound) => parameter -> bound.instantiate(tpe.instances)) /** A parameterless generic declaration returns a polymorphic value. Keep its * binders around the annotation until the value's interface is observed. @@ -185,7 +195,7 @@ class NewResolver: , N, tpe.resolution)) result }) - declaredType(resolution, tpe.bindings) + declaredType(resolution, tpe) private def typeViews(using rs: NewResolverState) = rs.typeViews private def abstractTypes(using rs: NewResolverState) = rs.abstractTypes @@ -236,9 +246,9 @@ class NewResolver: def follow(current: DeclaredType, args: Ls[DeclaredType], aliases: Set[TypeResolution], publish: Listener)(using NewResolverState): Unit = current.resolution.listen: shape => def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = - current.bindings ++ params.zipWithIndex.map: (param, index) => + effectiveBindings(current) ++ params.zipWithIndex.map: (param, index) => param.sym -> args.lift(index).getOrElse(abstractType(current.resolution, S(param.sym))) - def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current.bindings), Nil, aliases, publish) + def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), Nil, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) case TypeShape.Inferred(value) => listenInstanceViews(value)(publish) @@ -262,11 +272,12 @@ class NewResolver: aliases + current.resolution, publish) case N => publish(OpaqueTypeShape(tpe.resolution.source)) case TypeShape.Applied(base, params) => - follow(declaredType(base, current.bindings), params.map(declaredType(_, current.bindings)), aliases, publish) + follow(declaredType(base, current), params.map(declaredType(_, current)), aliases, publish) case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match - case S(bound) => follow(bound, Nil, aliases, publish) + case S(bound) => follow(bound.instantiate(current.instances), Nil, aliases, publish) case N => - listenTypeArgument(host): value => + val target = current.instances.get(symbol).fold(host)(_.inferenceHost) + listenTypeArgument(target): value => // Report the use of an abstract parameter in a signature, not // just its declaration. This survives substitution through // generic members and nested tuple or function annotations. @@ -279,7 +290,7 @@ class NewResolver: case value: TermShape => listenInstanceViews(value)(publish) case NoShape => () case TypeShape.Captured(base, thru) => - follow(declaredType(base, current.bindings), args, aliases, + follow(declaredType(base, current), args, aliases, shape => publish(shape match // Nominal/function interfaces are closed descriptions. Only // parameter flow carries a lexical activation to transport. @@ -288,26 +299,28 @@ class NewResolver: case TypeShape.Function(params, ret) => val ps = params match case Tup(fields) => DeclaredParams(fields.collect: - case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current.bindings)) + case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current)) , fields.exists(!_.isInstanceOf[Fld]), N) - case single => DeclaredParams(S(declaredType(typeResolution(single), current.bindings)) :: Nil, false, N) + case single => DeclaredParams(S(declaredType(typeResolution(single), current)) :: Nil, false, N) publish(CallableTypeShape(tpe.resolution.source, ps :: Nil, - S(declaredType(ret, current.bindings)), Nil)) + S(declaredType(ret, current)), N, N, N)) case TypeShape.Polymorphic(params, _, body) => + val symbols = params.map(_.parameter.symbol) + val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances -- symbols) val binders = params.map: param => DeclaredTypeParameter(param.parameter, - param.lower.map(declaredType(_, current.bindings)), - param.upper.map(declaredType(_, current.bindings))) - follow(declaredType(body, current.bindings), args, aliases, shape => shape match + param.lower.map(declaredType(_, lexical)), + param.upper.map(declaredType(_, lexical))) + follow(declaredType(body, lexical), args, aliases, shape => shape match case Marked(callable: CallableTypeShape, marks) => - callable.copy(tparams = binders ::: callable.tparams).exit(marks) match + callable.copy(scheme = S(TypeScheme(current.resolution, binders ::: callable.tparams))).exit(marks) match case value: TermShape => publish(value) case NoShape => () case _ => publish(shape)) case TypeShape.Tuple(fields) => publish(TupleShape(current.resolution.source, - fields.map(field => TupleShape.TypedField(declaredType(field, current.bindings), Nil)))(this)) - case TypeShape.Record(source, fields) => publish(RecordTypeShape(source, fields, current.bindings)) + fields.map(field => TupleShape.TypedField(declaredType(field, current), Nil)))(this)) + case TypeShape.Record(source, fields) => publish(RecordTypeShape(source, fields, effectiveBindings(current))) case TypeShape.Union(left, right) => next(left); next(right) case TypeShape.Intersection(left, right) => next(left); next(right) case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) @@ -342,7 +355,7 @@ class NewResolver: capturedTypes.getOrElseUpdate((bound, scope), { val captured = new TypeResolution(bound.resolution.source, bound.resolution.fail) captured.publish(TypeShape.Captured(bound.resolution, scope)) - declaredType(captured, bound.bindings) + declaredType(captured, bound) }) private def listenDeclaredMember(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], flow: FlowSymbol, @@ -352,10 +365,15 @@ class NewResolver: case S(symbol: TermSymbol) if !symbol.isInstanceOf[ClassCtorSymbol] => selected(symbol) val td = symbol.defn.get + // A selected generic method binds its own parameters anew. Receiver + // bindings can mention an earlier call of that same source method in + // their values, but cannot bind the new method's local names. + val capturedBindings = bindings -- td.tparams.toList.flatten.map(_.sym) def publish(shape: TermShape)(using NewResolverState): Unit = val generic = shape match case callable: CallableTypeShape => - callable.copy(tparams = td.tparams.toList.flatten.map(p => declaredParameter(p.sym)) ::: callable.tparams) + val parameters = td.tparams.toList.flatten.map(p => declaredParameter(p.sym)) ::: callable.tparams + callable.copy(scheme = if parameters.isEmpty then N else S(TypeScheme(td.tsym, parameters)), declaration = S(td)) case instance: InstanceShape => InstanceShape(quantifiedType(instance.tpe, td.tparams.toList.flatten.map(_.sym))) case _ => shape @@ -373,10 +391,10 @@ class NewResolver: // as a new-resolution Capture does, before the member exits it. val legacyParameters = mutable.Set.empty[VarSymbol] def visit(term: Term)(using NewResolverState): Unit = term match - case Ref(symbol: VarSymbol) if bindings.contains(symbol) => legacyParameters += symbol + case Ref(symbol: VarSymbol) if capturedBindings.contains(symbol) => legacyParameters += symbol case _ => term.subTerms.foreach(visit) if !td.tsym.decl.exists(_.isInstanceOf[Param]) then visit(sign) - val scopedBindings = bindings.map: (symbol, bound) => + val scopedBindings = capturedBindings.map: (symbol, bound) => symbol -> (if legacyParameters(symbol) then captureType(bound, td.tsym) else bound) declaredType(typeResolution(sign), scopedBindings) val result = resultSignature(td).map(signature) @@ -385,7 +403,7 @@ class NewResolver: else if td.params.nonEmpty then publish(CallableTypeShape(source, td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), - ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature))), result, Nil)) + ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature))), result, N, N, S(td))) else result match case S(tpe) => listenTypeInstances(tpe)(publish) case N => @@ -462,6 +480,45 @@ class NewResolver: private def declaredParameter(symbol: VarSymbol)(using NewResolverState): DeclaredTypeParameter = DeclaredTypeParameter(TypeShape.Parameter(symbol, symbol.inferenceHost), N, N) + /** The first term application owns the binder group. Curried tails carry the + * instantiated references and no scheme, so applying a later list reuses it. + */ + private def instantiateCallable(callable: CallableTypeShape, site: FlowSymbol, + marks: Ls[Marks])(using NewResolverState): CallableTypeShape = callable.scheme match + case N => callable + case S(scheme) => + val key = (callable, site, marks) + rstate.instantiatedCallables.get(key) match + case S(instantiated) => instantiated + case N => + val substitution = rstate.instantiateTypeParameters(scheme.owner, site, scheme.parameters.map(_.parameter.symbol)) + def instantiate(tpe: DeclaredType): DeclaredType = tpe.instantiate(substitution) + val instantiated = callable.copy( + paramLists = callable.paramLists.map: params => + DeclaredParams(params.params.map(_.map(instantiate)), params.hasRest, params.rest.map(instantiate)) + , result = callable.result.map(instantiate), scheme = N, supplied = N) + // Reentrant observations must find the view before supplied arguments + // can publish candidates or trigger another observation of this call. + rstate.instantiatedCallables(key) = instantiated + callable.supplied.foreach: arguments => + scheme.parameters.zip(arguments).foreach: (parameter, argument) => + val instance = substitution(parameter.parameter.symbol) + rstate.markExplicitTypeArgument(instance, marks) + listenTypeInstances(argument): value => + publishParameter(instance, value.enter(marks)) + instantiated + + /** The body must satisfy its annotated result even if no value escapes or no + * call inspects the implementation. In particular a returned closure receives + * its declared domain before its member targets are completed. + */ + def checkDeclaredResult(definition: TermDefinition)(using NewResolverState): Unit = + definition.body.foreach: body => + resultSignature(definition).foreach: sign => + val expected = declaredType(typeResolution(sign), Map.empty) + listenTerm(body): shape => + constrainFunction(expected, shape, Nil) + // Install each constraint edge before subscribing: callback parameter/result // flow can revisit it immediately. Distinct instantiations retain their marks. private def typeConstraints(using rs: NewResolverState) = rs.typeConstraints @@ -477,16 +534,18 @@ class NewResolver: val next = seen + tpe.resolution tpe.resolution.listen: case TypeShape.Parameter(symbol, host) => tpe.bindings.get(symbol) match - case S(bound) => follow(bound, Nil, captures, next) - case N if !rstate.hasExplicitTypeArgument(symbol, marks) => publishParameter(host, value.enter(captures)) - case _ => () + case S(bound) => follow(bound.instantiate(tpe.instances), Nil, captures, next) + case N => + val target = tpe.instances.get(symbol) + if !rstate.hasExplicitTypeArgument(target.getOrElse(symbol), marks) then + publishParameter(target.fold(host)(_.inferenceHost), value.enter(captures)) case TypeShape.Captured(base, thru) => - follow(declaredType(base, tpe.bindings), args, + follow(declaredType(base, tpe), args, EntryMark(ResolutionBoundary(thru), N, NoMarks) :: captures, next) case TypeShape.Applied(base, params) => - follow(declaredType(base, tpe.bindings), params.map(declaredType(_, tpe.bindings)), captures, next) + follow(declaredType(base, tpe), params.map(declaredType(_, tpe)), captures, next) case TypeShape.Alias(symbol, S(rhs)) => - val bindings = tpe.bindings ++ symbol.defn.get.tparams.map(_.sym).zip(args) + val bindings = effectiveBindings(tpe) ++ symbol.defn.get.tparams.map(_.sym).zip(args) follow(declaredType(rhs, bindings), Nil, captures, next) case TypeShape.Tuple(fields) => value.enter(captures) match case Marked(tuple: TupleShape, context) => @@ -495,19 +554,19 @@ class NewResolver: case MemberLookup.Indexed(actual, inner) => listenTupleField(actual): shape => shape.exit(inner).exit(context) match case value: TermShape => - inferTypeArguments(declaredType(field, tpe.bindings), value, marks) + inferTypeArguments(declaredType(field, tpe), value, marks) case NoShape => () case _ => () case _ => () case TypeShape.Record(_, fields) => value.enter(captures) match - case actual: TermShape => constrainRecord(fields, tpe.bindings, actual, marks) + case actual: TermShape => constrainRecord(fields, effectiveBindings(tpe), actual, marks) case NoShape => () case TypeShape.Function(_, _) => value.enter(captures) match case actual: TermShape => - constrainFunction(declaredType(tpe.resolution, tpe.bindings), actual, marks) + constrainFunction(tpe, actual, marks) case NoShape => () case TypeShape.Polymorphic(_, _, body) => - follow(declaredType(body, tpe.bindings), args, captures, next) + follow(declaredType(body, tpe), args, captures, next) case TypeShape.Nominal(cls) if args.nonEmpty => val (head, context) = value.applicationHead def constrain(param: TyParam, pattern: DeclaredType)(using NewResolverState): Unit = @@ -1118,7 +1177,8 @@ class NewResolver: return case _ => () lhs match - case Marked(callable: CallableTypeShape, context) => + case Marked(original: CallableTypeShape, context) => + val callable = instantiateCallable(original, res.resSym, context :: Nil) val ps = callable.paramLists.head zipArgumentShapes(context :: Nil, ps.params.length, ps.hasRest, args, res, lhs): (index, value) => (if index < ps.params.length then ps.params(index) else ps.rest).foreach: tpe => @@ -1555,8 +1615,10 @@ class NewResolver: MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N) case _ => shape wrappedListener(captured) - case S(d: TermDefinition) if d.body.isEmpty && !d.tsym.isInstanceOf[ClassCtorSymbol] - && d.tsym.owner.exists(_.asDefnSym.defn.exists(_.tparams.nonEmpty)) => + case S(d: TermDefinition) if !d.tsym.isInstanceOf[ClassCtorSymbol] && + ((d.sign.nonEmpty && (!d.flags.hasResultAnnotation || d.params.forall(ps => + (ps.params ::: ps.restParam.toList).forall(_.sign.nonEmpty)))) || + (d.body.isEmpty && d.tsym.owner.exists(_.asDefnSym.defn.exists(_.tparams.nonEmpty)))) => listenDeclaredMember(bms, instanceBindings(d, markss), resSym, trm, N, selected, receiver): value => value.exit(markss) match case value: TermShape => listener(value) @@ -1693,12 +1755,21 @@ class NewResolver: listen(underlying, discardMarks): shape => def instantiate(value: TermShape)(using NewResolverState): Unit = listenInstanceViews(value): viewed => viewed.applicationHead match - case (callee: (DefnShape | CallableTypeShape), marks) => applyTypeArguments(callee, marks, args, trm) - case _ => () + case (callable: CallableTypeShape, marks) => + if callable.tparams.length != args.length then + resolError(trm, msg"${callable.describe.capitalize} expected ${callable.tparams.length} type ${ + "argument".pluralized(callable.tparams.length)}, but got ${args.length}" -> callable.toLoc :: Nil) + val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty)) + callable.copy(supplied = S(supplied)).exit(marks) match + case value: TermShape => listener(value) + case NoShape => () + case (callee: DefnShape, marks) => + applyTypeArguments(callee, marks, args, trm) + listener(value) + case _ => listener(value) shape match case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => (), false) case value: TermShape => instantiate(value) - listener(shape) case mut @ Mut(underlying: Tup) => listener(mutableArray(mut, underlying)) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => listenAggregate(tuple, listener): publish => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 01a61e2e58..a5fd8f9089 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -193,6 +193,8 @@ final class NewResolverState private ( new Cache(source.map(_.typeInterpretations), identity) val quantifiedTypes: Cache[(TypeResolution, Ls[VarSymbol]), TypeResolution] = new Cache(source.map(_.quantifiedTypes), identity) + val instantiatedCallables: Cache[(CallableTypeShape, FlowSymbol, Ls[Marks]), CallableTypeShape] = + new Cache(source.map(_.instantiatedCallables), identity) // A scheme is owned by its source definition, or by the original interpretation // of an anonymous quantified annotation. Neither a view nor an instance is an owner. private val typeInstances: Cache[(AnyDefinitionSymbol | TypeResolution, FlowSymbol), Map[VarSymbol, TypeParameterInstance]] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 4416a5be27..735d73b986 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -423,10 +423,12 @@ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool, re * applications constrain the same parameters as inferred arguments. */ final case class CallableTypeShape(source: Term, paramLists: Ls[DeclaredParams], - result: Opt[DeclaredType], tparams: Ls[DeclaredTypeParameter]) extends NonAppTermShape: + result: Opt[DeclaredType], scheme: Opt[TypeScheme], supplied: Opt[Ls[DeclaredType]], + declaration: Opt[TermDefinition]) extends NonAppTermShape: require(paramLists.nonEmpty) - def describe: Str = "function with a declared signature" - def toLoc: Opt[Loc] = source.toLoc + def tparams: Ls[DeclaredTypeParameter] = scheme.toList.flatMap(_.parameters) + def describe: Str = declaration.fold("function with a declared signature")(d => s"function '${d.bsym.nme}'") + def toLoc: Opt[Loc] = declaration.fold(source.toLoc)(_.toLoc) protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Missing /** An abstract annotation authorizes no operations based on the implementation. */ diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 21acae630e..44ec3df6d4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -80,7 +80,16 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => /** A type with its lexical type-parameter bindings. These bindings describe declared * interfaces, not constructor arguments or value-flow capture paths. */ -final case class DeclaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType]) +final case class DeclaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], + instances: Map[VarSymbol, TypeParameterInstance]): + require(instances.forall((source, instance) => instance.origin eq source), + "A substitution must map original binders to their call-site instances") + /** Instantiation changes references to source binders, never expands their bounds. + * A reference already interpreted in another call retains that interpretation. + * The finite map contains original binders and canonical site instances only. + */ + def instantiate(substitution: Map[VarSymbol, TypeParameterInstance]): DeclaredType = + copy(instances = substitution ++ instances) /** A quantified binder belongs to the source scheme; its bounds retain graph links * so mutually dependent bounds do not require expanding or copying their types. @@ -91,3 +100,8 @@ final case class TypeQuantifier(parameter: TypeShape.Parameter, /** A callable's binder and bounds interpreted in the surrounding lexical bindings. */ final case class DeclaredTypeParameter(parameter: TypeShape.Parameter, lower: Opt[DeclaredType], upper: Opt[DeclaredType]) + +/** The owner is the original declaration, or a quantified annotation for an + * anonymous callable. Applied and imported views retain this identity. + */ +final case class TypeScheme(owner: AnyDefinitionSymbol | TypeResolution, parameters: Ls[DeclaredTypeParameter]) diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index cb7340ecce..ae501b086d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -57,9 +57,10 @@ class Child extends Base with //│ ╔══[COMPILATION ERROR] Resolution error in type application //│ ║ l.56: [1].map[Int, Int]((x, ...) => x) //│ ║ ^^^^^^^^^^^^^^^^^ -//│ ╟── Function with a declared signature expected 1 type argument, but got 2 -//│ ║ l.56: [1].map[Int, Int]((x, ...) => x) -//│ ╙── ^^^^^^^ +//│ ╟── Function 'map' expected 1 type argument, but got 2 +//│ ║ in ../decls/Prelude.mls +//│ ║ l.162: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] // Imported free functions and methods use the same inference as prelude methods. @@ -83,7 +84,7 @@ class Methods with fun test(m: Methods) = m.identity[Base](new Child).extra test(new Methods) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.83: fun test(m: Methods) = m.identity[Base](new Child).extra +//│ ║ l.84: fun test(m: Methods) = m.identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls b/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls index 732afbf3b7..fdfd5e908b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls @@ -26,9 +26,10 @@ Empty.toString(1) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.25: Empty.toString(1) //│ ║ ^^^^^^^^^^^^^^^^^ -//│ ╟── Function with a declared signature expected 0 arguments, but got 1 -//│ ║ l.25: Empty.toString(1) -//│ ╙── ^^^^^^^^^^^^^^ +//│ ╟── Function 'toString' expected 0 arguments, but got 1 +//│ ║ in ../decls/Prelude.mls +//│ ║ l.7: fun toString(): Str +//│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ = "Empty" :expect "empty" diff --git a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls index f14b692268..cb030c5f84 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls @@ -168,3 +168,69 @@ identity[Int, Int](1) //│ ╙── ^^^^^^ //│ = 1 //│ identity = fun identity + +// One stored polymorphic value has separate inferred arguments at each call site. +:expect [14, 15] +class First(val first: Int) +class Second(val second: Int) +let identity = (x => x) as [A] -> A -> A +[identity(First(14)).first, identity(Second(15)).second] +//│ = [14, 15] +//│ identity = fun identity + +// Specialization is retained by the value; it does not specialize the shared source. +:expect [16, 17] +class First(val first: Int) +class Second(val second: Int) +let identity = (x => x) as [A] -> A -> A +let first = identity[First] +let second = identity[Second] +[first(First(16)).first, second(Second(17)).second] +//│ = [16, 17] +//│ first = fun identity +//│ identity = fun identity +//│ second = fun identity + +// Later parameter lists retain the first application's type-parameter instance. +:expect [18, 19] +class First(val first: Int) +class Second(val second: Int) +let keep = (x => y => x) as [A] -> A -> A -> A +let first = keep(First(18)) +let second = keep(Second(19)) +[first(First(0)).first, second(Second(0)).second] +//│ = [18, 19] +//│ first = fun +//│ keep = fun keep +//│ second = fun + +// A stored reference to a fully annotated declaration uses the application's site. +:expect [20, 21] +class First(val first: Int) +class Second(val second: Int) +private fun identity[A](x: A): A = x +let shared = identity +[shared(First(20)).first, shared(Second(21)).second] +//│ = [20, 21] +//│ shared = fun identity + +// A returned scheme binds B at its own call while retaining the captured A. +:expect [22, 23, 24, 25] +class First(val first: Int) +class Second(val second: Int) +let pair = (x => y => [x, y]) as [A] -> A -> ([B] -> B -> [A, B]) +let first = pair(First(22)) +let second = pair(Second(23)) +let left = first(Second(24)) +let right = second(First(25)) +[left.0.first, right.0.second, left.1.second, right.1.first] +//│ = [22, 23, 24, 25] +//│ first = fun +//│ left = [First(22), Second(24)] +//│ pair = fun pair +//│ right = [Second(23), First(25)] +//│ second = fun + +// A private returned closure must be checked from its annotation before any call. +class Item(val value: Int) +private fun get(): Item -> Int = x => x.value From 5857fe0b811ea4432c342454896454cf535e1704 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 00:59:04 +0800 Subject: [PATCH 096/228] Preserve call-site binder views through inferred function bodies --- doc/new-resolution-design.md | 19 +- doc/new-resolution-type-value-flow.md | 40 ++- .../scala/hkmc2/TypeInstantiationTest.scala | 42 +++ .../hkmc2/semantics/InterfaceExposure.scala | 28 +- .../scala/hkmc2/semantics/NewResolver.scala | 263 +++++++++++++----- .../hkmc2/semantics/NewResolverState.scala | 81 +++--- .../main/scala/hkmc2/semantics/Shape.scala | 94 ++++++- .../mlscript/newres/ContextualInference.mls | 57 ++++ 8 files changed, 484 insertions(+), 140 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ContextualInference.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 463faa801d..ddc38974ed 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -67,11 +67,20 @@ nominal parameters, such as `Foo[A]` containing a `Box[A]`. Generic methods use the same flow as free functions, including callback-result inference and curried signatures. Declared callable shapes retain their type parameters. -Generic definitions receive a fresh abstract activation when their type parameters -are declared. This enforces generic opacity independently of visibility, exposure, -or strict mode. The abstract activation cannot match a concrete caller's exit, -so concrete calls still substitute into generic results without specializing the -definition's allowed operations. +Generic definitions receive a distinct checking activation when their type +parameters are declared. Its `RigidTypeShape` witnesses enforce generic opacity +independently of visibility, exposure, or strict mode. Interface observation turns +a witness into an unknown interface, but call-site inference does not copy that +witness as a bound. Symbolic references remain available for substitution instead. + +Inline explicit binders in partially annotated functions use the same finite +definition/application-site allocation as complete callable signatures. The +shared body carries a flat substitution through deferred tuples, records, +callbacks, and closures. A source-flow event's body activation and its value's +caller-side type references remain distinct, including when recursion rebinds the +same original parameter. Contextual observations select compatible activations; +marks continue to distinguish enclosing callers of a shared inner site. See +`newres/ContextualInference.mls` for stored-function and deferred-field cases. ## Declared interfaces and exposure checking diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 0c00a6b2ce..34463f4e89 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -12,10 +12,11 @@ values. Calls through complete callable signatures use the bounded binder-instan cache. `DeclaredType.instances` carries a flat substitution through their declared components; curried tails retain it, and independently quantified returned callables instantiate their own binders. Stored specializations of these callable signatures -retain supplied arguments until application. Partial and inferred bodies still use -the earlier inference path. Contextual views of those bodies, bidirectional -constraints, variance, hole inference, and inferred member interfaces remain to be -connected to call-site instantiation. +retain supplied arguments until application. Inline binders on partial and inferred +functions also instantiate at application sites. Their shared bodies retain flat +substitutions through deferred tuples, records, callbacks, and closures. This does +not yet connect constructors, bidirectional constraints, variance, hole inference, +or inferred nominal member interfaces to call-site instantiation. The representation and implementation order below are the outcome of the design review, not evidence that the recursive acceptance cases already work. See the [resolver notes](new-resolution-design.md) @@ -113,10 +114,10 @@ domain and result. Interpreting a union for member lookup can observe each alternative, while transporting its wrapper preserves the original union node. This refactor alone does not implement the planned distinction between supplying -a type argument and adding an ordinary bound. In particular, the current -`inferTypeArguments` still expands wrappers when inferring from an instance, and -explicit arguments still suppress ordinary refinement. The proposed constraint -work must replace these behaviors together. +a type argument and adding an ordinary bound. `inferTypeArguments` retains wrappers +when contributing a parameter bound and observes them for structural comparison. +Explicit arguments still suppress ordinary refinement; the constraint work must +replace that suppression together with bidirectional flow. ## Variance rules to implement @@ -279,6 +280,22 @@ node under `A -> A_s`; the second subscribes to `y` under the call's marks. A closure returning `x` keeps the first reference after the enclosing call returns. The body, field nodes, and `y` symbol are shared; only their views differ. +The implemented body transport keeps two substitutions separate. A value such as +`InstanceShape(A_outer)` retains the caller's type reference. Its flow into a body +also carries that body's activation, for example `A -> A_inner` at a recursive +call. `ActivatedShape` records this second map on source-flow events; +`ContextualShape` and the tuple/record views retain deferred observations of values. +Dispatch unpacks the activation before performing an operation. Source listeners +accept all activations; an observation with a chosen substitution accepts only +compatible activations of its source node. It must not rewrite an already bound +caller reference to the callee's parameter. `NewResolverState.withInstances` +memoizes flat activation views that share the consumer's hosts, and `inGraph` +preserves the incoming activation when selecting an imported listener's graph. + +`newres/ContextualInference.mls` exercises separate calls through one stored +function reference, mixed annotated/inferred tuple and record fields, callbacks, +and returned closures. These cases use the existing reference marks unchanged. + Generic-body checking observes the source graph with its original abstract binders. Call inference observes it with the site's substitution. The abstract unknown produced to check operations on a rigid binder is a checking witness, @@ -510,8 +527,11 @@ For the implemented signature views, the substitution map contains only original binder keys and canonical instance-symbol values. Composition cannot add a nested environment or a compound type to that map. Instantiated callable views are cached before installing supplied-argument listeners. This bounds this part of the graph; -the remaining inferred-body views and recursive alias bindings still need the -corresponding convergence checks before the full design is complete. +the activation views similarly use finite binder maps and a fixed base state, +never chains of activation states. Unit tests check repeated composition and +imported-view identity, shared consumer hosts, and exporter isolation. Whole-graph +listener convergence and recursive alias bindings still need the corresponding +checks before the full design is complete. During implementation, assert that a call instance's origin is an original binder, all of one scheme's binders are allocated before its constraints are activated, diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala index cf56c26e10..0b0cb23199 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala @@ -88,3 +88,45 @@ class TypeInstantiationTest extends AnyFunSuite: assert(right.currentShapes(using second).isEmpty) assert(parameter.currentShapes(using source).isEmpty) assert(source.allocatedTypeInstanceCount == 0) + + test("activation views compose flatly and share their consumer's hosts"): + given owner: Elaborator.State = new Elaborator.State + val source = owner.newResolverState + val scheme = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val parameter = new VarSymbol(new syntax.Tree.Ident("A")) + val local = new VarSymbol(new syntax.Tree.Ident("value")) + val first = source.instantiateTypeParameters(scheme, FlowSymbol.app(), List(parameter)) + val second = source.instantiateTypeParameters(scheme, FlowSymbol.app(), List(parameter)) + val left = source.withInstances(first) + val right = source.withInstances(second) + (1 to 1000).foreach: _ => + assert(left.withInstances(second) eq right) + assert(right.withInstances(first) eq left) + assert(left.withInstances(Map.empty) eq source) + assert(left.inGraph(right) eq left) + assert(local.inferenceHost(using left) eq local.inferenceHost(using right)) + assert(source.allocatedTypeInstanceCount == 2) + + test("imported activation views retain consumer substitutions without mutating exporter hosts"): + given owner: Elaborator.State = new Elaborator.State + val source = owner.newResolverState + val consumer = new Elaborator.State().newResolverState + val other = new Elaborator.State().newResolverState + val scheme = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val parameter = new VarSymbol(new syntax.Tree.Ident("A")) + val local = new VarSymbol(new syntax.Tree.Ident("value")) + val sourceSubstitution = source.instantiateTypeParameters(scheme, FlowSymbol.app(), List(parameter)) + val substitution = consumer.instantiateTypeParameters(scheme, FlowSymbol.app(), List(parameter)) + local.inferenceHost(using source).publish(UnknownValueShape.at(Term.UnitVal()))(using source) + val contextual = consumer.withInstances(substitution) + val imported = contextual.inGraph(source.withInstances(sourceSubstitution)) + assert(imported.instances == substitution) + assert(imported.withInstances(Map.empty) eq consumer.inGraph(source)) + (1 to 1000).foreach: _ => + assert(contextual.inGraph(source) eq imported) + assert(imported.inGraph(source) eq imported) + local.inferenceHost(using imported).publish(DynShape())(using imported) + assert(local.currentShapes(using imported).size == 2) + assert(local.currentShapes(using source).size == 1) + assert(local.currentShapes(using other.inGraph(source)).size == 1) + assert(consumer.allocatedTypeInstanceCount == 1) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index c5d8157d1f..d54a599da1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -101,15 +101,28 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) // Check returned implementations against the declared calling interface. // Its parameter types constrain returned closures instead of unrestricted // unknowns, and consumers see only the annotated result. - val declared = resolver.declaredType(resolver.typeResolution(sign), Map.empty) + val declared = resolver.declaredType(resolver.typeResolution(sign), Map.empty).instantiate(rstate.instances) watch((new Identity(body), declared, marks))(resolver.listenTerm(body)): shape => resolver.constrainFunction(declared, shape, marks) watch((declared, marks))(resolver.listenTypeInstances(declared)): shape => emit(shape.exit(marks), path) - private def value(shape: TermShape, path: Path)(using NewResolverState): Unit = + private def value(shape: TermShape, path: Path)(using NewResolverState): Unit = valueIn(shape, path, rstate.instances) + + private def valueIn(shape: TermShape, path: Path, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = val (head, marks) = shape.applicationHead + def contextualTerm(value: Term, context: Ls[Marks], path: Path, + substitution: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + watch((new Identity(value), context, substitution))( + listener => resolver.listenTerm(value)(listener)(using rstate.withInstances(substitution))): shape => + emit(resolver.instantiateShape(shape, substitution).exit(context), path) head match + case _: ActivatedShape => lastWords("Exposure must observe values after activation dispatch") + case contextual: ContextualShape => contextual.source.exit(marks) match + case value: TermShape => + val substitution = instances ++ contextual.instances + valueIn(value, path, substitution)(using rstate.withInstances(substitution)) + case NoShape => () case _: InstanceShape => watch((shape, "instance view"))(resolver.listenInstanceViews(shape))(emit(_, path)) case ds: DefnShape if ds.defn.sym.getState is rstate.owner => @@ -129,7 +142,7 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) case intro: IntroShape => intro.trm match case Lam(_, body) => parameters(shape.unappliedParams, path) - term(body, marks, path.via(msg"This value is returned here." -> body.toLoc)) + contextualTerm(body, marks, path.via(msg"This value is returned here." -> body.toLoc), instances) case _ => () case tuple: TupleShape => tuple.segments.foreach: case field: TupleShape.Fixed => @@ -137,9 +150,10 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) emit(shape.exit(marks), path.via(msg"This value is stored in this tuple." -> tuple.source.toLoc)) case _ => () case record: RecordShape => record.elements.foreach: - case RecordShape.Field(field) => term(field.rhs, marks, - path.via(msg"This value is stored in this record field." -> field.toLoc)) - case RecordShape.Spread(inner, context) => emit(inner.exit(context).exit(marks), path) + case RecordShape.Field(field) => contextualTerm(field.rhs, marks, + path.via(msg"This value is stored in this record field." -> field.toLoc), instances ++ record.instances) + case RecordShape.Spread(inner, context) => + emit(resolver.instantiateShape(inner, instances ++ record.instances).exit(context).exit(marks), path) case _ => () case base: BaseShape => members(base.defn, marks, path) case callable: CallableTypeShape => @@ -154,7 +168,7 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) emit(element, path.via(msg"This value is stored in this array." -> binding.resolution.source.toLoc)) // Structural annotations hide initializer implementations. Unknown and // dynamic values have no static graph. - case _: (RecordTypeShape | OpaqueTypeShape | UnknownValueShape | DynShape | ErrShape) => () + case _: (RecordTypeShape | OpaqueTypeShape | UnknownValueShape | RigidTypeShape | DynShape | ErrShape) => () def check(exports: Ls[BlockMemberSymbol], values: Ls[Term]): Unit = try diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index b26313be1c..7453a6d476 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -219,7 +219,7 @@ class NewResolver: listenTypeInstances(declaredType(typeResolution(sign), Map.empty))(listener) private[semantics] def listenTypeInstances(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = - listener(InstanceShape(tpe)) + listener(InstanceShape(tpe.instantiate(rstate.instances))) /** Expanding a type is an observation, not a type-argument constraint. Cache * observations before following parameters so recursive interfaces reach the @@ -236,6 +236,46 @@ class NewResolver: private def listenTermViews(term: Term)(listener: Listener)(using NewResolverState): Unit = listenTerm(term)(shape => listenInstanceViews(shape)(listener)) + /** Substitution changes a deferred observation, not its source graph. Compound + * children inherit the same flat map when they are subsequently observed. + */ + private[semantics] def instantiateShape(value: TermShape, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): TermShape = + if instances.isEmpty then value else + val Marked(source, marks) = value + val instantiated: NonMarkedShape = source match + case instance: InstanceShape => InstanceShape(instance.tpe.instantiate(instances)) + case rigid: RigidTypeShape => instances.get(rigid.parameter).fold[NonMarkedShape](rigid)(p => InstanceShape(instanceType(p))) + case tuple: TupleShape => tuple.copy(instances = instances ++ tuple.instances)(this) + case record: RecordShape => record.copy(instances = instances ++ record.instances) + case nominal: NominalInstanceView => + val bindings = instances.map((parameter, instance) => parameter -> instanceType(instance)) ++ + nominal.bindings.map((parameter, bound) => parameter -> bound.instantiate(instances)) + nominal.copy(bindings = bindings, parent = nominal.parent.map(instantiateShape(_, instances)))(nominal.annotation)(this) + case record: RecordTypeShape => + val bindings = instances.map((parameter, instance) => parameter -> instanceType(instance)) ++ + record.bindings.map((parameter, bound) => parameter -> bound.instantiate(instances)) + record.copy(bindings = bindings) + case contextual: ContextualShape => contextual.copy(instances = instances ++ contextual.instances) + case callable: CallableTypeShape => + val captured = instances -- callable.tparams.map(_.parameter.symbol) + callable.copy(paramLists = callable.paramLists.map: params => + DeclaredParams(params.params.map(_.map(_.instantiate(captured))), params.hasRest, params.rest.map(_.instantiate(captured))) + , result = callable.result.map(_.instantiate(captured))) + case literal: IntroShape if !literal.trm.isInstanceOf[Lam] => literal + case _: (UnknownValueShape | DynShape | OpaqueTypeShape | ErrShape) => source + case _ => ContextualShape(source, instances) + marks match + case NoMarks => instantiated + case marks: SomeMarks => MarkedShape(instantiated, marks) + + private def contextualParts(value: TermShape): (TermShape, Map[VarSymbol, TypeParameterInstance]) = value match + case Marked(ContextualShape(source, instances), marks) => + (marks match + case NoMarks => source + case marks: SomeMarks => MarkedShape(source, marks) + , instances) + case _ => (value, Map.empty) + private def listenTypeViews(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = typeViews.get(tpe) match case S(host) => host.listen(listener) @@ -251,7 +291,7 @@ class NewResolver: def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), Nil, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) - case TypeShape.Inferred(value) => listenInstanceViews(value)(publish) + case TypeShape.Inferred(value) => listenInstanceViews(instantiateShape(value, current.instances))(publish) case TypeShape.Nominal(defn) => val bindings = bind(defn.tparams) defn.ext match @@ -282,12 +322,15 @@ class NewResolver: // just its declaration. This survives substitution through // generic members and nested tuple or function annotations. val annotated = value match + case Marked(rigid: RigidTypeShape, marks) => + UnknownValueShape(rigid.source)(rigid.provenance.via( + msg"This type annotation supplies the value's shape." -> tpe.resolution.source.toLoc)).exit(marks) case Marked(unknown: UnknownValueShape, marks) => UnknownValueShape(unknown.source)(unknown.provenance.via( msg"This type annotation supplies the value's shape." -> tpe.resolution.source.toLoc)).exit(marks) case _ => value annotated match - case value: TermShape => listenInstanceViews(value)(publish) + case value: TermShape => listenInstanceViews(instantiateShape(value, current.instances))(publish) case NoShape => () case TypeShape.Captured(base, thru) => follow(declaredType(base, current), args, aliases, @@ -433,8 +476,7 @@ class NewResolver: val site = FlowSymbol("generic interface") params.foreach: param => val ref = SimpleRef(param)(param.id) - val unknown = UnknownValueShape(ref)(ShapeProvenance( - msg"Type parameter '${param.nme}' does not specify a member interface." -> param.toLoc :: Nil)) + val unknown = RigidTypeShape(param, ref) publishParameter(param, MarkedShape.enter(unknown, ResolutionBoundary(owner), S(site))) /** Explicit and inferred type arguments use the same entry/exit paths as term @@ -450,7 +492,9 @@ class NewResolver: case _ => softAssert(false, "A type parameter received a symbolic overload set") private[semantics] def publishParameter(symbol: VarSymbol, shape: TermShape | NoShape)(using NewResolverState): Unit = - publishParameter(symbol.inferenceHost, shape) + shape match + case value: TermShape if !symbol.decl.exists(_.isInstanceOf[TyParam]) => publishActivated(symbol, value) + case _ => publishParameter(symbol.inferenceHost, shape) private[semantics] def publishParameter(host: Publisher.Data[Shape], shape: TermShape | NoShape)(using NewResolverState): Unit = shape match case value: TermShape => host.publish(value) @@ -524,14 +568,14 @@ class NewResolver: private def typeConstraints(using rs: NewResolverState) = rs.typeConstraints private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = if !typeConstraints.add((tpe, value, marks)) then return - value match - case Marked(_: InstanceShape, _) => - listenInstanceViews(value)(inferTypeArguments(tpe, _, marks)) - return - case _ => () def follow(tpe: DeclaredType, args: Ls[DeclaredType], captures: Ls[Marks], seen: Set[TypeResolution])(using NewResolverState): Unit = if !seen(tpe.resolution) then val next = seen + tpe.resolution + def observe(listener: Listener)(using NewResolverState): Unit = + listenInstanceViews(value): actual => + actual.enter(captures) match + case actual: TermShape => listener(actual) + case NoShape => () tpe.resolution.listen: case TypeShape.Parameter(symbol, host) => tpe.bindings.get(symbol) match case S(bound) => follow(bound.instantiate(tpe.instances), Nil, captures, next) @@ -547,7 +591,7 @@ class NewResolver: case TypeShape.Alias(symbol, S(rhs)) => val bindings = effectiveBindings(tpe) ++ symbol.defn.get.tparams.map(_.sym).zip(args) follow(declaredType(rhs, bindings), Nil, captures, next) - case TypeShape.Tuple(fields) => value.enter(captures) match + case TypeShape.Tuple(fields) => observe: case Marked(tuple: TupleShape, context) => fields.zipWithIndex.foreach: (field, index) => tuple.getMember(index.toString) match @@ -558,26 +602,22 @@ class NewResolver: case NoShape => () case _ => () case _ => () - case TypeShape.Record(_, fields) => value.enter(captures) match - case actual: TermShape => constrainRecord(fields, effectiveBindings(tpe), actual, marks) - case NoShape => () - case TypeShape.Function(_, _) => value.enter(captures) match - case actual: TermShape => - constrainFunction(tpe, actual, marks) - case NoShape => () + case TypeShape.Record(_, fields) => observe(constrainRecord(fields, effectiveBindings(tpe), _, marks)) + case TypeShape.Function(_, _) => observe(constrainFunction(tpe, _, marks)) case TypeShape.Polymorphic(_, _, body) => follow(declaredType(body, tpe), args, captures, next) - case TypeShape.Nominal(cls) if args.nonEmpty => - val (head, context) = value.applicationHead + case TypeShape.Nominal(cls) if args.nonEmpty => listenInstanceViews(value): value => + val (actual, instances) = contextualParts(value) + val (head, context) = actual.applicationHead def constrain(param: TyParam, pattern: DeclaredType)(using NewResolverState): Unit = listenTypeArgument(param.sym): shape => - shape.exit(context) match + instantiateShape(shape, instances).exit(context) match case actual: TermShape => inferTypeArguments(pattern, actual, marks) case NoShape => () def constrainNominal(nominal: NominalInstanceView)(using NewResolverState): Unit = if nominal.defn is cls then cls.tparams.zip(args).foreach: (param, pattern) => nominal.bindings.get(param.sym).foreach: bound => - listenTypeInstances(bound): shape => + listenTypeInstances(bound.instantiate(instances)): shape => shape.exit(context) match case actual: TermShape => inferTypeArguments(pattern, actual, marks) case NoShape => () @@ -601,25 +641,28 @@ class NewResolver: val expected = declaredType(sign, bindings) val flow = FlowSymbol.memSym(field.sym)(using rstate.owner) def receive(value: TermShape)(using NewResolverState): Unit = inferTypeArguments(expected, value, marks) - actual.getMember(field.sym.nme) match + def member(info: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match + case MemberLookup.Contextual(source, substitution) => member(source, instances ++ substitution) case MemberLookup.Found(RecordMember(member, true), _) => receive(UnknownValueShape.at(member.rhs)) case MemberLookup.Found(member, inner) => - fromBMS(member.memberSymbol, flow, inner, receive, field.rhs, _ => (), false) + fromBMS(member.memberSymbol, flow, inner, value => receive(instantiateShape(value, instances)), field.rhs, _ => (), false)(using rstate.withInstances(instances)) case MemberLookup.Declared(member, bound, inner, annotation) => listenDeclaredMember(member, bound, flow, field.rhs, annotation, _ => (), false): value => - value.exit(inner) match + instantiateShape(value, instances).exit(inner) match case value: TermShape => receive(value) case NoShape => () case MemberLookup.Dynamic(inner) => DynShape().exit(inner) match case value: TermShape => receive(value) case NoShape => () case _ => () + member(actual.getMember(field.sym.nme), Map.empty) /** Function constraints reverse the parameter flow and preserve result flow. * Read an implementation only while checking it against its declared interface; * calls through that interface continue to expose only the declared result. */ - private[semantics] def constrainFunction(expected: DeclaredType, actual: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = + private[semantics] def constrainFunction(expected: DeclaredType, source: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = + val (actual, instances) = contextualParts(source) actual match case Marked(_: InstanceShape, _) => listenInstanceViews(actual)(constrainFunction(expected, _, marks)) @@ -628,13 +671,13 @@ class NewResolver: val context = actual.applicationHead._2 def results(listener: Listener)(using NewResolverState): Unit = actual.applicationHead._1 match case intro: IntroShape => intro.trm match - case Lam(_, body) => listenTerm(body)(listener) + case Lam(_, body) => listenTerm(body)(shape => listener(instantiateShape(shape, instances)))(using rstate.withInstances(instances)) case _ => () case ds: DefnShape => ds.defn match case td: TermDefinition => td.sign match - case S(sign) => listenSignatureResult(declaredType(typeResolution(sign), Map.empty), + case S(sign) => listenSignatureResult(declaredType(typeResolution(sign), Map.empty).instantiate(instances), if td.flags.hasResultAnnotation then 0 else td.params.length)(listener) - case N => td.body.foreach(listenTerm(_)(listener)) + case N => td.body.foreach(body => listenTerm(body)(shape => listener(instantiateShape(shape, instances)))(using rstate.withInstances(instances))) case _ => () case _ => () def matchLists(expected: DeclaredType, remaining: Ls[(ParamList, Ls[Marks])])(using NewResolverState): Unit = @@ -650,7 +693,7 @@ class NewResolver: params.params.zip(declared.params).foreach: (param, sign) => sign.foreach: tpe => listenTypeInstances(tpe): shape => - constrainParameter(param, shape.exit(expectedContext).enter(context), context) + constrainParameterIn(param, shape.exit(expectedContext).enter(context), context, instances) params.restParam.foreach: rest => val fields = declared.params.drop(params.params.length).map: case S(tpe) => TupleShape.TypedField(tpe, Nil) @@ -659,7 +702,7 @@ class NewResolver: then TupleShape.Unknown(callable.source, Nil, UnknownValueShape.at(callable.source)) :: Nil else Nil val tuple = TupleShape(callable.source, fields ::: suffix)(this) - constrainParameter(rest, tuple.exit(expectedContext).enter(context), context) + constrainParameterIn(rest, tuple.exit(expectedContext).enter(context), context, instances) callable.result.foreach: ret => tail match case Nil => results: shape => @@ -782,6 +825,8 @@ class NewResolver: case TupleShape.TypedField(tpe, _) => listenTypeInstances(tpe)(receive) case TupleShape.UnknownField(source, _) => receive(UnknownValueShape.at(source)) case TupleShape.ValueField(value, _) => receive(value) + case TupleShape.ViewedField(source, instances, _) => + listenTupleField(source)(value => receive(instantiateShape(value, instances)))(using rstate.withInstances(instances)) /** Array spreads have unknown length, but their declared element type still * constrains every element. Never recover elements from constructor values: @@ -821,17 +866,25 @@ class NewResolver: * an argument, nor evidence of an arity mismatch; each resolved combination is. */ def zipArgs(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Term, src: Term, funSh: TermShape)(using NewResolverState): Unit = + zipArgsIn(mss, ps, r, args, src, funSh, rstate.instances) + + private def zipArgsIn(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Term, src: Term, funSh: TermShape, + instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = val params = ps ::: r.toList zipArgumentShapes(mss, ps.length, r.nonEmpty, args, src, funSh): (index, shape) => - constrainParameter(params(index), shape, mss) + constrainParameterIn(params(index), shape, mss, instances) private[semantics] def constrainParameter(p: Param, shape: TermShape | NoShape, marks: Ls[Marks])(using NewResolverState): Unit = + constrainParameterIn(p, shape, marks, rstate.instances) + + private def constrainParameterIn(p: Param, shape: TermShape | NoShape, marks: Ls[Marks], + instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = shape match case NoShape => () case sh: TermShape => parameterSignature(p) match - case S(tpe) => inferTypeArguments(tpe, sh, marks) - case N => publishParameter(p.sym, sh) + case S(tpe) => inferTypeArguments(tpe.instantiate(instances), sh, marks) + case N => publishParameter(p.sym, sh)(using rstate.withInstances(instances)) private[semantics] def parameterSignature(p: Param)(using NewResolverState): Opt[DeclaredType] = p.sign.map(sign => declaredType(typeResolution(sign), Map.empty)).orElse(signatureParameters.get(p.sym)) @@ -1006,7 +1059,7 @@ class NewResolver: // Rejected duplicate/negated bindings have no allocated symbol. Pattern // validation already reports them; only valid bindings receive flow. al.symbolOption.foreach: symbol => - if symbol.currentShapes.add(sh) then symbol.notifyShapeListeners(sh) + publishActivated(symbol, sh) matched(sh) case Pattern.Wildcard() | Pattern.Literal(_) => matched(shape) case Pattern.Chain(left, right) => @@ -1025,13 +1078,14 @@ class NewResolver: def check(sh: TermShape, unknown: Opt[TermShape])(using NewResolverState): Unit = if sh.isInstanceOfClass(cls) then fs.foreach: (bms, pat) => - sh.getMember(bms.nme) match + def member(info: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match + case MemberLookup.Contextual(source, substitution) => member(source, instances ++ substitution) case MemberLookup.Found(sym: BlockMemberSymbol, marks) => - val field = symShapes.getOrElseUpdate((sym, resSym, marks), SymShape(sym, resSym, marks)) - matchShapePat(field, pat)(_ => ()) + fromBMS(sym, resSym, marks, + field => matchShapePat(instantiateShape(field, instances), pat)(_ => ()), ctor.target, _ => (), false) case MemberLookup.Declared(member, bindings, marks, annotation) => listenDeclaredMember(member, bindings, resSym, ctor.target, annotation, _ => (), false): field => - listenInstanceViews(field): field => + listenInstanceViews(instantiateShape(field, instances)): field => field.exit(marks) match case value: TermShape => val field = (value.applicationHead._1, unknown) match @@ -1053,6 +1107,7 @@ class NewResolver: // between nominal matching and member lookup, not recovery // from a malformed class (filtered by classPattern above). softAssert(false, "Matched constructor is missing its field") + member(sh.getMember(bms.nme), Map.empty) matched(sh) def narrow(sh: TermShape)(using NewResolverState): Unit = sh match case Marked(unknown: UnknownValueShape, _) => @@ -1162,10 +1217,32 @@ class NewResolver: def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern)(using NewResolverState): Unit = if newResolution then listenTerm(scrutinee)(sh => matchShapePat(sh, pattern)(_ => ())) - def appShape(lhs: TermShape, args: Term, res: App)(using NewResolverState): Unit = + def appShape(source: TermShape, args: Term, res: App)(using NewResolverState): Unit = + val callerInstances = rstate.instances + val (lhs, lexical) = contextualParts(source) + val captured = callerInstances ++ lexical + val instances = lhs match + case Marked(definition: DefnShape, _) => definition.defn match + case td: TermDefinition if !td.tsym.isInstanceOf[ClassCtorSymbol] && td.tparams.exists(_.nonEmpty) => + val marks = lhs.applicationHead._2 + val parameters = td.tparams.get.map(_.sym) + val substitution = captured ++ rstate.instantiateTypeParameters(td.tsym, res.resSym, parameters) + if rstate.inferredInstantiations.add((td.tsym, res.resSym, substitution, marks)) then + parameters.foreach: parameter => + val instance = substitution(parameter) + if rstate.hasExplicitTypeArgument(parameter, marks) then rstate.markExplicitTypeArgument(instance, marks) + listenTypeArgument(parameter): bound => + bound.applicationHead._1 match + case _: RigidTypeShape => () + case _ => publishParameter(instance, instantiateShape(bound, substitution)) + substitution + case _ => captured + case _ => captured + def publish(value: TermShape)(using NewResolverState): Unit = + publishViewed(res, value, instances)(using rstate.withInstances(callerInstances)) lhs match case Marked(_: InstanceShape, _) => - listenInstanceViews(lhs)(appShape(_, args, res)) + listenInstanceViews(instantiateShape(lhs, instances))(appShape(_, args, res)) return case _ => () // Only explicit dynamic values authorize calls without a known interface. @@ -1173,12 +1250,15 @@ class NewResolver: // happen to be callable in this compilation unit. lhs match case Marked(_: DynShape, _) => - if res.currentShapes.add(lhs) then res.notifyShapeListeners(lhs) + publish(lhs) return case _ => () lhs match case Marked(original: CallableTypeShape, context) => - val callable = instantiateCallable(original, res.resSym, context :: Nil) + val viewed = instantiateShape(original, instances) match + case callable: CallableTypeShape => callable + case _ => lastWords("A callable view must remain callable") + val callable = instantiateCallable(viewed, res.resSym, context :: Nil) val ps = callable.paramLists.head zipArgumentShapes(context :: Nil, ps.params.length, ps.hasRest, args, res, lhs): (index, value) => (if index < ps.params.length then ps.params(index) else ps.rest).foreach: tpe => @@ -1187,7 +1267,7 @@ class NewResolver: case NoShape => () def publish(shape: TermShape)(using NewResolverState): Unit = shape.exit(context) match case value: TermShape => - if res.currentShapes.add(value) then res.notifyShapeListeners(value) + publishViewed(res, value, Map.empty)(using rstate.withInstances(callerInstances)) case NoShape => () callable.paramLists.tail match case Nil => callable.result match @@ -1204,10 +1284,9 @@ class NewResolver: lhs.unappliedParams match case Nil => () case (ps, mss) :: pss => - zipArgs(mss, ps.params, ps.restParam, args, res, lhs) + zipArgsIn(mss, ps.params, ps.restParam, args, res, lhs, instances) log(s"appShape isSaturated? ${sh.isSaturated}; head? ${sh.applicationHead}") - def register(using NewResolverState) = if res.currentShapes.add(sh) then - res.notifyShapeListeners(sh) + def register(using NewResolverState) = publish(sh) log(s"lhs ${lhs.isSaturated} ${lhs.unappliedParams.map(_.mapFirst(_.showDbg).mapSecond(_.map(_.showDbg)))}") if lhs.isSaturated && !rstate.hasError(res) then rstate.markError(res) @@ -1231,8 +1310,7 @@ class NewResolver: sh.exit(mss) match case NoShape => case sh: TermShape => - if res.currentShapes.add(sh) then - res.notifyShapeListeners(sh) + publish(sh) sh.applicationHead match case (ds: DefnShape, mss) => ds.defn match @@ -1253,10 +1331,10 @@ class NewResolver: td.sign match case S(sign) => val count = if td.flags.hasResultAnnotation then 0 else td.params.length - listenSignatureResult(declaredType(typeResolution(sign), Map.empty), count): result => + listenSignatureResult(declaredType(typeResolution(sign), Map.empty).instantiate(instances), count): result => result.exit(mss) match case value: TermShape => - if res.currentShapes.add(value) then res.notifyShapeListeners(value) + publish(value) case NoShape => () case N => td.body.foreach(go(_, mss)) case _ => @@ -1272,10 +1350,28 @@ class NewResolver: softAssert(rstate.hasError(res)) else register + private def publishActivated(host: ShapeHost, source: Shape)(using NewResolverState): Unit = + val shape = if rstate.instances.isEmpty then source else source match + case value: TermShape => ActivatedShape(value, rstate.instances) + case symbol: SymShape => + rstate.activatedSymbols.getOrElseUpdate((symbol, rstate.instances), ActivatedSymShape(symbol, rstate.instances)) + if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) + + private def publishViewed(host: ShapeHost, source: Shape, + instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + val shape = source match + case value: TermShape => instantiateShape(value, instances) + case symbol: SymShape if instances.nonEmpty => + val (original, substitution) = symbol match + case contextual: ContextualSymShape => (contextual.source, instances ++ contextual.instances) + case _ => (symbol, instances) + rstate.contextualSymbols.getOrElseUpdate((original, substitution), ContextualSymShape(original, substitution)) + case _ => source + publishActivated(host, shape) + private def publishMember(host: NewResolvable & ShapeHost, member: BlockMemberSymbol | RecordMember, - flow: FlowSymbol, marks: Ls[Marks])(using NewResolverState): Unit = - def publish(shape: Shape)(using NewResolverState): Unit = - if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) + flow: FlowSymbol, marks: Ls[Marks], instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + def publish(shape: Shape)(using NewResolverState): Unit = publishViewed(host, shape, instances) def definition(sym: BlockMemberSymbol)(using NewResolverState): Unit = publish(symShapes.getOrElseUpdate((sym, flow, marks), SymShape(sym, flow, marks))) member match @@ -1289,17 +1385,18 @@ class NewResolver: publish(UnknownValueShape.at(field.rhs)) private def publishDeclared(host: NewResolvable & ShapeHost, member: BlockMemberSymbol, flow: FlowSymbol, - bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term])(using NewResolverState): Unit = + bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term], + instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = // Keep one diagnostic witness per semantic candidate, as for unknown values. // A different annotation location must not create additional inference flow. val shape = declaredSymShapes.getOrElseUpdate((member, flow, marks, bindings), DeclaredSymShape(member, flow, marks, bindings, annotation)) - if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) + publishViewed(host, shape, instances) private def publishDynamic(host: NewResolvable & ShapeHost, marks: Ls[Marks])(using NewResolverState): Unit = DynShape().exit(marks) match case shape: TermShape => - if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) + publishActivated(host, shape) case NoShape => () private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, provenance: ShapeProvenance)(using NewResolverState): Unit = if !rstate.hasError(host) then @@ -1314,15 +1411,16 @@ class NewResolver: def unresolvedRef(ref: UnresolvedRef)(using NewResolverState): Unit = ref.prefixes.foreach: prefix => listenReceiver(prefix): shape => - shape.getMember(ref.id.name) match + def member(info: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match + case MemberLookup.Contextual(source, substitution) => member(source, instances ++ substitution) case MemberLookup.Found(member, marks) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member.memberSymbol) => val candidate = prefix -> member.memberSymbol rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) - publishMember(ref, member, ref.resSym, marks) + publishMember(ref, member, ref.resSym, marks, instances) case MemberLookup.Declared(member, bindings, marks, annotation) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member) => val candidate = prefix -> member rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) - publishDeclared(ref, member, ref.resSym, bindings, marks, annotation) + publishDeclared(ref, member, ref.resSym, bindings, marks, annotation, instances) case MemberLookup.Indexed(_, _) if rstate.canResolve(ref) => resolError(ref, msg"Tuple elements must be selected by index." -> ref.toLoc :: Nil) case MemberLookup.Dynamic(marks) if rstate.canResolve(ref) || ref.dynamicPrefixes.contains(prefix) => @@ -1335,6 +1433,7 @@ class NewResolver: case MemberLookup.Unknown(reason, provenance) => unknownMember(ref, ref.id.name, reason, provenance) case _ => () // Completed references only route their selected members. + member(shape.getMember(ref.id.name), Map.empty) /** Inspect an overload set only once its definitions have all been published. */ private def completedClass(shape: SymShape)(selected: ShapeListener[ClassDef], absent: () => NewResolverState ?=> Unit)(using NewResolverState): Unit = @@ -1374,14 +1473,16 @@ class NewResolver: def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") - def member(info: MemberLookup, description: Message, loc: Opt[Loc])(using NewResolverState): Unit = info match + def member(info: MemberLookup, description: Message, loc: Opt[Loc], + instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match + case MemberLookup.Contextual(source, substitution) => member(source, description, loc, instances ++ substitution) case MemberLookup.Found(bms, marks) if rstate.canResolve(sel) || sel.resolvedMembers.contains(bms.memberSymbol) => log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") rstate.recordResolution(sel, sel.resolvedMembers.contains(bms.memberSymbol))(sel.resolvedMembers ::= bms.memberSymbol) - publishMember(sel, bms, sel.resSym, marks) + publishMember(sel, bms, sel.resSym, marks, instances) case MemberLookup.Declared(member, bindings, marks, annotation) if rstate.canResolve(sel) || sel.resolvedMembers.contains(member) => rstate.recordResolution(sel, sel.resolvedMembers.contains(member))(sel.resolvedMembers ::= member) - publishDeclared(sel, member, sel.resSym, bindings, marks, annotation) + publishDeclared(sel, member, sel.resSym, bindings, marks, annotation, instances) case MemberLookup.Indexed(field, marks) if rstate.canResolve(sel) || sel.tupleIndex == sel.id.name.toIntOption => val index = sel.id.name.toIntOption softAssert(index.exists(_ >= 0), "Tuple lookup must identify a nonnegative index") @@ -1389,7 +1490,7 @@ class NewResolver: listenTupleField(field): shape => shape.exit(marks) match case value: TermShape => - if sel.currentShapes.add(value) then sel.notifyShapeListeners(value) + publishViewed(sel, value, instances) case NoShape => () case MemberLookup.Dynamic(marks) if rstate.canResolve(sel) || sel.hasDynamicTarget => rstate.recordResolution(sel, sel.hasDynamicTarget)(sel.hasDynamicTarget = true) @@ -1404,7 +1505,7 @@ class NewResolver: sel.cls match case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") - member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc) + member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc, Map.empty) case S(cls) => listenClass(cls)((cd, marks) => val candidate = cd.sym -> marks @@ -1431,8 +1532,8 @@ class NewResolver: val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs)), N) MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation) case _ => info - member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc) - case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc)) + member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, Map.empty) + case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc, Map.empty)) , sh => rstate.markError(sel) resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc :: Nil) @@ -1485,7 +1586,7 @@ class NewResolver: case ((ps, _), args) => zipArgs(marks, ps.params, ps.restParam, args, nw, dsh) NewShape(dsh, cd.sym, marks, nw.args, nw) }) - if nw.currentShapes.add(sh) then nw.notifyShapeListeners(sh) + publishViewed(nw, sh, rstate.instances) ) , shape => if !rstate.hasError(nw) then @@ -1502,8 +1603,7 @@ class NewResolver: println(s"TODO: defineVar for TermSymbol ${sym.showDbg}") case sym: LocalSymbol => listen(rhs): sh => - if sym.currentShapes.add(sh) then - sym.notifyShapeListeners(sh) + publishActivated(sym, sh) DefineVar(sym, rhs) def listenDefn(sym: TermSymbol, listener: Listener)(using NewResolverState): Unit = @@ -1522,8 +1622,7 @@ class NewResolver: def pipeTerm(from: Term, to: ShapeHost)(using NewResolverState): Unit = log(s"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.currentShapes}") listenTerm(from): sh => - if to.currentShapes.add(sh) then - to.notifyShapeListeners(sh) + publishActivated(to, sh) // Classes without an explicit parent expose Object's declarations. Share this // root interface between inferred instances, nominal annotations, and this-values; @@ -1561,6 +1660,8 @@ class NewResolver: private def fromSymbol(shape: SymShape, listener: Listener, source: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(using NewResolverState): Unit = shape match + case contextual: ContextualSymShape => + fromSymbol(contextual.source, value => listener(instantiateShape(value, contextual.instances)), source, selected, receiver)(using rstate.withInstances(contextual.instances)) case declared: DeclaredSymShape => listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, declared.annotation, selected, receiver): value => value.exit(shape.markss) match @@ -1743,7 +1844,21 @@ class NewResolver: start: shape => if aggregate.currentShapes.add(shape) then aggregate.notifyShapeListeners(shape) - def listen(trm: Term, discardMarks: Bool = false)(listener: ShapeListener[Shape])(using NewResolverState): Unit = + def listen(trm: Term, discardMarks: Bool = false)(receive: ShapeListener[Shape])(using NewResolverState): Unit = + // Source listeners process all activations. A deferred observation with a + // chosen substitution accepts only compatible activations of that source; + // the delivered value still keeps its independent caller-side references. + val requestedInstances = rstate.instances + def compatible(instances: Map[VarSymbol, TypeParameterInstance]): Bool = + requestedInstances.forall((parameter, instance) => instances.get(parameter).forall(_ eq instance)) + val listener: ShapeListener[Shape] = shape => shape match + case ActivatedShape(value, instances) => + if compatible(instances) then receive(value)(using rstate.withInstances(requestedInstances ++ instances)) + case activated: ActivatedSymShape => + if compatible(activated.instances) then + receive(activated.source)(using rstate.withInstances(requestedInstances ++ activated.instances)) + case value: TermShape => receive(instantiateShape(value, rstate.instances)) + case _ => receive(shape) log(s"listen: trm = ${trm.showDbg}") trm.addShapeListener(listener) trm match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index a5fd8f9089..7b09727ffc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -37,11 +37,23 @@ object NewResolverState: * before a consumer can extend either collection. */ final class NewResolverState private ( - val owner: Elaborator.State, private val consumer: Opt[NewResolverState], private val source: Opt[NewResolverState]): + val owner: Elaborator.State, private val consumer: Opt[NewResolverState], private val source: Opt[NewResolverState], + private val contextBase: Opt[NewResolverState], val instances: Map[VarSymbol, TypeParameterInstance]): import NewResolverState.{Cache, Seen} - def this(owner: Elaborator.State) = this(owner, N, N) + def this(owner: Elaborator.State) = this(owner, N, N, N, Map.empty) private def root: NewResolverState = consumer.getOrElse(this) + private def inherited: Opt[NewResolverState] = contextBase.orElse(source) + private val contexts = mutable.Map.empty[(NewResolverState, Map[VarSymbol, TypeParameterInstance]), NewResolverState] + /** Activations share the consuming unit's hosts and memoized source graph. + * Only the finite substitution varies; composing views never adds a parent + * context or changes either endpoint's originating graph. + */ + def withInstances(substitution: Map[VarSymbol, TypeParameterInstance]): NewResolverState = + val base = contextBase.getOrElse(this) + if substitution.isEmpty then base + else root.contexts.getOrElseUpdate((base, substitution), + new NewResolverState(owner, S(root), source, S(base), substitution)) private var reporter: Raise | Null = null def withReporter(raise: Raise): this.type = root.reporter = raise @@ -57,17 +69,18 @@ final class NewResolverState private ( // memoized nodes individually. Creating a view never traverses a source map. private val views = mutable.Map.empty[NewResolverState, NewResolverState] private val inheritedViews = mutable.Map.empty[(NewResolverState, NewResolverState), NewResolverState] - private[hkmc2] def inGraph(graph: NewResolverState): NewResolverState = rebase(graph, root) + private[hkmc2] def inGraph(graph: NewResolverState): NewResolverState = + rebase(graph.contextBase.getOrElse(graph), root).withInstances(instances) private def rebase(graph: NewResolverState, destination: NewResolverState): NewResolverState = val origin = source.fold(graph)(_.rebase(graph, destination)) if origin.root eq root then origin else if root eq destination then - root.views.getOrElseUpdate(origin, new NewResolverState(owner, S(root), S(origin))) + root.views.getOrElseUpdate(origin, new NewResolverState(owner, S(root), S(origin), N, Map.empty)) else root.views.get(origin).getOrElse: // A previously unused path can require a view of an intermediate exporter. // Memoize that read-only view in the consumer, never in the exporter. destination.inheritedViews.getOrElseUpdate((root, origin), - new NewResolverState(owner, S(root), S(origin))) + new NewResolverState(owner, S(root), S(origin), N, Map.empty)) private val copies = mutable.Map.empty[Publisher.Data[?], Publisher.Data[?]] private val pending = mutable.Map.empty[Identity[Publisher[?]], Publisher.Data[?]] @@ -170,35 +183,41 @@ final class NewResolverState private ( update val membersCache: Cache[(Identity[TermShape], Str), MemberLookup] = - new Cache(source.map(_.membersCache), identity) + new Cache(inherited.map(_.membersCache), identity) val spreadInputs: Cache[Object, mutable.Set[TermShape]] = - new Cache(source.map(_.spreadInputs), _.clone()) + new Cache(inherited.map(_.spreadInputs), _.clone()) val reportedArities: Cache[Object, mutable.Set[Int]] = - new Cache(source.map(_.reportedArities), _.clone()) + new Cache(inherited.map(_.reportedArities), _.clone()) val appShapes: Cache[(TermShape, FlowSymbol), AppShape] = - new Cache(source.map(_.appShapes), identity) + new Cache(inherited.map(_.appShapes), identity) val newShapes: Cache[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = - new Cache(source.map(_.newShapes), identity) + new Cache(inherited.map(_.newShapes), identity) val introShapes: Cache[Identity[IntroTerm], IntroShape] = - new Cache(source.map(_.introShapes), identity) + new Cache(inherited.map(_.introShapes), identity) val symShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = - new Cache(source.map(_.symShapes), identity) + new Cache(inherited.map(_.symShapes), identity) val declaredSymShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks], Map[VarSymbol, DeclaredType]), DeclaredSymShape] = - new Cache(source.map(_.declaredSymShapes), identity) + new Cache(inherited.map(_.declaredSymShapes), identity) val selfShapes: Cache[InnerSymbol, BaseShape] = - new Cache(source.map(_.selfShapes), identity) + new Cache(inherited.map(_.selfShapes), identity) val defnShapes: Cache[DefinitionSymbol[?], DefnShape] = - new Cache(source.map(_.defnShapes), identity) + new Cache(inherited.map(_.defnShapes), identity) val typeInterpretations: Cache[Identity[Term], TypeResolution] = - new Cache(source.map(_.typeInterpretations), identity) + new Cache(inherited.map(_.typeInterpretations), identity) val quantifiedTypes: Cache[(TypeResolution, Ls[VarSymbol]), TypeResolution] = - new Cache(source.map(_.quantifiedTypes), identity) + new Cache(inherited.map(_.quantifiedTypes), identity) val instantiatedCallables: Cache[(CallableTypeShape, FlowSymbol, Ls[Marks]), CallableTypeShape] = - new Cache(source.map(_.instantiatedCallables), identity) + new Cache(inherited.map(_.instantiatedCallables), identity) + val contextualSymbols: Cache[(SymShape, Map[VarSymbol, TypeParameterInstance]), ContextualSymShape] = + new Cache(inherited.map(_.contextualSymbols), identity) + val activatedSymbols: Cache[(SymShape, Map[VarSymbol, TypeParameterInstance]), ActivatedSymShape] = + new Cache(inherited.map(_.activatedSymbols), identity) + val inferredInstantiations: Seen[(TermSymbol, FlowSymbol, Map[VarSymbol, TypeParameterInstance], Ls[Marks])] = + new Seen(inherited.map(_.inferredInstantiations)) // A scheme is owned by its source definition, or by the original interpretation // of an anonymous quantified annotation. Neither a view nor an instance is an owner. private val typeInstances: Cache[(AnyDefinitionSymbol | TypeResolution, FlowSymbol), Map[VarSymbol, TypeParameterInstance]] = - new Cache(source.map(_.typeInstances), identity) + new Cache(inherited.map(_.typeInstances), identity) private var allocatedTypeInstances: Int = 0 private[hkmc2] def allocatedTypeInstanceCount: Int = root.allocatedTypeInstances private[hkmc2] def instantiateTypeParameters(scheme: AnyDefinitionSymbol | TypeResolution, @@ -222,29 +241,29 @@ final class NewResolverState private ( assert(instances.keySet == parameters.toSet, "A source scheme's binders must remain stable") instances val typeViews: Cache[DeclaredType, TermShapeHost] = - new Cache(source.map(_.typeViews), identity) + new Cache(inherited.map(_.typeViews), identity) val patternTypes: Cache[(Identity[Pattern.Constructor], InnerSymbol), DeclaredType] = - new Cache(source.map(_.patternTypes), identity) + new Cache(inherited.map(_.patternTypes), identity) val primitiveTypes: Cache[ClassSymbol, DeclaredType] = - new Cache(source.map(_.primitiveTypes), identity) + new Cache(inherited.map(_.primitiveTypes), identity) val abstractTypes: Cache[(TypeResolution, Opt[VarSymbol]), DeclaredType] = - new Cache(source.map(_.abstractTypes), identity) + new Cache(inherited.map(_.abstractTypes), identity) val signatureParameters: Cache[VarSymbol, DeclaredType] = - new Cache(source.map(_.signatureParameters), identity) + new Cache(inherited.map(_.signatureParameters), identity) val capturedTypes: Cache[(DeclaredType, TermSymbol), DeclaredType] = - new Cache(source.map(_.capturedTypes), identity) + new Cache(inherited.map(_.capturedTypes), identity) val tupleArrayParents: Cache[Identity[TupleShape], NominalInstanceView] = - new Cache(source.map(_.tupleArrayParents), identity) + new Cache(inherited.map(_.tupleArrayParents), identity) val instanceParameterTypes: Cache[(VarSymbol, Bool), DeclaredType] = - new Cache(source.map(_.instanceParameterTypes), identity) + new Cache(inherited.map(_.instanceParameterTypes), identity) val mutableArrays: Cache[Identity[Term.Mut], TermShape] = - new Cache(source.map(_.mutableArrays), identity) + new Cache(inherited.map(_.mutableArrays), identity) // Named tuple fields have stable property identities, shared with consumers // through the tuple's original graph rather than allocated per spread candidate. val namedTupleRecords: Cache[Identity[Tup], Rcd] = - new Cache(source.map(_.namedTupleRecords), identity) + new Cache(inherited.map(_.namedTupleRecords), identity) val aggregateProducers: Seen[Identity[Tup | Rcd]] = - new Seen(source.map(_.aggregateProducers)) + new Seen(inherited.map(_.aggregateProducers)) // Explicit instantiations are visible to every graph view in this consumer; // inherited flags are queried without enumerating an exporter's instantiations. private val explicitTypeArguments = mutable.Set.empty[(VarSymbol, Ls[Marks])] @@ -253,7 +272,7 @@ final class NewResolverState private ( def hasExplicitTypeArgument(symbol: VarSymbol, marks: Ls[Marks]): Bool = root.explicitTypeArguments((symbol, marks)) || source.exists(_.hasExplicitTypeArgument(symbol, marks)) val typeConstraints: Seen[(DeclaredType, TermShape, Ls[Marks])] = - new Seen(source.map(_.typeConstraints)) + new Seen(inherited.map(_.typeConstraints)) private[semantics] final class TermShapeHost extends Host[TermShape]: def showDbg(using DebugPrinter): Str = "instance views" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 735d73b986..55d4d8f67a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -30,6 +30,9 @@ sealed trait Shape extends ShapeLike: case ts: NominalInstanceView => s"NominalInstanceView(${ts.defn.sym.showDbg})" case ts: OpaqueTypeShape => s"OpaqueTypeShape(${ts.source.showDbg})" case ts: CallableTypeShape => s"CallableTypeShape(${ts.source.showDbg})" + case view: ContextualShape => s"ContextualShape(${view.source.shwDbg})" + case rigid: RigidTypeShape => s"RigidTypeShape(${rigid.parameter.showDbg})" + case flow: ActivatedShape => s"ActivatedShape(${flow.value.shwDbg})" case bs: BaseShape => s"BaseShape(${bs.defn.sym.showDbg})" case es: ErrShape => es.describe @@ -260,6 +263,7 @@ end TermShape /** Missing means definitely absent; Unknown must not be treated as a miss when * searching wildcard opens, since it can introduce an additional candidate. */ enum MemberLookup: + case Contextual(source: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance]) case Found(member: BlockMemberSymbol | RecordMember, marks: Ls[Marks]) // Declared members expose signatures only. Their marks transport dependent // type arguments; they never authorize reading an implementation's value flow. @@ -270,6 +274,7 @@ enum MemberLookup: case Unknown(reason: MemberLookup.Uncertainty, provenance: ShapeProvenance) def withMarks(marks: Ls[Marks]): MemberLookup = this match + case Contextual(source, instances) => Contextual(source.withMarks(marks), instances) case Found(member, inner) => Found(member, inner ::: marks) case Declared(member, bindings, inner, annotation) => Declared(member, bindings, inner ::: marks, annotation) case Indexed(field, inner) => Indexed(field, inner ::: marks) @@ -278,9 +283,16 @@ enum MemberLookup: /** The receiver's annotation remains the diagnostic origin when lookup visits a parent. */ def withAnnotation(annotation: Opt[Term]): MemberLookup = this match + case Contextual(source, instances) => Contextual(source.withAnnotation(annotation), instances) case Declared(member, bindings, marks, _) => Declared(member, bindings, marks, annotation) case _ => this + def instantiate(instances: Map[VarSymbol, TypeParameterInstance]): MemberLookup = + if instances.isEmpty then this else this match + case Contextual(source, previous) => Contextual(source, instances ++ previous) + case Missing | Unknown(_, _) | Dynamic(_) => this + case _ => Contextual(this, instances) + object MemberLookup: enum Uncertainty: case ValueShape, RecordOverwrite @@ -342,6 +354,14 @@ class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: L def enter(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = ??? def enter(marks: Marks)(using TL): TermShape | NoShape = ??? +/** Deferred overload selection retains the value's binder substitution. */ +final class ContextualSymShape(val source: SymShape, val instances: Map[VarSymbol, TypeParameterInstance]) +extends SymShape(source.sym, source.resSym, source.markss) + +/** Symbolic source-flow events carry the same body activation as value events. */ +final class ActivatedSymShape(val source: SymShape, val instances: Map[VarSymbol, TypeParameterInstance]) +extends SymShape(source.sym, source.resSym, source.markss) + /** Keep a declared receiver's boundary while consumers choose between the term, * type, and constructor interpretations of the same overload set. */ @@ -387,6 +407,26 @@ final case class InstanceShape(tpe: DeclaredType) extends NonAppTermShape: protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = lastWords("Instance member lookup requires interpreting its type first") +/** A deferred value observation shares its source shape and keeps a flat binder + * substitution. Members and function bodies are observed in that same view. + */ +final case class ContextualShape(source: NonMarkedShape, instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: + require(!source.isInstanceOf[ContextualShape], "Compose shape views rather than nesting them") + def describe: Str = source.describe + def toLoc: Opt[Loc] = source.toLoc + override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = source.isInstanceOfClass(cls) + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = source.getMember(name).instantiate(instances) + +/** A source-flow event identifies the activation in which its consumer executes. + * The value separately retains the caller's type references; these maps cannot + * be merged when a recursive callee rebinds the same source parameter. + */ +final case class ActivatedShape(value: TermShape, instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: + def describe: Str = value.describe + def toLoc: Opt[Loc] = value.toLoc + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = + lastWords("Activation events must be unpacked before observing their values") + /** A nominal annotation exposes only declarations, including inherited declarations. * In particular, selecting an unannotated field does not inspect its initializer. * The annotation is a diagnostic witness, excluded from shape equality; synthesized @@ -494,15 +534,19 @@ class RefinedShape(val base: TermShape, val refinements: Ls[Str -> Term]) extend * lazy. Recursive spreads can have unknown length; retain the known fields around * them instead of discarding either those fields or the unresolved possibilities. */ -final case class TupleShape(source: Term, elements: Ls[TupleShape.Element])(resolver: NewResolver) extends NonAppTermShape: +final case class TupleShape(source: Term, elements: Ls[TupleShape.Element], + instances: Map[VarSymbol, TypeParameterInstance])(resolver: NewResolver) extends NonAppTermShape: def arrayParent(using NewResolverState): NominalInstanceView = resolver.tupleArrayParent(this) override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = arrayParent.isInstanceOfClass(cls) - lazy val segments: Ls[TupleShape.Segment] = elements.flatMap: + private lazy val sourceSegments: Ls[TupleShape.Segment] = elements.flatMap: case segment: TupleShape.Segment => segment :: Nil case TupleShape.Rest(_, segments) => segments case TupleShape.Spread(shape, marks) => shape.segments.map: case field: TupleShape.Fixed => field.withMarks(marks :: Nil) case TupleShape.Unknown(source, inner, value) => TupleShape.Unknown(source, inner ::: marks :: Nil, value) + lazy val segments: Ls[TupleShape.Segment] = sourceSegments.map: + case field: TupleShape.Fixed => field.instantiate(instances) + case other => other /** Does this candidate already depend on the given producer in the given context? * `source` identifies the producer by syntax-node identity; `marks` distinguish * its spread contexts. Inspect the selected dependency tree, not flattened @@ -567,6 +611,17 @@ final case class UnknownValueShape(source: Term)(val provenance: ShapeProvenance protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, provenance) +/** A generic definition must be valid without choosing a caller's type. This + * checking witness is not an inferred bound on any call-site parameter. + */ +final case class RigidTypeShape(parameter: VarSymbol, source: Term) extends NonAppTermShape: + def describe: Str = "value of a type parameter" + def toLoc: Opt[Loc] = source.toLoc + def provenance: ShapeProvenance = ShapeProvenance( + msg"Type parameter '${parameter.nme}' does not specify a member interface." -> parameter.toLoc :: Nil) + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, provenance) + object UnknownValueShape: def at(source: Term): UnknownValueShape = UnknownValueShape(source)(ShapeProvenance(msg"The shape of this value is unknown." -> source.toLoc :: Nil)) @@ -579,6 +634,8 @@ object UnknownValueShape: }) object TupleShape: + def apply(source: Term, elements: Ls[Element])(resolver: NewResolver): TupleShape = + new TupleShape(source, elements, Map.empty)(resolver) sealed trait Element sealed trait Segment extends Element sealed trait Fixed extends Segment: @@ -588,6 +645,12 @@ object TupleShape: case TypedField(tpe, inner) => TypedField(tpe, inner ::: outer) case UnknownField(source, inner) => UnknownField(source, inner ::: outer) case ValueField(value, inner) => ValueField(value, inner ::: outer) + case ViewedField(source, instances, inner) => ViewedField(source, instances, inner ::: outer) + def instantiate(instances: Map[VarSymbol, TypeParameterInstance]): Fixed = + if instances.isEmpty then this else this match + case ViewedField(source, previous, marks) => ViewedField(source, instances ++ previous, marks) + case _ => ViewedField(this, instances, Nil) + final case class ViewedField(source: Fixed, instances: Map[VarSymbol, TypeParameterInstance], marks: Ls[Marks]) extends Fixed final case class Field(field: Fld, marks: Ls[Marks]) extends Fixed final case class ValueField(value: TermShape, marks: Ls[Marks]) extends Fixed final case class TypedField(tpe: DeclaredType, marks: Ls[Marks]) extends Fixed @@ -619,7 +682,8 @@ final case class RecordMember(field: RcdField, mutable: Bool) * provenance. Lookup follows runtime's last-write-wins order. Unknown entries * are barriers: an opaque spread or computed key can overwrite earlier fields. */ -final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element]) extends NonAppTermShape: +final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element], + instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: def describe: Str = "record literal" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = @@ -634,22 +698,26 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] case _ => unknown(field.field) case (unknown: RecordShape.Unknown) :: _ => Unknown(Uncertainty.RecordOverwrite, unknown.provenance) case RecordShape.Dynamic(marks) :: _ => Dynamic(marks) - case RecordShape.Spread(shape, marks) :: rest => shape.getMember(name) match - case Dynamic(inner) => Dynamic(inner ::: marks :: Nil) - case Unknown(_, provenance) => Unknown(Uncertainty.RecordOverwrite, provenance) - case Missing => loop(rest) - case Found(member: RecordMember, inner) => - Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil) - case Found(_: BlockMemberSymbol, _) | Declared(_, _, _, _) | Indexed(_, _) => - // Record spreads recursively look up RecordShapes, which only create RecordMembers. - lastWords("Record lookup returned a nominal member") - loop(elements.reverse) + case RecordShape.Spread(shape, marks) :: rest => + def spread(info: MemberLookup): Opt[MemberLookup] = info match + case Contextual(source, instances) => spread(source).map(_.instantiate(instances)) + case Dynamic(inner) => S(Dynamic(inner ::: marks :: Nil)) + case Unknown(_, provenance) => S(Unknown(Uncertainty.RecordOverwrite, provenance)) + case Missing => N + case Found(member: RecordMember, inner) => + S(Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil)) + case Found(_: BlockMemberSymbol, _) | Declared(_, _, _, _) | Indexed(_, _) => + // Record spreads recursively look up RecordShapes, which only create RecordMembers. + lastWords("Record lookup returned a nominal member") + spread(shape.getMember(name)).getOrElse(loop(rest)) + loop(elements.reverse).instantiate(instances) def containsSpread(record: Term.Rcd, marks: Marks): Bool = elements.exists: case RecordShape.Spread(shape, inner) => ((shape.source is record) && inner == marks) || shape.containsSpread(record, marks) case _ => false object RecordShape: + def apply(source: Term.Rcd, elements: Ls[Element]): RecordShape = new RecordShape(source, elements, Map.empty) enum Element: case Field(field: RcdField) case Spread(shape: RecordShape, marks: Marks) diff --git a/hkmc2/shared/src/test/mlscript/newres/ContextualInference.mls b/hkmc2/shared/src/test/mlscript/newres/ContextualInference.mls new file mode 100644 index 0000000000..0a6efe015c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ContextualInference.mls @@ -0,0 +1,57 @@ +:. + +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val value: T) +class Foo[X](val f: Box[X]) +private fun test[A](x: Foo[A]) = x.f +[test(new Foo(new Box(First(1)))).value.first, + test(new Foo(new Box(Second(2)))).value.second] +//│ = [1, 2] + +// Both the explicit binder and the unannotated parameter belong to each activation. +:expect [3, 4, 5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun pair[A](x: A, y) = [x, y] +private val shared = pair +let left = shared(First(3), Second(4)) +let right = shared(Second(5), First(6)) +[left.0.first, left.1.second, right.0.second, right.1.first] +//│ = [3, 4, 5, 6] +//│ left = [First(3), Second(4)] +//│ right = [Second(5), First(6)] +//│ shared = fun pair + +// Deferred record fields retain the same view as deferred tuple fields. +:expect [7, 8] +class First(val first: Int) +class Second(val second: Int) +private fun delayed[A](x: A) = {get: () => x} +[delayed(First(7)).get().first, delayed(Second(8)).get().second] +//│ = [7, 8] + +// Both deferred record fields must retain their activation after the call returns. +:expect [9, 10, 11, 12] +class First(val first: Int) +class Second(val second: Int) +private fun pair[A](x: A, y) = {annotated: () => x, inferred: () => y} +private val shared = pair +let left = shared(First(9), Second(10)) +let right = shared(Second(11), First(12)) +[left.annotated().first, left.inferred().second, right.annotated().second, right.inferred().first] +//│ = [9, 10, 11, 12] +//│ left = {annotated: fun annotated, inferred: fun inferred} +//│ right = {annotated: fun annotated, inferred: fun inferred} +//│ shared = fun pair + +// A callback call inside the shared body uses the enclosing call's binder view. +:expect [13, 14] +class First(val first: Int) +class Second(val second: Int) +private fun apply[A](x: A, f) = f(x) +private val shared = apply +[shared(First(13), x => x.first), shared(Second(14), x => x.second)] +//│ = [13, 14] +//│ shared = fun apply From a7b62feb869ba03b8ad2a01a3e954e5c1d841e6f Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 01:17:03 +0800 Subject: [PATCH 097/228] Relate nominal type arguments through contextual constraints --- doc/new-resolution-design.md | 9 ++ doc/new-resolution-suite-migration.md | 11 +- doc/new-resolution-type-value-flow.md | 65 ++++++-- .../test/scala/hkmc2/TypeRelationTest.scala | 140 ++++++++++++++++++ .../scala/hkmc2/semantics/NewResolver.scala | 94 ++++++++++-- .../hkmc2/semantics/NewResolverState.scala | 10 ++ .../scala/hkmc2/semantics/TypeShape.scala | 18 +++ .../mlscript/newres/ContextualInference.mls | 16 ++ .../test/mlscript/newres/MutableArrays.mls | 30 +--- .../mlscript/newres/TypeArgumentVariance.mls | 118 +++++++++++++++ 10 files changed, 453 insertions(+), 58 deletions(-) create mode 100644 hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala create mode 100644 hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index ddc38974ed..c19c1ea791 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -67,6 +67,15 @@ nominal parameters, such as `Foo[A]` containing a `Box[A]`. Generic methods use the same flow as free functions, including callback-result inference and curried signatures. Declared callable shapes retain their type parameters. +Nominal argument comparisons retain directed `ContextualType` endpoint pairs. +Invariant arguments install both directions, rather than copying expanded +candidates. `in`/`out` arguments use InvalML's input/output comparison rules; +written wildcards override declaration variance. A parameter receives a symbolic +instance wrapper, and structured/concrete targets retain listeners for later +bounds. `newres/MutableArrays.mls` includes passing direct and recursive append +cases, including distinct callers through one stored function reference. +`TypeRelationTest` checks graph replay and consumer isolation directly. + Generic definitions receive a distinct checking activation when their type parameters are declared. Its `RigidTypeShape` witnesses enforce generic opacity independently of visibility, exposure, or strict mode. Interface observation turns diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 25ef5f822a..7c34e2655f 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -96,11 +96,12 @@ The inventory below separates these from compiler and prelude gaps. the agreed variance rules and once-per-definition/call-site instantiation of declared type parameters. Its design review specifies contextual views with marks, partial signatures, and inference holes for omitted generic arguments. - Selected members also infer missing types through nominal annotations, with - receiver contexts and override constraints preserved. - Implement both input and output constraints for `Array[A]` together; - `appendTyped` and the recursive cases in `newres/MutableArrays.mls` record the - missing propagation and required isolation between callers. Member-variable + The review also requires inferred missing member types through nominal + annotations, with receiver contexts and override constraints preserved. + Bidirectional nominal argument constraints now pass `appendTyped` and the + recursive cases in `newres/MutableArrays.mls`, including isolation between + callers. Complete their integration with explicit specialization, constructors, + and holes; verify the whole graph's termination bound. Member-variable definitions still need work. Handler inference needs separate flows for the receiver, values passed to resumptions, and abortive results (`newres/HandlerResults.mls` and `codegen/ScopedBlocksAndHandlers`). Agree these designs before implementation. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 34463f4e89..6c40ea1dda 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -14,11 +14,12 @@ components; curried tails retain it, and independently quantified returned calla instantiate their own binders. Stored specializations of these callable signatures retain supplied arguments until application. Inline binders on partial and inferred functions also instantiate at application sites. Their shared bodies retain flat -substitutions through deferred tuples, records, callbacks, and closures. This does -not yet connect constructors, bidirectional constraints, variance, hole inference, -or inferred nominal member interfaces to call-site instantiation. -The representation and implementation order below are the outcome of the design -review, not evidence that the recursive acceptance cases already work. +substitutions through deferred tuples, records, callbacks, and closures. Nominal +argument comparisons retain both endpoint references and apply declaration/use-site +variance. The recursive array acceptance cases pass. Explicit argument suppression, +constructor instantiation, holes, inferred nominal member interfaces, and general +recursive alias environments still need integration. The implementation order below +remains the full design, not a claim that all its parts are complete. See the [resolver notes](new-resolution-design.md) for current behavior and the [migration worklist](new-resolution-suite-migration.md) for remaining ports. @@ -119,7 +120,7 @@ when contributing a parameter bound and observes them for structural comparison. Explicit arguments still suppress ordinary refinement; the constraint work must replace that suppression together with bidirectional flow. -## Variance rules to implement +## Variance rules Follow InvalML's argument interpretation in [`typeAndSubstType`](../hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala) @@ -152,9 +153,26 @@ admits only the input connection. Substitution into member types must select the appropriate part at each polarity, including nested arrows. Copy InvalML's variance semantics, not its fresh inference-variable implementation. +`TypeShape.Wildcard` retains the written input/output nodes. Nominal bindings apply +declaration variance only to unqualified arguments. Interface observation follows +the output part; an instance bound constrains the input part. Function constraints +already reverse domains, including callbacks nested in member inputs. Argument +comparison installs the two directed `ContextualType` relations above. Missing +parts use shared `Top`/`Bottom` nodes, independently of inference holes. +`newres/TypeArgumentVariance.mls` covers these paths; applying the same machinery +to standalone function/constructor specialization remains part of the explicit +type-argument integration work. + +Synthesized declaration variance uses `TypeShape.Argument`, whose parts retain +complete contextual type references. It must not transplant an argument's syntax +node into the alias or class's new binding environment: an outer argument with +the same source binder can then resolve back to its own wrapper. Reusing the +argument reference and normalizing repeated synthesized wrappers bounds this +construction. The recursive covariant `Tree` regression exercises this invariant. + ## Recursive acceptance example -This example is retained as a `:fixme` regression in +This passing regression is retained in [`newres/MutableArrays.mls`](../hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls) (the recursive `append` block, immediately after `appendTyped`): @@ -167,14 +185,11 @@ append(xs, Item(5), 2) xs.0.value ``` -The committed resolver cannot resolve `xs.0.value` because generic array input -propagation is missing. Its expected result is `5`. A discarded implementation -using two independent candidate-copying edges reached a different failure: -transporting an expanded abstract candidate through a wildcard capture and back -lost its original activation, letting it contaminate a real caller. The retained -golden output does not reproduce that prototype's diagnostic. Both failures -motivate the acceptance case: preserve type references and their contexts before -observation, rather than trying to recover them from expanded candidates. +The result is `5`. The relation connects the array's element parameter to the +call-site instance of `A` in both directions, retaining each endpoint's marks. +Recursive calls reuse their parameter instance and propagate the inserted element +back through that graph. The adjacent two-array case checks isolation between +external callers of the shared recursive site. ## Resolver integration @@ -325,6 +340,15 @@ applied to expanded candidates. Preserve a supplied union as a whole in negative position. Deduplicate relations and delivered obligations by semantic references, not by diagnostic witnesses. +The implemented `constrainTypes` memoizes pairs of `ContextualType` endpoints +before following them. A parameter receives an instance wrapper retaining the +source reference; a structured or concrete target subscribes to that reference's +later bounds. Quiet concrete mismatch diagnostics do not discard these obligations. +`TypeRelationTest` checks cyclic propagation, early/late bounds, every distinct +upper target, preservation of a whole negative union, reverse endpoint contexts, +and independent importers. These relation tests do not yet replace the separate +explicit-argument path described above. + Existing marks still transport instance flow through lexical scopes and distinguish enclosing activations sharing a static inner call. Apply their current entry/exit operations to each endpoint in its own view. A callee's symbol allocation key @@ -533,6 +557,17 @@ imported-view identity, shared consumer hosts, and exporter isolation. Whole-gra listener convergence and recursive alias bindings still need the corresponding checks before the full design is complete. +Shape substitution is memoized too: repeated observations reuse the same deferred +tuple/record identity, so synthesizing its nominal array interface cannot create +a fresh type node on each traversal. Relation keys contain endpoint references, +flat binder maps, and existing normalized marks; they contain no candidate history. +Replaying a relation adds no listener or candidate. The recursive changing-array +and mutual-recursion cases in `newres/ContextualInference.mls` terminate with mark +invariant checks enabled. For a fixed finite set of source type references, there +are finitely many endpoint/map/mark combinations, and replay adds no listeners. +That local bound does not establish the remaining whole-graph alias-environment +bound, which must also prove that source references cannot proliferate. + During implementation, assert that a call instance's origin is an original binder, all of one scheme's binders are allocated before its constraints are activated, views are composed rather than nested, and generic checking witnesses never become diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala new file mode 100644 index 0000000000..ebe83bea93 --- /dev/null +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -0,0 +1,140 @@ +package hkmc2 +package semantics + +import org.scalatest.funsuite.AnyFunSuite +import scala.collection.mutable.ArrayBuffer +import hkmc2.syntax.{Fun, Tree} +import hkmc2.utils.*, shorthands.* + + +class TypeRelationTest extends AnyFunSuite: + private class Harness: + import io.PlatformPath.given + given owner: Elaborator.State = new Elaborator.State + given state: NewResolverState = owner.newResolverState + given Raise = diagnostic => fail(diagnostic.theMsg) + given DebugPrinter = new DebugPrinter + given trace: TraceLogger = new TraceLogger: + override def doTrace: Boolean = false + given Config = Config.default(TestFolders.mainTestDir(os.pwd)) + given CompilerCtx = CompilerCtx.fresh(io.FileSystem.default, TestFolders.compilerPaths(os.pwd), summon[Config]) + val resolver = Elaborator(trace, TestFolders.mainTestDir(os.pwd), Elaborator.Ctx.empty) + def tpe(shape: TypeShape): DeclaredType = + val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + resolution.publish(shape) + DeclaredType(resolution, Map.empty, Map.empty) + def parameter(name: String): (VarSymbol, DeclaredType) = + val symbol = VarSymbol(Tree.Ident(name)) + (symbol, tpe(TypeShape.Parameter(symbol, symbol.inferenceHost))) + def observe(reference: ContextualType): ArrayBuffer[TermShape] = + val values = ArrayBuffer.empty[TermShape] + InstanceShape(reference.tpe).exit(reference.marks) match + case value: TermShape => resolver.listenInstanceViews(value)(values += _) + case NoShape => fail("A type reference cannot be unreachable") + values + + test("symbolic cycles retain early and late bounds without adding listeners on replay"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val (b, bt) = h.parameter("B") + val ar = ContextualType(at, Nil) + val br = ContextualType(bt, Nil) + val first = IntroShape(Term.UnitVal(), N) + val second = DynShape() + h.resolver.publishParameter(a, first) + h.resolver.constrainTypes(ar, br) + h.resolver.constrainTypes(br, ar) + val seen = h.observe(br) + h.resolver.publishParameter(a, second) + assert(seen.toSet == Set(first, second)) + val counts = (a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) + (1 to 1000).foreach: _ => + h.resolver.constrainTypes(ar, br) + h.resolver.constrainTypes(br, ar) + h.resolver.publishParameter(a, first) + assert((a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) + assert(seen.toList == List(first, second)) + + test("each concrete upper target receives later bounds and a union stays whole"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val one = h.tpe(TypeShape.Unit) + val two = h.tpe(TypeShape.Abstract) + val union = h.tpe(TypeShape.Union(one.resolution, two.resolution)) + val first = IntroShape(Term.UnitVal(), N) + val second = DynShape() + h.resolver.publishParameter(a, first) + h.resolver.constrainTypes(ContextualType(at, Nil), ContextualType(one, Nil)) + h.resolver.constrainTypes(ContextualType(at, Nil), ContextualType(two, Nil)) + h.resolver.publishParameter(a, second) + for target <- List(one, two); bound <- List(first, second) do + assert(h.state.typeConstraints((target, bound, Nil))) + val (b, bt) = h.parameter("B") + val distinct = OpaqueTypeShape(Term.UnitVal()) + h.resolver.constrainTypes(ContextualType(bt, Nil), ContextualType(union, Nil)) + h.resolver.publishParameter(b, distinct) + assert(h.state.typeConstraints((union, distinct, Nil))) + assert(!h.state.typeConstraints((one, distinct, Nil))) + assert(!h.state.typeConstraints((two, distinct, Nil))) + + test("reverse relations retain each endpoint's activation and exclude another caller"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val (b, bt) = h.parameter("B") + val source = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("source"))) + val target = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("target"))) + val site = FlowSymbol.app() + val other = FlowSymbol.app() + val sourceMarks = ExitMark(source, S(site), NoMarks) :: Nil + val targetMarks = ExitMark(target, S(site), NoMarks) :: Nil + val ar = ContextualType(at, sourceMarks) + val br = ContextualType(bt, targetMarks) + h.resolver.constrainTypes(ar, br) + h.resolver.constrainTypes(br, ar) + val seen = h.observe(ar) + val accepted = IntroShape(Term.UnitVal(), N) + h.resolver.publishParameter(b, accepted.enter(targetMarks)) + h.resolver.publishParameter(b, MarkedShape.enter(DynShape(), target, S(other))) + assert(seen.toList == List(accepted)) + + test("repeated deferred views reuse source identities for synthesized interfaces"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val substitution = h.state.instantiateTypeParameters(at.resolution, FlowSymbol.app(), List(a)) + val tuple = TupleShape(Term.UnitVal(), TupleShape.TypedField(at, Nil) :: Nil)(h.resolver) + val view = h.resolver.instantiateShape(tuple, substitution) + (1 to 1000).foreach: _ => + assert(h.resolver.instantiateShape(tuple, substitution) eq view) + + test("importers extend a cyclic relation without mutating the exporter or one another"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val (b, bt) = h.parameter("B") + val ar = ContextualType(at, Nil) + val br = ContextualType(bt, Nil) + h.resolver.constrainTypes(ar, br) + h.resolver.constrainTypes(br, ar) + val output = new TermShapeHost + h.resolver.listenInstanceViews(InstanceShape(bt))(output.publish) + val exporter = ArrayBuffer.empty[TermShape] + val detach = output.inferenceHost.observe(exporter += _) + val left = new Elaborator.State().newResolverState.inGraph(h.state) + val right = new Elaborator.State().newResolverState.inGraph(h.state) + val leftValues = ArrayBuffer.empty[TermShape] + val rightValues = ArrayBuffer.empty[TermShape] + h.resolver.listenInstanceViews(InstanceShape(bt))(leftValues += _)(using left) + h.resolver.listenInstanceViews(InstanceShape(bt))(rightValues += _)(using right) + val first = IntroShape(Term.UnitVal(), N) + val second = DynShape() + h.resolver.publishParameter(a, first)(using left) + h.resolver.publishParameter(a, second)(using right) + assert(leftValues.toList == List(first)) + assert(rightValues.toList == List(second)) + assert(exporter.isEmpty) + assert(output.currentShapes.isEmpty) + detach() diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 7453a6d476..0305caa908 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -118,6 +118,7 @@ class NewResolver: case DynTy() => result.publish(TypeShape.Dynamic) case FunTy(params, ret, _) => result.publish(TypeShape.Function(params, typeResolution(ret))) case UnitVal() => result.publish(TypeShape.Unit) + case WildcardTy(input, output) => result.publish(TypeShape.Wildcard(input.map(typeResolution), output.map(typeResolution))) case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => result.publish(TypeShape.Parameter(sym, sym.inferenceHost)) case Ref(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => @@ -134,7 +135,7 @@ class NewResolver: result.publish(TypeShape.Abstract) else result.publish(TypeShape.Record(record, fields.reverse.distinctBy(_.sym.nme).reverse.map(field => field -> typeResolution(field.rhs)))) - case _: WildcardTy | _: Neg | _: Tup | _: Lit | Missing | Error() => + case _: Neg | _: Tup | _: Lit | Missing | Error() => result.publish(TypeShape.Abstract) case _ => listen(term, discardMarks = true): case shape: SymShape => shape.sym.onComplete: () => @@ -179,6 +180,48 @@ class NewResolver: private def instanceType(instance: TypeParameterInstance)(using NewResolverState): DeclaredType = declaredType(typeResolution(instance.reference), Map.empty) + private def parameterType(parameter: TypeShape.Parameter)(using NewResolverState): DeclaredType = + rstate.parameterTypes.getOrElseUpdate(parameter, { + val source = SimpleRef(parameter.symbol)(parameter.symbol.id) + val resolution = new TypeResolution(source, messages => resolError(source, messages)) + resolution.publish(parameter) + declaredType(resolution, Map.empty) + }) + + private def extremeType(top: Bool)(using NewResolverState): DeclaredType = + rstate.extremeTypes.getOrElseUpdate(top, { + val resolution = new TypeResolution(Term.Missing, _ => lastWords("A synthesized extreme type cannot be invalid")) + resolution.publish(if top then TypeShape.Top else TypeShape.Bottom) + declaredType(resolution, Map.empty) + }) + + /** Written wildcards override declaration variance. Unqualified arguments use + * the declared variance; an invariant argument supplies both identical parts. + */ + private def argumentType(tpe: DeclaredType, variance: Opt[Bool])(using NewResolverState): DeclaredType = + if variance.isEmpty || tpe.resolution.source.withoutCaptures.isInstanceOf[WildcardTy] then tpe else + // A substituted argument is used as a type at this occurrence. Select its + // output before applying the new declaration's variance; do not rebind a + // reference to its own formal or stack repeated variance wrappers. + tpe.resolution.currentShapes.toList match + case TypeShape.Argument(parts) :: Nil => argumentType(parts.output.instantiate(tpe.instances), variance) + case _ => rstate.variantTypes.getOrElseUpdate((tpe, variance.get), { + val resolution = new TypeResolution(tpe.resolution.source, tpe.resolution.fail) + val parts = if variance.get then TypeArgument(extremeType(false), tpe) + else TypeArgument(tpe, extremeType(true)) + resolution.publish(TypeShape.Argument(parts)) + declaredType(resolution, Map.empty) + }) + + private def listenArgumentParts(tpe: DeclaredType)(listener: ShapeListener[TypeArgument])(using NewResolverState): Unit = + tpe.resolution.listen: + case TypeShape.Wildcard(input, output) => + listener(TypeArgument(input.fold(extremeType(false))(declaredType(_, tpe)), + output.fold(extremeType(true))(declaredType(_, tpe)))) + case TypeShape.Argument(parts) => + listener(TypeArgument(parts.input.instantiate(tpe.instances), parts.output.instantiate(tpe.instances))) + case _ => listener(TypeArgument(tpe, tpe)) + private def effectiveBindings(tpe: DeclaredType)(using NewResolverState): Map[VarSymbol, DeclaredType] = tpe.instances.map((parameter, instance) => parameter -> instanceType(instance)) ++ tpe.bindings.map((parameter, bound) => parameter -> bound.instantiate(tpe.instances)) @@ -240,7 +283,7 @@ class NewResolver: * children inherit the same flat map when they are subsequently observed. */ private[semantics] def instantiateShape(value: TermShape, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): TermShape = - if instances.isEmpty then value else + if instances.isEmpty then value else rstate.shapeViews.getOrElseUpdate((value, instances), { val Marked(source, marks) = value val instantiated: NonMarkedShape = source match case instance: InstanceShape => InstanceShape(instance.tpe.instantiate(instances)) @@ -267,6 +310,7 @@ class NewResolver: marks match case NoMarks => instantiated case marks: SomeMarks => MarkedShape(instantiated, marks) + }) private def contextualParts(value: TermShape): (TermShape, Map[VarSymbol, TypeParameterInstance]) = value match case Marked(ContextualShape(source, instances), marks) => @@ -287,10 +331,15 @@ class NewResolver: current.resolution.listen: shape => def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = effectiveBindings(current) ++ params.zipWithIndex.map: (param, index) => - param.sym -> args.lift(index).getOrElse(abstractType(current.resolution, S(param.sym))) + param.sym -> argumentType(args.lift(index).getOrElse(abstractType(current.resolution, S(param.sym))), param.vce) def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), Nil, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) + case TypeShape.Top => publish(OpaqueTypeShape(tpe.resolution.source)) + case TypeShape.Bottom => () + case TypeShape.Wildcard(_, output) => + output.fold(publish(OpaqueTypeShape(tpe.resolution.source)))(next) + case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), Nil, aliases, publish) case TypeShape.Inferred(value) => listenInstanceViews(instantiateShape(value, current.instances))(publish) case TypeShape.Nominal(defn) => val bindings = bind(defn.tparams) @@ -566,6 +615,15 @@ class NewResolver: // Install each constraint edge before subscribing: callback parameter/result // flow can revisit it immediately. Distinct instantiations retain their marks. private def typeConstraints(using rs: NewResolverState) = rs.typeConstraints + /** Keep both endpoints symbolic. Register before following either endpoint, + * so recursive references reuse the relation even before any bound exists. + */ + private[semantics] def constrainTypes(lower: ContextualType, upper: ContextualType)(using NewResolverState): Unit = + if rstate.typeRelations.add((lower, upper)) then + InstanceShape(lower.tpe).exit(lower.marks).enter(upper.marks) match + case value: TermShape => inferTypeArguments(upper.tpe, value, upper.marks) + case NoShape => () + private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = if !typeConstraints.add((tpe, value, marks)) then return def follow(tpe: DeclaredType, args: Ls[DeclaredType], captures: Ls[Marks], @@ -606,27 +664,33 @@ class NewResolver: case TypeShape.Function(_, _) => observe(constrainFunction(tpe, _, marks)) case TypeShape.Polymorphic(_, _, body) => follow(declaredType(body, tpe), args, captures, next) + case TypeShape.Wildcard(input, _) => input.foreach(res => follow(declaredType(res, tpe), args, captures, next)) + case TypeShape.Argument(parts) => follow(parts.input.instantiate(tpe.instances), args, captures, next) case TypeShape.Nominal(cls) if args.nonEmpty => listenInstanceViews(value): value => val (actual, instances) = contextualParts(value) val (head, context) = actual.applicationHead - def constrain(param: TyParam, pattern: DeclaredType)(using NewResolverState): Unit = - listenTypeArgument(param.sym): shape => - instantiateShape(shape, instances).exit(context) match - case actual: TermShape => inferTypeArguments(pattern, actual, marks) - case NoShape => () + def constrain(param: TyParam, bound: DeclaredType, pattern: DeclaredType)(using NewResolverState): Unit = + listenArgumentParts(bound.instantiate(instances)): actual => + listenArgumentParts(argumentType(pattern, param.vce)): expected => + constrainTypes(ContextualType(actual.output, context), ContextualType(expected.output, Nil)) + constrainTypes(ContextualType(expected.input, Nil), ContextualType(actual.input, context)) def constrainNominal(nominal: NominalInstanceView)(using NewResolverState): Unit = if nominal.defn is cls then cls.tparams.zip(args).foreach: (param, pattern) => - nominal.bindings.get(param.sym).foreach: bound => - listenTypeInstances(bound.instantiate(instances)): shape => - shape.exit(context) match - case actual: TermShape => inferTypeArguments(pattern, actual, marks) - case NoShape => () + nominal.bindings.get(param.sym).foreach(constrain(param, _, pattern)) head match - case ds: DefnShape if ds.clsDef.contains(cls) => cls.tparams.zip(args).foreach(constrain) + case ds: DefnShape if ds.clsDef.contains(cls) => cls.tparams.zip(args).foreach: (param, pattern) => + val bound = parameterType(TypeShape.Parameter(param.sym, param.sym.inferenceHost)) + constrain(param, argumentType(bound, param.vce), pattern) case nominal: NominalInstanceView => constrainNominal(nominal) case tuple: TupleShape => constrainNominal(tuple.arrayParent) case _ => () - case _ => () // Concrete annotations are opaque, irrespective of the argument value. + case TypeShape.Top => () + case _ => value match + // A concrete target stays fixed, but must receive bounds that arrive + // through a symbolic source later. Concrete mismatch reporting is + // separate from retaining this obligation in the graph. + case Marked(_: InstanceShape, _) => listenInstanceViews(value)(inferTypeArguments(tpe, _, marks)) + case _ => () // Alias expansion guards only unproductive cycles. Descending into a tuple, // nominal argument, or arrow installs another edge with its own cycle guard. follow(tpe, Nil, Nil, Set.empty) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 7b09727ffc..9d63f2304c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -210,6 +210,8 @@ final class NewResolverState private ( new Cache(inherited.map(_.instantiatedCallables), identity) val contextualSymbols: Cache[(SymShape, Map[VarSymbol, TypeParameterInstance]), ContextualSymShape] = new Cache(inherited.map(_.contextualSymbols), identity) + val shapeViews: Cache[(TermShape, Map[VarSymbol, TypeParameterInstance]), TermShape] = + new Cache(inherited.map(_.shapeViews), identity) val activatedSymbols: Cache[(SymShape, Map[VarSymbol, TypeParameterInstance]), ActivatedSymShape] = new Cache(inherited.map(_.activatedSymbols), identity) val inferredInstantiations: Seen[(TermSymbol, FlowSymbol, Map[VarSymbol, TypeParameterInstance], Ls[Marks])] = @@ -246,6 +248,10 @@ final class NewResolverState private ( new Cache(inherited.map(_.patternTypes), identity) val primitiveTypes: Cache[ClassSymbol, DeclaredType] = new Cache(inherited.map(_.primitiveTypes), identity) + val extremeTypes: Cache[Bool, DeclaredType] = + new Cache(inherited.map(_.extremeTypes), identity) + val variantTypes: Cache[(DeclaredType, Bool), DeclaredType] = + new Cache(inherited.map(_.variantTypes), identity) val abstractTypes: Cache[(TypeResolution, Opt[VarSymbol]), DeclaredType] = new Cache(inherited.map(_.abstractTypes), identity) val signatureParameters: Cache[VarSymbol, DeclaredType] = @@ -256,6 +262,8 @@ final class NewResolverState private ( new Cache(inherited.map(_.tupleArrayParents), identity) val instanceParameterTypes: Cache[(VarSymbol, Bool), DeclaredType] = new Cache(inherited.map(_.instanceParameterTypes), identity) + val parameterTypes: Cache[TypeShape.Parameter, DeclaredType] = + new Cache(inherited.map(_.parameterTypes), identity) val mutableArrays: Cache[Identity[Term.Mut], TermShape] = new Cache(inherited.map(_.mutableArrays), identity) // Named tuple fields have stable property identities, shared with consumers @@ -273,6 +281,8 @@ final class NewResolverState private ( root.explicitTypeArguments((symbol, marks)) || source.exists(_.hasExplicitTypeArgument(symbol, marks)) val typeConstraints: Seen[(DeclaredType, TermShape, Ls[Marks])] = new Seen(inherited.map(_.typeConstraints)) + val typeRelations: Seen[(ContextualType, ContextualType)] = + new Seen(inherited.map(_.typeRelations)) private[semantics] final class TermShapeHost extends Host[TermShape]: def showDbg(using DebugPrinter): Str = "instance views" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 44ec3df6d4..d3de942c1b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -17,6 +17,9 @@ enum TypeShape: case Function(params: Term, result: TypeResolution) case Polymorphic(params: Ls[TypeQuantifier], outer: Opt[VarSymbol], body: TypeResolution) case Applied(base: TypeResolution, args: Ls[TypeResolution]) + case Wildcard(input: Opt[TypeResolution], output: Opt[TypeResolution]) + // Synthesized declaration variance retains the argument's lexical environment. + case Argument(parts: TypeArgument) // The same third-party symbol can have different inference in two exporters. // Retain its originating host so importing a result needs no whole-state copy. case Parameter(symbol: VarSymbol, host: Publisher.Data[Shape]) @@ -28,6 +31,8 @@ enum TypeShape: case Unit case Dynamic case Abstract + case Top + case Bottom /** Candidates are published during elaboration. Erasure reads the resulting graph; it * never performs lookup or resolves an alias by inspecting an elaborated definition. @@ -65,6 +70,8 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case Alias(_, rhs) => rhs.foreach(_.validate(next)) case Captured(base, _) => base.validate(next) case Applied(base, args) => base.validate(next); args.foreach(_.validate(next)) + case Wildcard(input, output) => input.foreach(_.validate(next)); output.foreach(_.validate(next)) + case Argument(parts) => parts.input.resolution.validate(next); parts.output.resolution.validate(next) case Function(_, result) => result.validate(next) case Polymorphic(params, _, body) => params.foreach: param => @@ -91,6 +98,17 @@ final case class DeclaredType(resolution: TypeResolution, bindings: Map[VarSymbo def instantiate(substitution: Map[VarSymbol, TypeParameterInstance]): DeclaredType = copy(instances = substitution ++ instances) +/** A type reference observed from a common comparison scope. The marks belong + * to this endpoint: reversing a constraint swaps endpoints, not an expanded + * candidate's already-normalized path. + */ +final case class ContextualType(tpe: DeclaredType, marks: Ls[Marks]) + +/** Invariant S has S in both positions. Missing wildcard parts are represented + * by Nothing on input and Any on output, never by inference holes. + */ +final case class TypeArgument(input: DeclaredType, output: DeclaredType) + /** A quantified binder belongs to the source scheme; its bounds retain graph links * so mutually dependent bounds do not require expanding or copying their types. */ diff --git a/hkmc2/shared/src/test/mlscript/newres/ContextualInference.mls b/hkmc2/shared/src/test/mlscript/newres/ContextualInference.mls index 0a6efe015c..7206dedfa5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ContextualInference.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ContextualInference.mls @@ -55,3 +55,19 @@ private val shared = apply [shared(First(13), x => x.first), shared(Second(14), x => x.second)] //│ = [13, 14] //│ shared = fun apply + +// Recursive visits change the compound argument without expanding its type graph. +:expect [[[1]]] +private fun grow[A](value: A, n: Int) = + if n > 0 then grow(mut [value], n - 1) else value +grow(1, 3) +//│ = [[[1]]] + +// Mutual recursion also reuses each definition/site's binder group. +:expect [[2]] +private fun left[A](value: A, n: Int) = + if n > 0 then right(mut [value], n - 1) else value +private fun right[B](value: B, n: Int) = + if n > 0 then left(value, n - 1) else value +left(2, 3) +//│ = [[2]] diff --git a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls index ef5bbfd82f..89e243cd33 100644 --- a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls @@ -226,24 +226,17 @@ xs.0.value //│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' //│ xs = [Item(2)] -// TODO: generic Array[A] parameter bindings must carry writes back to the argument array. -:fixme +// Invariant Array[A] arguments retain the element reference in both directions. :expect 3 class Item(val value: Int) fun appendTyped[A](xs: Array[A], value: A) = xs.push(value) let xs = mut [] appendTyped(xs, Item(3)) xs.0.value -//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.236: xs.0.value -//│ ╙── ^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' +//│ = 3 //│ xs = [Item(3)] -// TODO: recursive generic input/output constraints must preserve caller activations. -// This also needs the element propagation exercised by appendTyped above. -:fixme +// Recursive input/output constraints preserve caller activations. :expect 5 class Item(val value: Int) fun append[A](xs: Array[A], value: A, n: Int) = @@ -251,16 +244,11 @@ fun append[A](xs: Array[A], value: A, n: Int) = let xs = mut [] append(xs, Item(5), 2) xs.0.value -//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.253: xs.0.value -//│ ╙── ^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '5', got: 'undefined' +//│ = 5 //│ xs = [Item(5)] -// TODO: instantiate declared A separately at each application through the alias. -// The recursive site is shared; enclosing marks must still separate its callers. -:fixme +// Distinct applications through an alias instantiate A separately. +// The recursive site is shared; enclosing marks still separate its callers. :expect [1, 2] class First(val first: Int) class Second(val second: Int) @@ -272,11 +260,7 @@ let ys = mut [] g(xs, First(1), 2) g(ys, Second(2), 2) [xs.0.first, ys.0.second] -//│ ╔══[COMPILATION ERROR] This selection of member 'first' has no resolved target -//│ ║ l.274: [xs.0.first, ys.0.second] -//│ ╙── ^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '[1, 2]', got: 'undefined' +//│ = [1, 2] //│ g = fun append //│ xs = [First(1)] //│ ys = [Second(2)] diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls b/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls new file mode 100644 index 0000000000..55a0c72ef9 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls @@ -0,0 +1,118 @@ +:. + +// An output wildcard retains the declared element interface. +:expect 1 +class Item(val value: Int) +let xs = (mut [Item(1)] as Array[out Item]) +xs.0.value +//│ = 1 +//│ xs = [Item(1)] + +// An input wildcard accepts elements but supplies no output member interface. +:ge +class Item(val value: Int) +let xs = (mut [] as Array[in Item]) +xs.push(Item(2)) +xs.0.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.16: xs.0.value +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This abstract type does not specify a member interface. +//│ ║ l.14: let xs = (mut [] as Array[in Item]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [Item(2)] + +// An output-only argument must not send an unrelated lower bound back to the array. +:ge +class Item(val value: Int) +private fun connect[A](xs: Array[out A], value: A) = () +let xs = mut [] +connect(xs, Item(3)) +xs.0.value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.33: xs.0.value +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [] + +// Declaration-site contravariance does not infer A from the sink's input type. +:expect 4 +class Base +class Child(val extra: Int) extends Base +class Sink[in T] with + fun put(value: T) = () +private fun infer[A](sink: Sink[A], value: A): A = value +infer(new Sink[Base], Child(4)).extra +//│ = 4 + +// A written invariant argument uses the declaration's output variance. +:expect 5 +class Item(val value: Int) +class Box[out T](val item: T) +private fun get[A](box: Box[A]): A = box.item +get(new Box(Item(5))).value +//│ = 5 + +// A use-site input wildcard overrides an output declaration. +:ge +class Item(val value: Int) +class Box[out T](val item: T) +let box = (new Box(Item(6)) as Box[in Item]) +box.item.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.63: box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This abstract type does not specify a member interface. +//│ ║ l.62: let box = (new Box(Item(6)) as Box[in Item]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ box = Box(Item(6)) + +// Callback inputs reverse function polarity inside a member's own input. +:expect 7 +class Item(val value: Int) +class Box[T](val item: T) with + fun apply(f: T -> Int): Int = f(item) +let box = (new Box(Item(7)) as Box[out Item]) +box.apply(x => x.value) +//│ = 7 +//│ box = Box(Item(7)) + +// The same callback sees Any when the argument supplies only an input part. +:ge +class Item(val value: Int) +class Box[T](val item: T) with + fun apply(f: T -> Int): Int = f(item) +let box = (new Box(Item(8)) as Box[in Item]) +box.apply(x => x.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.90: box.apply(x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This abstract type does not specify a member interface. +//│ ║ l.88: fun apply(f: T -> Int): Int = f(item) +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ box = Box(Item(8)) + +// Explicit input and output parts can both be written on the same argument. +:expect 9 +class Item(val value: Int) +let xs = (mut [] as Array[in Item out Item]) +xs.push(Item(9)) +xs.0.value +//│ = 9 +//│ xs = [Item(9)] + +// Recursive covariant aliases retain the source argument's lexical environment. +:expect 10 +type Tree[out A] = Node[A] | Empty +object Empty +data class Node[out A](item: A, tail: Tree[A]) +class Item(val value: Int) +private fun item[A](node: Node[A]): A = node.item +item(Node(Item(10), Node(Item(11), Empty))).value +//│ = 10 From facba31b6f4669c5dc1dc675b61d95b7ff87bcbf Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 01:59:57 +0800 Subject: [PATCH 098/228] Bind stored specializations at their application sites --- doc/new-resolution-design.md | 12 ++- doc/new-resolution-suite-migration.md | 5 +- doc/new-resolution-type-value-flow.md | 62 +++++++++--- .../src/test/scala/hkmc2/CompilerTest.scala | 22 ++++- .../test/scala/hkmc2/TypeRelationTest.scala | 95 +++++++++++++++++++ .../scala/hkmc2/semantics/Elaborator.scala | 6 +- .../hkmc2/semantics/InterfaceExposure.scala | 4 + .../scala/hkmc2/semantics/NewResolver.scala | 92 ++++++++++++++---- .../hkmc2/semantics/NewResolverState.scala | 20 +++- .../main/scala/hkmc2/semantics/Shape.scala | 11 +++ .../mlscript/newres/StoredSpecializations.mls | 94 ++++++++++++++++++ 11 files changed, 380 insertions(+), 43 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index c19c1ea791..93c3f409fb 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -62,7 +62,8 @@ recursive calls, nested captures, partial construction, and independent field re Explicit and inferred function/constructor type arguments flow through the corresponding type-parameter symbols with entry/exit marks. Explicit arguments -prevent further refinement from value arguments. Inference also connects nested +receive input constraints without adding value-argument shapes to their output +interface. Inference also connects nested nominal parameters, such as `Foo[A]` containing a `Box[A]`. Generic methods use the same flow as free functions, including callback-result inference and curried signatures. Declared callable shapes retain their type parameters. @@ -91,6 +92,15 @@ same original parameter. Contextual observations select compatible activations; marks continue to distinguish enclosing callers of a shared inner site. See `newres/ContextualInference.mls` for stored-function and deferred-field cases. +Standalone specializations of inferred functions retain their supplied types in +`SpecializedShape` until application. Each application binds its own memoized +parameter instances; the original generic binder remains unchanged. Argument +arity is checked even for unused specializations. `newres/StoredSpecializations.mls` +covers stored aliases, inferred record results, and curried calls. It also records +the remaining gap in observing an inferred specialized result during callback +checking before application; see the +[type-flow reference](new-resolution-type-value-flow.md#partial-application-and-explicit-specialization). + ## Declared interfaces and exposure checking Type interpretations retain arrows, type arguments, and lexical captures. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 7c34e2655f..6b6ac91a67 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -100,7 +100,10 @@ The inventory below separates these from compiler and prelude gaps. annotations, with receiver contexts and override constraints preserved. Bidirectional nominal argument constraints now pass `appendTyped` and the recursive cases in `newres/MutableArrays.mls`, including isolation between - callers. Complete their integration with explicit specialization, constructors, + callers. Stored specializations of inferred functions also preserve independent + call-site binders, including deferred records and curried calls. Complete + pre-application observations of specialized inferred results (the callback + regression in `newres/StoredSpecializations.mls`), constructors, and holes; verify the whole graph's termination bound. Member-variable definitions still need work. Handler inference needs separate flows for the receiver, values passed to resumptions, and abortive results (`newres/HandlerResults.mls` and diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 6c40ea1dda..e83bc79c36 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -11,13 +11,15 @@ are implemented. Explicit type application also observes annotated polymorphic values. Calls through complete callable signatures use the bounded binder-instance cache. `DeclaredType.instances` carries a flat substitution through their declared components; curried tails retain it, and independently quantified returned callables -instantiate their own binders. Stored specializations of these callable signatures -retain supplied arguments until application. Inline binders on partial and inferred +instantiate their own binders. Stored specializations of complete signatures and +inferred functions retain supplied arguments until application. Inline binders on partial and inferred functions also instantiate at application sites. Their shared bodies retain flat substitutions through deferred tuples, records, callbacks, and closures. Nominal argument comparisons retain both endpoint references and apply declaration/use-site -variance. The recursive array acceptance cases pass. Explicit argument suppression, -constructor instantiation, holes, inferred nominal member interfaces, and general +variance. The recursive array acceptance cases pass. Supplied function arguments +receive input obligations while retaining their declared output interface. +Observing specialized inferred functions before application, replacing the remaining +explicit-argument flags and constructor path, holes, inferred nominal member interfaces, and general recursive alias environments still need integration. The implementation order below remains the full design, not a claim that all its parts are complete. See the [resolver notes](new-resolution-design.md) @@ -114,11 +116,14 @@ the implementation is a `Child`. Interpreting a function type exposes its declar domain and result. Interpreting a union for member lookup can observe each alternative, while transporting its wrapper preserves the original union node. -This refactor alone does not implement the planned distinction between supplying -a type argument and adding an ordinary bound. `inferTypeArguments` retains wrappers -when contributing a parameter bound and observes them for structural comparison. -Explicit arguments still suppress ordinary refinement; the constraint work must -replace that suppression together with bidirectional flow. +`inferTypeArguments` retains wrappers when contributing a parameter bound and +observes them for structural comparison. For an explicitly supplied argument, +an input obligation follows its retained type reference instead of becoming an +additional output candidate. Each distinct supplied type receives the obligation; +a supplied union stays whole. The implementation still identifies supplied +positions with explicit-argument flags. Integrating those positions into the +shared reference graph remains necessary for constructor views and general +recursive type environments. ## Variance rules @@ -346,8 +351,11 @@ source reference; a structured or concrete target subscribes to that reference's later bounds. Quiet concrete mismatch diagnostics do not discard these obligations. `TypeRelationTest` checks cyclic propagation, early/late bounds, every distinct upper target, preservation of a whole negative union, reverse endpoint contexts, -and independent importers. These relation tests do not yet replace the separate -explicit-argument path described above. +and independent importers. The supplied-argument tests additionally check input +obligations arriving before and after supplied references, chained parameters in +different marked contexts, isolation between activations, and cycles without +listener growth. These checks do not yet replace the remaining explicit-argument +flags or establish the whole-graph termination bound. Existing marks still transport instance flow through lexical scopes and distinguish enclosing activations sharing a static inner call. Apply their current entry/exit @@ -490,6 +498,38 @@ introducing a second allocation policy at type-application nodes. Check argument arity against the retained scheme immediately, and allow observations of the specialized interface to use its supplied type references before a term call. +`SpecializedShape` retains the original inferred function, supplied argument +references, and captured call-site instances. Application unwraps that recipe, +allocates or reuses the definition/site binder group, and attaches the supplied +references before replaying body constraints. It does not publish arguments into +the original definition's type parameters. `newres/StoredSpecializations.mls` +checks independent specializations through one stored alias, deferred record +results, curried calls, and arity errors even when a specialization is unused. + +Pre-application observation of an inferred result is still missing. The same +worksheet retains this concrete regression: + +```mlscript +class Item(val value: Int) +private fun identity[A](value: A) = value +private fun use[B](f: Item -> B): B = f(Item(9)) +use(identity[Item]).value +``` + +The expected result is `9`. Checking the argument against `Item -> B` must observe +`identity`'s inferred result with `A` referring to the supplied `Item`. Currently, +the deferred body view can map `A` only to a call-site parameter symbol, and no +call to `identity` has been observed at this point. Its result therefore fails to +constrain `B`. Allocating an instance during callback checking or writing `Item` +into the shared original `A` would violate the design. + +The proposed extension, pending review, is to let deferred views also reference +supplied type-argument nodes. Such references must remain shared graph edges: +recursively substituting `DeclaredType.bindings` maps into one another would lose +the finite-domain argument below. The representation must support deferred tuple +fields and closures as well as this scalar result. This regression remains a +`:fixme` until that representation and its termination invariant are settled. + ### Constraint propagation and implementation order 1. Preserve partial declaration/signature schemes and their original identities, diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index 85450c14aa..1b83aa8bf3 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -346,7 +346,7 @@ class CompilerTest extends AnyFunSuite: "A consumer must not change the cached definition's inference graph") test("a supplied compound type stays inside one instance wrapper"): - import semantics.{InstanceShape, Marked, TermShape, TypeShape} + import semantics.{InstanceShape, Marked, Statement, Term, TermShape, TypeShape} val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) given DebugPrinter = new DebugPrinter @@ -363,8 +363,24 @@ class CompilerTest extends AnyFunSuite: val prelude = cctx.getPrelude(paths.preludeFile).ctx val unit = cctx.getElaboratedBlock(io.Path("/Types.mls"), prelude) val module = unit.compilationUnit.defaultExport.get.asModOrObj.get.defn.get - val parameter = module.body.members("identity").asTrm.get.defn.get.tparams.get.head.sym - val supplied = parameter.shapes.toList.flatMap: + val definition = module.body.members("identity").asTrm.get.defn.get + val parameter = definition.tparams.get.head.sym + def applications(statement: Statement): List[Term.App] = + val here = statement match + case app: Term.App => app :: Nil + case _ => Nil + here ::: statement.subStatements.toList.flatMap(applications) + val calls = applications(unit.term) + assert(calls.length == 1) + val state = unit.state.newResolverState + assert(state.allocatedTypeInstanceCount == 1) + val instance = state.instantiateTypeParameters(definition.tsym, calls.head.resSym, parameter :: Nil)(parameter) + assert(state.allocatedTypeInstanceCount == 1, "Observation must reuse the application's binder") + assert(!parameter.shapes.exists: + case Marked(_: InstanceShape, _) => true + case _ => false + , "Specialization must not publish supplied arguments into the source binder") + val supplied = instance.currentShapes(using state).toList.flatMap: case value: TermShape => value match case Marked(instance: InstanceShape, _) => instance.tpe.resolution :: Nil case _ => Nil diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index ebe83bea93..ddd07a1180 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -79,6 +79,101 @@ class TypeRelationTest extends AnyFunSuite: assert(!h.state.typeConstraints((one, distinct, Nil))) assert(!h.state.typeConstraints((two, distinct, Nil))) + test("supplied arguments receive input obligations instead of discarding them"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val one = h.tpe(TypeShape.Unit) + val two = h.tpe(TypeShape.Abstract) + val first = IntroShape(Term.UnitVal(), N) + val second = DynShape() + h.state.markExplicitTypeArgument(a, Nil) + h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(first)), Nil), ContextualType(at, Nil)) + h.resolver.publishParameter(a, InstanceShape(one)) + h.resolver.publishParameter(a, InstanceShape(two)) + h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(second)), Nil), ContextualType(at, Nil)) + for target <- List(one, two); bound <- List(first, second) do + assert(h.state.typeConstraints((target, bound, Nil))) + assert(a.currentShapes.toSet == Set(InstanceShape(one), InstanceShape(two))) + + test("a supplied union receives an input obligation as one type reference"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val one = h.tpe(TypeShape.Unit) + val two = h.tpe(TypeShape.Abstract) + val union = h.tpe(TypeShape.Union(one.resolution, two.resolution)) + val bound = IntroShape(Term.UnitVal(), N) + h.state.markExplicitTypeArgument(a, Nil) + h.resolver.publishParameter(a, InstanceShape(union)) + h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil), ContextualType(at, Nil)) + assert(h.state.typeConstraints((union, bound, Nil))) + assert(!h.state.typeConstraints((one, bound, Nil))) + assert(!h.state.typeConstraints((two, bound, Nil))) + + test("supplied parameter references forward input obligations across marked contexts"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val (b, bt) = h.parameter("B") + val source = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("source"))) + val outer = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("outer"))) + val target = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("target"))) + val site = FlowSymbol.app() + val sourceMarks = ExitMark(source, S(site), NoMarks) :: ExitMark(outer, S(site), NoMarks) :: Nil + val targetMarks = ExitMark(target, S(site), NoMarks) :: Nil + val concrete = h.tpe(TypeShape.Unit) + val bound = IntroShape(Term.UnitVal(), N) + h.state.markExplicitTypeArgument(a, targetMarks) + h.state.markExplicitTypeArgument(b, sourceMarks) + h.resolver.publishParameter(a, InstanceShape(bt).exit(sourceMarks).enter(targetMarks)) + h.resolver.publishParameter(b, InstanceShape(concrete).enter(sourceMarks)) + h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil), ContextualType(at, targetMarks)) + assert(h.state.typeConstraints((concrete, bound, Nil))) + assert(b.currentShapes.toSet == Set(InstanceShape(concrete).enter(sourceMarks))) + + test("a supplied argument receives only obligations for its marked activation"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val owner = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("owner"))) + val firstMarks = ExitMark(owner, S(FlowSymbol.app()), NoMarks) :: Nil + val secondMarks = ExitMark(owner, S(FlowSymbol.app()), NoMarks) :: Nil + val one = h.tpe(TypeShape.Unit) + val two = h.tpe(TypeShape.Abstract) + val bound = IntroShape(Term.UnitVal(), N) + h.state.markExplicitTypeArgument(a, firstMarks) + h.state.markExplicitTypeArgument(a, secondMarks) + h.resolver.publishParameter(a, InstanceShape(one).enter(firstMarks)) + h.resolver.publishParameter(a, InstanceShape(two).enter(secondMarks)) + h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil), ContextualType(at, firstMarks)) + assert(h.state.typeConstraints((one, bound, Nil))) + assert(!h.state.typeConstraints((two, bound, Nil))) + + test("supplied reference cycles saturate without expanding their arguments"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val (b, bt) = h.parameter("B") + val concrete = h.tpe(TypeShape.Unit) + val bound = IntroShape(Term.UnitVal(), N) + val lower = ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil) + h.state.markExplicitTypeArgument(a, Nil) + h.state.markExplicitTypeArgument(b, Nil) + h.resolver.publishParameter(a, InstanceShape(bt)) + h.resolver.publishParameter(b, InstanceShape(at)) + h.resolver.constrainTypes(lower, ContextualType(at, Nil)) + h.resolver.publishParameter(b, InstanceShape(concrete)) + assert(h.state.typeConstraints((concrete, bound, Nil))) + val counts = (a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) + (1 to 1000).foreach: _ => + h.resolver.constrainTypes(lower, ContextualType(at, Nil)) + h.resolver.publishParameter(a, InstanceShape(bt)) + h.resolver.publishParameter(b, InstanceShape(at)) + assert((a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) + assert(a.currentShapes.toSet == Set(InstanceShape(bt))) + assert(b.currentShapes.toSet == Set(InstanceShape(at), InstanceShape(concrete))) + test("reverse relations retain each endpoint's activation and exclude another caller"): val h = new Harness import h.given diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index c969e7cebb..c8549d7298 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1380,13 +1380,17 @@ extends Importer: raise(ErrorReport(msg"Name not found: $name" -> id.toLoc :: Nil)) error case TyApp(lhs, targs) => - Term.TyApp(subterm(lhs, interp), targs.map { + val result = Term.TyApp(subterm(lhs, interp), targs.map { case Modified(Keywrd(Keyword.`in`), arg) => Term.WildcardTy(S(subterm(arg, Tpe)), N) case Modified(Keywrd(Keyword.`out`), arg) => Term.WildcardTy(N, S(subterm(arg, Tpe))) case Tup(Modified(Keywrd(Keyword.`in`), arg1) :: Modified(Keywrd(Keyword.`out`), arg2) :: Nil) => Term.WildcardTy(S(subterm(arg1, Tpe)), S(subterm(arg2, Tpe))) case arg => subterm(arg, Tpe) })(N).withLocOf(tree) + // A term specialization must validate its arguments even when unused. + // Type and constructor interpretations have their own argument checks. + if newResolution && interp == Trm then listenTerm(result)(_ => ()) + result case InfixApp(TyTup(tvs), Keywrd(Keyword.`->`), body) => val boundVars = mutable.HashMap.empty[Str, VarSymbol] def genSym(id: Tree.Ident) = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index d54a599da1..9533a5fc06 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -123,6 +123,10 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) val substitution = instances ++ contextual.instances valueIn(value, path, substitution)(using rstate.withInstances(substitution)) case NoShape => () + case specialized: SpecializedShape => + resolver.instantiateShape(specialized.declaration, instances ++ specialized.instances).exit(marks) match + case value: TermShape => valueIn(value, path, instances) + case NoShape => () case _: InstanceShape => watch((shape, "instance view"))(resolver.listenInstanceViews(shape))(emit(_, path)) case ds: DefnShape if ds.defn.sym.getState is rstate.owner => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 0305caa908..a392560290 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -299,6 +299,8 @@ class NewResolver: record.bindings.map((parameter, bound) => parameter -> bound.instantiate(instances)) record.copy(bindings = bindings) case contextual: ContextualShape => contextual.copy(instances = instances ++ contextual.instances) + case specialized: SpecializedShape => specialized.copy( + arguments = specialized.arguments.map(_.instantiate(instances)), instances = instances ++ specialized.instances) case callable: CallableTypeShape => val captured = instances -- callable.tparams.map(_.parameter.symbol) callable.copy(paramLists = callable.paramLists.map: params => @@ -554,10 +556,8 @@ class NewResolver: if params.length != args.length then resolError(source, msg"${callee.describe.capitalize} expected ${params.length} type ${ "argument".pluralized(params.length)}, but got ${args.length}" -> callee.toLoc :: Nil) - params.zip(args).foreach: (param, arg) => - rstate.markExplicitTypeArgument(param.parameter.symbol, marks) - listenTypeInstances(arg): tpe => - publishParameter(param.parameter.host, tpe.enter(marks)) + bindTypeArguments(params.map(_.parameter), marks, + args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances))) callee match case callable: CallableTypeShape => apply(callable.tparams) case defn: DefnShape => defn.clsDef match @@ -573,6 +573,14 @@ class NewResolver: private def declaredParameter(symbol: VarSymbol)(using NewResolverState): DeclaredTypeParameter = DeclaredTypeParameter(TypeShape.Parameter(symbol, symbol.inferenceHost), N, N) + private def bindTypeArguments(parameters: Ls[TypeShape.Parameter], marks: Ls[Marks], arguments: Ls[DeclaredType]) + (using NewResolverState): Unit = + // Establish every supplied position before publication can replay body + // constraints or trigger observations of a mutually dependent parameter. + parameters.foreach(p => rstate.markExplicitTypeArgument(p.symbol, marks)) + parameters.zip(arguments).foreach: (parameter, argument) => + publishParameter(parameter.host, InstanceShape(argument).enter(marks)) + /** The first term application owns the binder group. Curried tails carry the * instantiated references and no scheme, so applying a later list reuses it. */ @@ -594,11 +602,10 @@ class NewResolver: // can publish candidates or trigger another observation of this call. rstate.instantiatedCallables(key) = instantiated callable.supplied.foreach: arguments => - scheme.parameters.zip(arguments).foreach: (parameter, argument) => + val parameters: Ls[TypeShape.Parameter] = scheme.parameters.map: parameter => val instance = substitution(parameter.parameter.symbol) - rstate.markExplicitTypeArgument(instance, marks) - listenTypeInstances(argument): value => - publishParameter(instance, value.enter(marks)) + TypeShape.Parameter(instance, instance.inferenceHost) + bindTypeArguments(parameters, marks, arguments) instantiated /** The body must satisfy its annotated result even if no value escapes or no @@ -639,8 +646,20 @@ class NewResolver: case S(bound) => follow(bound.instantiate(tpe.instances), Nil, captures, next) case N => val target = tpe.instances.get(symbol) - if !rstate.hasExplicitTypeArgument(target.getOrElse(symbol), marks) then - publishParameter(target.fold(host)(_.inferenceHost), value.enter(captures)) + val destination = target.fold(host)(_.inferenceHost) + val lower = value.enter(captures) + if rstate.hasExplicitTypeArgument(target.getOrElse(symbol), marks) then + listenTypeArgument(destination): supplied => + // Compare in the caller's scope, retaining the supplied + // reference's endpoint for any further parameter constraint. + supplied.exit(captures).exit(marks) match + case Marked(instance: InstanceShape, context) => + val endpoint = if context == NoMarks then Nil else context :: Nil + value.exit(marks).enter(context) match + case actual: TermShape => inferTypeArguments(instance.tpe, actual, endpoint) + case NoShape => () + case _ => () + else publishParameter(destination, lower) case TypeShape.Captured(base, thru) => follow(declaredType(base, tpe), args, EntryMark(ResolutionBoundary(thru), N, NoMarks) :: captures, next) @@ -862,7 +881,8 @@ class NewResolver: array.ancestor(cls).flatMap(_.bindings.get(cls.tparams.head.sym)) /** Element writes constrain the same declared parameter as Array's methods. - * Written concrete annotations remain opaque under inferTypeArguments. + * An annotated element type receives input constraints without exposing + * additional members from the assigned value. */ private[semantics] def assignArrayElement(lhs: Term, rhs: Term)(using NewResolverState): Unit = val receiver = lhs match @@ -1283,7 +1303,14 @@ class NewResolver: def appShape(source: TermShape, args: Term, res: App)(using NewResolverState): Unit = val callerInstances = rstate.instances - val (lhs, lexical) = contextualParts(source) + val (original, outer) = contextualParts(source) + val (lhs, lexical, supplied) = original match + case Marked(specialized: SpecializedShape, marks) => + val declaration = marks match + case NoMarks => specialized.declaration + case marks: SomeMarks => MarkedShape(specialized.declaration, marks) + (declaration, outer ++ specialized.instances, S(specialized.arguments)) + case _ => (original, outer, N) val captured = callerInstances ++ lexical val instances = lhs match case Marked(definition: DefnShape, _) => definition.defn match @@ -1291,6 +1318,11 @@ class NewResolver: val marks = lhs.applicationHead._2 val parameters = td.tparams.get.map(_.sym) val substitution = captured ++ rstate.instantiateTypeParameters(td.tsym, res.resSym, parameters) + supplied.foreach: arguments => + bindTypeArguments(parameters.map: parameter => + val instance = substitution(parameter) + TypeShape.Parameter(instance, instance.inferenceHost) + , marks, arguments) if rstate.inferredInstantiations.add((td.tsym, res.resSym, substitution, marks)) then parameters.foreach: parameter => val instance = substitution(parameter) @@ -1298,7 +1330,7 @@ class NewResolver: listenTypeArgument(parameter): bound => bound.applicationHead._1 match case _: RigidTypeShape => () - case _ => publishParameter(instance, instantiateShape(bound, substitution)) + case _ => inferTypeArguments(instanceType(instance), instantiateShape(bound, substitution), marks) substitution case _ => captured case _ => captured @@ -1930,21 +1962,39 @@ class NewResolver: lastWords("Synthetic selections must not enter new resolution") case Asc(_, sign) => listenTypeInstances(sign)(listener) case _: DynSel | _: DynNew => listener(DynShape()) - case TyApp(underlying, args) => + case application @ TyApp(underlying, args) => + def checkArity(callee: TermShape, count: Int)(using NewResolverState): Unit = + if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then + resolError(trm, msg"${callee.describe.capitalize} expected ${count} type ${ + "argument".pluralized(count)}, but got ${args.length}" -> callee.toLoc :: Nil) listen(underlying, discardMarks): shape => def instantiate(value: TermShape)(using NewResolverState): Unit = listenInstanceViews(value): viewed => - viewed.applicationHead match + val (actual, captured) = contextualParts(viewed) + actual.applicationHead match + case (specialized: SpecializedShape, _) => + checkArity(specialized, 0) + listener(value) + case (callable: CallableTypeShape, _) if callable.supplied.nonEmpty => + checkArity(callable, 0) + listener(value) case (callable: CallableTypeShape, marks) => - if callable.tparams.length != args.length then - resolError(trm, msg"${callable.describe.capitalize} expected ${callable.tparams.length} type ${ - "argument".pluralized(callable.tparams.length)}, but got ${args.length}" -> callable.toLoc :: Nil) - val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty)) + checkArity(callable, callable.tparams.length) + val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) callable.copy(supplied = S(supplied)).exit(marks) match case value: TermShape => listener(value) case NoShape => () case (callee: DefnShape, marks) => - applyTypeArguments(callee, marks, args, trm) - listener(value) + callee.defn match + case td: TermDefinition if !td.tsym.isInstanceOf[ClassCtorSymbol] => + val parameters = td.tparams.toList.flatten + checkArity(callee, parameters.length) + val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) + SpecializedShape(callee, supplied, captured).exit(marks) match + case specialized: TermShape => listener(specialized) + case NoShape => () + case _ => + applyTypeArguments(callee, marks, args, trm) + listener(value) case _ => listener(value) shape match case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => (), false) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 9d63f2304c..aefd4b6853 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -274,15 +274,25 @@ final class NewResolverState private ( new Seen(inherited.map(_.aggregateProducers)) // Explicit instantiations are visible to every graph view in this consumer; // inherited flags are queried without enumerating an exporter's instantiations. - private val explicitTypeArguments = mutable.Set.empty[(VarSymbol, Ls[Marks])] - def markExplicitTypeArgument(symbol: VarSymbol, marks: Ls[Marks]): Unit = - root.explicitTypeArguments += ((symbol, marks)) - def hasExplicitTypeArgument(symbol: VarSymbol, marks: Ls[Marks]): Bool = - root.explicitTypeArguments((symbol, marks)) || source.exists(_.hasExplicitTypeArgument(symbol, marks)) + private val explicitTypeArguments = mutable.Set.empty[(VarSymbol, Marks)] + private def argumentContext(marks: Ls[Marks])(using TL): Opt[Marks] = + // A path can arrive as several fragments or one composed mark. Use the + // existing transport operation so these identify the same supplied slot. + DynShape().exit(marks) match + case Marked(_, context) => S(context) + case NoShape => N + def markExplicitTypeArgument(symbol: VarSymbol, marks: Ls[Marks])(using TL): Unit = + argumentContext(marks).foreach(context => root.explicitTypeArguments += ((symbol, context))) + def hasExplicitTypeArgument(symbol: VarSymbol, marks: Ls[Marks])(using TL): Bool = + argumentContext(marks).exists(context => hasExplicitTypeArgument((symbol, context))) + private def hasExplicitTypeArgument(key: (VarSymbol, Marks)): Bool = + root.explicitTypeArguments(key) || source.exists(_.hasExplicitTypeArgument(key)) val typeConstraints: Seen[(DeclaredType, TermShape, Ls[Marks])] = new Seen(inherited.map(_.typeConstraints)) val typeRelations: Seen[(ContextualType, ContextualType)] = new Seen(inherited.map(_.typeRelations)) + val typeArgumentArityErrors: Seen[(Identity[TyApp], Int)] = + new Seen(inherited.map(_.typeArgumentArityErrors)) private[semantics] final class TermShapeHost extends Host[TermShape]: def showDbg(using DebugPrinter): Str = "instance views" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 55d4d8f67a..086996dc3c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -31,6 +31,7 @@ sealed trait Shape extends ShapeLike: case ts: OpaqueTypeShape => s"OpaqueTypeShape(${ts.source.showDbg})" case ts: CallableTypeShape => s"CallableTypeShape(${ts.source.showDbg})" case view: ContextualShape => s"ContextualShape(${view.source.shwDbg})" + case specialized: SpecializedShape => s"SpecializedShape(${specialized.declaration.shwDbg})" case rigid: RigidTypeShape => s"RigidTypeShape(${rigid.parameter.showDbg})" case flow: ActivatedShape => s"ActivatedShape(${flow.value.shwDbg})" case bs: BaseShape => s"BaseShape(${bs.defn.sym.showDbg})" @@ -176,6 +177,7 @@ sealed trait TermShape extends Shape: // All remaining parameter lists belong to the same callable and carry its // context. Keep the lists separate until the public view needs their pairs. private lazy val unappliedParamLists: Ls[ParamList] = this match + case specialized: SpecializedShape => (specialized.declaration: TermShape).unappliedParamLists case ds: DefnShape => ds.defn match case defn: TermDefinition => defn.params case defn: ClassDef => @@ -417,6 +419,15 @@ final case class ContextualShape(source: NonMarkedShape, instances: Map[VarSymbo override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = source.isInstanceOfClass(cls) protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = source.getMember(name).instantiate(instances) +/** A standalone type application is a recipe, not a call activation. Each later + * application uses its own binder group while retaining these caller references. + */ +final case class SpecializedShape(declaration: DefnShape, arguments: Ls[DeclaredType], + instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: + def describe: Str = declaration.describe + def toLoc: Opt[Loc] = declaration.toLoc + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = declaration.getMember(name).instantiate(instances) + /** A source-flow event identifies the activation in which its consumer executes. * The value separately retains the caller's type references; these maps cannot * be merged when a recursive callee rebinds the same source parameter. diff --git a/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls b/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls new file mode 100644 index 0000000000..af1fa1611a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls @@ -0,0 +1,94 @@ +:. + +// Specializations through one shared reference bind only their eventual call instances. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +private fun identity[A](x: A) = x +private val shared = identity +private val first = shared[First] +private val second = shared[Second] +[first(First(1)).first, second(Second(2)).second] +//│ = [1, 2] +//│ first = fun identity +//│ second = fun identity +//│ shared = fun identity + +// Deferred inferred results retain the supplied type reference. +:expect [3, 4] +class First(val first: Int) +class Second(val second: Int) +private fun delayed[A](x: A) = {get: () => x} +private val shared = delayed +private val first = shared[First] +private val second = shared[Second] +[first(First(3)).get().first, second(Second(4)).get().second] +//│ = [3, 4] +//│ first = fun delayed +//│ second = fun delayed +//│ shared = fun delayed + +// Curried tails keep the first application's specialization and inferred arguments. +:expect [5, 6, 7, 8] +class First(val first: Int) +class Second(val second: Int) +private fun pair[A](x: A)(y) = [x, y] +private val shared = pair +private val left = shared[First](First(5)) +private val right = shared[Second](Second(7)) +[left(Second(6)).0.first, left(Second(6)).1.second, + right(First(8)).0.second, right(First(8)).1.first] +//│ = [5, 6, 7, 8] +//│ left = fun +//│ right = fun +//│ shared = fun pair + +// Callback checking must observe the specialization before an implementation call. +// TODO: Deferred inferred body views need references to supplied type arguments. +:fixme +:expect 9 +class Item(val value: Int) +private fun identity[A](value: A) = value +private fun use[B](f: Item -> B): B = f(Item(9)) +use(identity[Item]).value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.53: use(identity[Item]).value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '9', got: 'undefined' + +// Supplying the scheme's arguments consumes its type parameter list immediately. +:ge +private fun inferred[A](value: A) = value +private fun declared[A](value: A): A = value +private val first = inferred[Int] +private val second = declared[Int] +first[Str] +second[Str] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.66: first[Str] +//│ ║ ^^^^^^^^^^ +//│ ╟── Function 'inferred' expected 0 type arguments, but got 1 +//│ ║ l.62: private fun inferred[A](value: A) = value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.67: second[Str] +//│ ║ ^^^^^^^^^^^ +//│ ╟── Function 'declared' expected 0 type arguments, but got 1 +//│ ║ l.63: private fun declared[A](value: A): A = value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ = fun declared +//│ first = fun inferred +//│ second = fun declared + +// Arity is checked at specialization even if the value is never called. +:ge +private fun identity[A](value: A) = value +private val tooMany = identity[Int, Str] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.87: private val tooMany = identity[Int, Str] +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'identity' expected 1 type argument, but got 2 +//│ ║ l.86: private fun identity[A](value: A) = value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ tooMany = fun identity From 0dc32f0776f6dd3f4ca2d5fd35cb6fedae664272 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 02:13:48 +0800 Subject: [PATCH 099/228] Infer omitted generic arguments in annotations --- doc/new-resolution-design.md | 9 + doc/new-resolution-suite-migration.md | 6 +- doc/new-resolution-type-value-flow.md | 50 ++++- .../test/scala/hkmc2/TypeRelationTest.scala | 81 ++++++++ .../scala/hkmc2/semantics/Elaborator.scala | 5 +- .../hkmc2/semantics/InterfaceExposure.scala | 16 +- .../scala/hkmc2/semantics/NewResolver.scala | 124 ++++++++++-- .../hkmc2/semantics/NewResolverState.scala | 6 +- .../scala/hkmc2/semantics/TypeShape.scala | 17 +- .../test/mlscript/newres/DeclaredTypes.mls | 18 +- .../test/mlscript/newres/InferenceHoles.mls | 184 ++++++++++++++++++ .../mlscript/newres/PartialSignatures.mls | 63 ++---- .../src/test/mlscript/newres/SpreadCalls.mls | 65 ++++--- 13 files changed, 525 insertions(+), 119 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 93c3f409fb..5aeb057e1b 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -92,6 +92,15 @@ same original parameter. Contextual observations select compatible activations; marks continue to distinguish enclosing callers of a shared inner site. See `newres/ContextualInference.mls` for stored-function and deferred-field cases. +Omitted generic arguments use `TypeShape.Hole`, a stable inference host for each +source type-use/formal-position pair. They infer through arguments, result +annotations, and ascriptions; an empty hole waits for evidence. Written fragments +continue to restrict the interface. Exposure checking contributes unknown values +to missing parts of external inputs, including callback results, without widening +written binders. `newres/InferenceHoles.mls` also retains unresolved cases for +recursive holes and omissions shared through an alias body. No hole creates a +call-site parameter instance. + Standalone specializations of inferred functions retain their supplied types in `SpecializedShape` until application. Each application binds its own memoized parameter instances; the original generic binder remains unchanged. Argument diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 6b6ac91a67..dc1ba602a3 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -103,8 +103,10 @@ The inventory below separates these from compiler and prelude gaps. callers. Stored specializations of inferred functions also preserve independent call-site binders, including deferred records and curried calls. Complete pre-application observations of specialized inferred results (the callback - regression in `newres/StoredSpecializations.mls`), constructors, - and holes; verify the whole graph's termination bound. Member-variable + regression in `newres/StoredSpecializations.mls`) and constructors. Omitted + arguments now infer through partial annotations, results, and ascriptions; + complete their recursive and alias-use contexts (`newres/InferenceHoles.mls`). + Verify the whole graph's termination bound. Member-variable definitions still need work. Handler inference needs separate flows for the receiver, values passed to resumptions, and abortive results (`newres/HandlerResults.mls` and `codegen/ScopedBlocksAndHandlers`). Agree these designs before implementation. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index e83bc79c36..2dcccf1c4d 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -16,12 +16,15 @@ inferred functions retain supplied arguments until application. Inline binders o functions also instantiate at application sites. Their shared bodies retain flat substitutions through deferred tuples, records, callbacks, and closures. Nominal argument comparisons retain both endpoint references and apply declaration/use-site -variance. The recursive array acceptance cases pass. Supplied function arguments +variance. The explicit-binder recursive array acceptance cases pass. Supplied function arguments receive input obligations while retaining their declared output interface. Observing specialized inferred functions before application, replacing the remaining -explicit-argument flags and constructor path, holes, inferred nominal member interfaces, and general -recursive alias environments still need integration. The implementation order below -remains the full design, not a claim that all its parts are complete. +explicit-argument flags and constructor path, inferred nominal member interfaces, +and general recursive alias environments still need integration. Omitted arguments +now use source-owned inference holes; recursive hole contexts and omissions inside +alias bodies still need the contextual reference work described below. The +implementation order below remains the full design, not a claim that all its parts +are complete. See the [resolver notes](new-resolution-design.md) for current behavior and the [migration worklist](new-resolution-suite-migration.md) for remaining ports. @@ -433,12 +436,35 @@ is transported with the alias use's context; expansion must not allocate more hole symbols. Excess arguments still indicate an arity error. The positive `Pair[Int]`, `HalfPair`, and bare `Array` cases in -`newres/PartialSignatures.mls` record this missing behavior as `:fixme`s. Existing +`newres/PartialSignatures.mls` now pass. Existing `DeclaredTypes.mls` examples selecting members from omitted arguments without any supporting flow remain negative tests: a hole is inferable, not evidence of an -arbitrary member. Replace the current unconditional `abstractType` treatment of -omissions; report insufficient inferred information when a genuinely unfilled -hole is observed. +arbitrary member. `TypeShape.Hole` retains a source inference host, cached by the +type-use node and omitted formal parameter. Observation and constraint propagation +share the argument-completion operation, so they reach the same hole. Bounds can +arrive after an observation; an empty host emits no unknown candidate. Intentional +abstraction remains `TypeShape.Abstract`. Type validation rejects excess arguments, +including in unused annotations. + +Result annotations and ascriptions constrain their implementations, allowing their +holes to receive evidence while their written fragments keep restricting the +interface. Exposure checking contributes unknown shapes only to missing output +parts of incoming values, including external callback results. It does not add +arbitrary lower bounds to written binders fixed by a partial application. These +unknowns represent real possible inputs and remain alongside local evidence. +`newres/InferenceHoles.mls` checks source/formal-position isolation, these annotation +boundaries, and exposed interfaces. + +Two context cases remain explicit `:fixme`s in that worksheet. A recursive +`append(xs: Array, value, n: Int)` currently mixes the element interfaces of two +external callers, whereas the explicit-binder version has separate call-site +symbols. Holes must retain ordinary marked inference; copying the hole at each +call is not an acceptable repair. Also, with `type SomeBox = Box`, the hole in the +alias body is currently shared by both parameters of +`both(left: SomeBox, right: SomeBox)`, even though their alias-use references differ. +The contextual graph representation must preserve those uses without expanding +the alias or allocating more hole symbols. Neither case is covered by the passing +source-occurrence isolation test, which uses two directly written `Pair` annotations. The scheme therefore cannot be a closed type synthesized from whatever shapes happen to be available first. It must retain live links to inferred portions of @@ -608,6 +634,14 @@ are finitely many endpoint/map/mark combinations, and replay adds no listeners. That local bound does not establish the remaining whole-graph alias-environment bound, which must also prove that source references cannot proliferate. +For omitted arguments, each source type-use/formal-position pair allocates one +`TypeShape.Hole` host. No recursive traversal or call allocates a parameter symbol +for it. `TypeRelationTest` checks reuse across repeated observations, empty hosts +waiting for evidence, cyclic constraints with stable listener counts, and private +bounds in separate importers sharing the same source identity. This bounds hole +allocation and replay; it does not establish the missing context precision for +recursive holes or alias-body omissions noted above. + During implementation, assert that a call instance's origin is an original binder, all of one scheme's binders are allocated before its constraints are activated, views are composed rather than nested, and generic checking witnesses never become diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index ddd07a1180..fb6e71ee45 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -56,6 +56,87 @@ class TypeRelationTest extends AnyFunSuite: assert((a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) assert(seen.toList == List(first, second)) + test("omitted arguments reuse source-owned holes and wait for evidence"): + val h = new Harness + import h.given + val (a, _) = h.parameter("A") + val (b, _) = h.parameter("B") + val firstUse = h.tpe(TypeShape.Abstract).resolution + val secondUse = h.tpe(TypeShape.Abstract).resolution + val hole = h.resolver.omittedType(firstUse, a) + val otherPosition = h.resolver.omittedType(firstUse, b) + val otherUse = h.resolver.omittedType(secondUse, a) + assert(!(hole.resolution eq otherPosition.resolution)) + assert(!(hole.resolution eq otherUse.resolution)) + val seen = h.observe(ContextualType(hole, Nil)) + val unrelated = h.observe(ContextualType(otherPosition, Nil)) + assert(seen.isEmpty) + val bound = IntroShape(Term.UnitVal(), N) + h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil), ContextualType(hole, Nil)) + assert(seen.toList == List(bound)) + assert(unrelated.isEmpty) + (1 to 1000).foreach: _ => + assert(h.resolver.omittedType(firstUse, a) eq hole) + assert(h.state.allocatedTypeInstanceCount == 0) + + test("cyclic hole constraints saturate without allocating parameter instances"): + val h = new Harness + import h.given + val (parameter, _) = h.parameter("T") + val first = h.resolver.omittedType(h.tpe(TypeShape.Abstract).resolution, parameter) + val second = h.resolver.omittedType(h.tpe(TypeShape.Abstract).resolution, parameter) + val left = ContextualType(first, Nil) + val right = ContextualType(second, Nil) + h.resolver.constrainTypes(left, right) + h.resolver.constrainTypes(right, left) + val seen = h.observe(right) + assert(seen.isEmpty) + val bound = IntroShape(Term.UnitVal(), N) + val input = ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil) + h.resolver.constrainTypes(input, left) + def listeners(tpe: DeclaredType): Int = tpe.resolution.currentShapes.toList match + case TypeShape.Hole(host) :: Nil => host.listeners.size + case _ => fail("An omitted argument must retain its inference host") + val counts = (listeners(first), listeners(second)) + (1 to 1000).foreach: _ => + h.resolver.constrainTypes(left, right) + h.resolver.constrainTypes(right, left) + h.resolver.constrainTypes(input, left) + assert(seen.toList == List(bound)) + assert((listeners(first), listeners(second)) == counts) + assert(h.state.allocatedTypeInstanceCount == 0) + + test("importers share hole identities while keeping inferred bounds private"): + val h = new Harness + import h.given + val (parameter, _) = h.parameter("T") + val source = h.tpe(TypeShape.Abstract).resolution + val hole = h.resolver.omittedType(source, parameter) + val output = new TermShapeHost + h.resolver.listenInstanceViews(InstanceShape(hole))(output.publish) + val exporter = ArrayBuffer.empty[TermShape] + val detach = output.inferenceHost.observe(exporter += _) + val left = new Elaborator.State().newResolverState.inGraph(h.state) + val right = new Elaborator.State().newResolverState.inGraph(h.state) + assert(h.resolver.omittedType(source, parameter)(using left) eq hole) + assert(h.resolver.omittedType(source, parameter)(using right) eq hole) + val leftValues = ArrayBuffer.empty[TermShape] + val rightValues = ArrayBuffer.empty[TermShape] + h.resolver.listenInstanceViews(InstanceShape(hole))(leftValues += _)(using left) + h.resolver.listenInstanceViews(InstanceShape(hole))(rightValues += _)(using right) + val first = IntroShape(Term.UnitVal(), N) + val second = DynShape() + h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(first)), Nil), ContextualType(hole, Nil))(using left) + h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(second)), Nil), ContextualType(hole, Nil))(using right) + assert(leftValues.toList == List(first)) + assert(rightValues.toList == List(second)) + assert(exporter.isEmpty) + assert(output.currentShapes.isEmpty) + assert(h.state.allocatedTypeInstanceCount == 0) + assert(left.allocatedTypeInstanceCount == 0) + assert(right.allocatedTypeInstanceCount == 0) + detach() + test("each concrete upper target receives later bounds and a union stays whole"): val h = new Harness import h.given diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index c8549d7298..1882e0fadf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1441,7 +1441,10 @@ extends Importer: case _ => lastWords(s"Unexpected lambda parameter shape: $lhs") case Keywrd(Keyword.`dyn`) => Term.DynTy().withLocOf(tree) case InfixApp(lhs, Keywrd(Keyword.`as`), rhs) => - Term.Asc(subterm(lhs, interp), subterm(rhs, Tpe)) + val body = subterm(lhs, interp) + val sign = subterm(rhs, Tpe) + if newResolution then checkAscription(body, sign) + Term.Asc(body, sign) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => block(Block(tree :: Nil), hasResult = false, resultInterp = interp)._1 case PrefixApp(kw @ Keywrd(Keyword.`not`), rhs) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index 9533a5fc06..d8ea41d8a3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -72,11 +72,15 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) case shape: TermShape => listener(shape) case NoShape => ()))(emit(_, path)) - private def parameter(param: Param, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = if resolver.parameterSignature(param).isEmpty then + private def parameter(param: Param, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = val ref = SimpleRef(param.sym)(param.sym.id) val unknown = UnknownValueShape(ref)(ShapeProvenance( (msg"Parameter '${param.sym.nme}' admits values of unknown shape." -> param.toLoc) :: path.diagnosticNotes)) - resolver.constrainParameter(param, unknown.enter(marks), marks) + resolver.parameterSignature(param) match + case N => resolver.constrainParameter(param, unknown.enter(marks), marks) + case S(tpe) => unknown.enter(marks) match + case value: TermShape => resolver.exposeTypeHoles(tpe.instantiate(rstate.instances), value, marks) + case NoShape => () private def parameters(lists: Ls[(ParamList, Ls[Marks])], path: Path)(using NewResolverState): Unit = lists.foreach: (params, marks) => @@ -161,6 +165,14 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) case _ => () case base: BaseShape => members(base.defn, marks, path) case callable: CallableTypeShape => + // Written structure constrains external values; missing output parts + // still admit unknown shapes even when local callers supplied evidence. + val unknown = UnknownValueShape(callable.source)(path) + callable.paramLists.foreach: params => + (params.params.flatten ::: params.rest.toList).foreach: input => + unknown.enter(marks) match + case value: TermShape => resolver.exposeTypeHoles(input, value, marks) + case NoShape => () callable.result.foreach: result => watch((result, marks))(resolver.listenTypeInstances(result)): shape => emit(shape.exit(marks), path) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index a392560290..a2e524aacf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -241,16 +241,80 @@ class NewResolver: declaredType(resolution, tpe) private def typeViews(using rs: NewResolverState) = rs.typeViews + /** Each missing formal position belongs to its source type use. Keeping one + * live host preserves late bounds without allocating a quantified parameter. + */ + private[semantics] def omittedType(source: TypeResolution, parameter: VarSymbol)(using NewResolverState): DeclaredType = + rstate.omittedTypes.getOrElseUpdate((source, parameter), { + val resolution = new TypeResolution(source.source, source.fail) + val bounds = new TermShapeHost + resolution.publish(TypeShape.Hole(bounds.inferenceHost)) + declaredType(resolution, Map.empty) + }) + + private def typeArguments(source: DeclaredType, arguments: Ls[DeclaredType], parameters: Ls[TyParam]) + (using NewResolverState): Ls[DeclaredType] = + parameters.zipWithIndex.map: (parameter, index) => + arguments.lift(index).getOrElse(omittedType(source.resolution, parameter.sym).instantiate(source.instances)) + + private def typeBindings(source: DeclaredType, arguments: Ls[DeclaredType], parameters: Ls[TyParam]) + (using NewResolverState): Map[VarSymbol, DeclaredType] = + effectiveBindings(source) ++ parameters.zip(typeArguments(source, arguments, parameters)).map: (parameter, argument) => + parameter.sym -> argumentType(argument, parameter.vce) + + /** External inputs contribute unknowns only to missing annotation parts. + * A written binder still denotes its declared type, including after a partial + * application fixes it; exposure must not add arbitrary values to that binder. + */ + private[semantics] def exposeTypeHoles(tpe: DeclaredType, unknown: TermShape, marks: Ls[Marks]) + (using NewResolverState): Unit = + if !rstate.exposedTypeHoles.add((tpe, unknown, marks)) then return + def follow(current: DeclaredType, args: Ls[DeclaredType], captures: Ls[Marks], seen: Set[TypeResolution]) + (using NewResolverState): Unit = if !seen(current.resolution) then + val next = seen + current.resolution + def descend(child: DeclaredType)(using NewResolverState): Unit = unknown.enter(captures) match + case value: TermShape => exposeTypeHoles(child, value, marks) + case NoShape => () + current.resolution.listen: + case TypeShape.Hole(host) => unknown.enter(captures) match + case value: TermShape => host.publish(value) + case NoShape => () + case TypeShape.Parameter(symbol, _) => + current.bindings.get(symbol).foreach(bound => follow(bound.instantiate(current.instances), Nil, captures, next)) + case TypeShape.Captured(base, thru) => + follow(declaredType(base, current), args, EntryMark(ResolutionBoundary(thru), N, NoMarks) :: captures, next) + case TypeShape.Applied(base, arguments) => + follow(declaredType(base, current), arguments.map(declaredType(_, current)), captures, next) + case TypeShape.Alias(symbol, S(rhs)) => + follow(declaredType(rhs, typeBindings(current, args, symbol.defn.get.tparams)), Nil, captures, next) + case TypeShape.Nominal(cls) => + cls.tparams.zip(typeArguments(current, args, cls.tparams)).foreach: (param, argument) => + listenArgumentParts(argumentType(argument, param.vce)): parts => + exposeTypeHoles(parts.output, unknown, marks) + case TypeShape.Tuple(fields) => fields.foreach(field => descend(declaredType(field, current))) + case TypeShape.Record(_, fields) => fields.foreach((_, field) => descend(declaredType(field, current))) + case TypeShape.Function(_, result) => descend(declaredType(result, current)) + case TypeShape.Polymorphic(parameters, _, body) => + val symbols = parameters.map(_.parameter.symbol) + val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances -- symbols) + follow(declaredType(body, lexical), args, captures, next) + case TypeShape.Wildcard(_, output) => output.foreach(res => follow(declaredType(res, current), args, captures, next)) + case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), args, captures, next) + case TypeShape.Union(left, right) => + follow(declaredType(left, current), Nil, captures, next) + follow(declaredType(right, current), Nil, captures, next) + case TypeShape.Intersection(left, right) => + follow(declaredType(left, current), Nil, captures, next) + follow(declaredType(right, current), Nil, captures, next) + case _ => () + follow(tpe, Nil, Nil, Set.empty) + private def abstractTypes(using rs: NewResolverState) = rs.abstractTypes - // An omitted argument in an annotation must not subscribe to constructor - // inference. Keep its parameter and annotation as a diagnostic witness: the - // enclosing nominal type is concrete even though this argument is unknown. - private def abstractType(source: TypeResolution, omitted: Opt[VarSymbol])(using NewResolverState): DeclaredType = abstractTypes.getOrElseUpdate((source, omitted), { + // Narrowing a nominal receiver by projection does not infer the narrower + // class's arguments. This abstract interface is distinct from an inference hole. + private def abstractType(source: TypeResolution)(using NewResolverState): DeclaredType = abstractTypes.getOrElseUpdate(source, { val resolution = new TypeResolution(source.source, source.fail) - resolution.publish(omitted match - case S(param) => TypeShape.Inferred(UnknownValueShape(source.source)(ShapeProvenance( - msg"Type argument '${param.nme}' is omitted in this annotation, so its shape is unknown." -> source.source.toLoc :: Nil))) - case N => TypeShape.Abstract) + resolution.publish(TypeShape.Abstract) declaredType(resolution, Map.empty) }) @@ -332,8 +396,7 @@ class NewResolver: def follow(current: DeclaredType, args: Ls[DeclaredType], aliases: Set[TypeResolution], publish: Listener)(using NewResolverState): Unit = current.resolution.listen: shape => def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = - effectiveBindings(current) ++ params.zipWithIndex.map: (param, index) => - param.sym -> argumentType(args.lift(index).getOrElse(abstractType(current.resolution, S(param.sym))), param.vce) + typeBindings(current, args, params) def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), Nil, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) @@ -343,6 +406,8 @@ class NewResolver: output.fold(publish(OpaqueTypeShape(tpe.resolution.source)))(next) case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), Nil, aliases, publish) case TypeShape.Inferred(value) => listenInstanceViews(instantiateShape(value, current.instances))(publish) + case TypeShape.Hole(host) => host.subscribe: value => + listenInstanceViews(instantiateShape(value, current.instances))(publish) case TypeShape.Nominal(defn) => val bindings = bind(defn.tparams) defn.ext match @@ -615,9 +680,12 @@ class NewResolver: def checkDeclaredResult(definition: TermDefinition)(using NewResolverState): Unit = definition.body.foreach: body => resultSignature(definition).foreach: sign => - val expected = declaredType(typeResolution(sign), Map.empty) - listenTerm(body): shape => - constrainFunction(expected, shape, Nil) + checkAscription(body, sign) + + def checkAscription(body: Term, sign: Term)(using NewResolverState): Unit = + val expected = declaredType(typeResolution(sign), Map.empty) + listenTerm(body): shape => + inferTypeArguments(expected.instantiate(rstate.instances), shape, Nil) // Install each constraint edge before subscribing: callback parameter/result // flow can revisit it immediately. Distinct instantiations retain their marks. @@ -666,9 +734,13 @@ class NewResolver: case TypeShape.Applied(base, params) => follow(declaredType(base, tpe), params.map(declaredType(_, tpe)), captures, next) case TypeShape.Alias(symbol, S(rhs)) => - val bindings = effectiveBindings(tpe) ++ symbol.defn.get.tparams.map(_.sym).zip(args) - follow(declaredType(rhs, bindings), Nil, captures, next) + follow(declaredType(rhs, typeBindings(tpe, args, symbol.defn.get.tparams)), Nil, captures, next) + case TypeShape.Hole(host) => value.enter(captures) match + case value: TermShape => host.publish(value) + case NoShape => () case TypeShape.Tuple(fields) => observe: + case unknown @ Marked(_: UnknownValueShape, _) => + fields.foreach(field => inferTypeArguments(declaredType(field, tpe), unknown, marks)) case Marked(tuple: TupleShape, context) => fields.zipWithIndex.foreach: (field, index) => tuple.getMember(index.toString) match @@ -685,7 +757,8 @@ class NewResolver: follow(declaredType(body, tpe), args, captures, next) case TypeShape.Wildcard(input, _) => input.foreach(res => follow(declaredType(res, tpe), args, captures, next)) case TypeShape.Argument(parts) => follow(parts.input.instantiate(tpe.instances), args, captures, next) - case TypeShape.Nominal(cls) if args.nonEmpty => listenInstanceViews(value): value => + case TypeShape.Nominal(cls) => listenInstanceViews(value): value => + val arguments = typeArguments(tpe, args, cls.tparams) val (actual, instances) = contextualParts(value) val (head, context) = actual.applicationHead def constrain(param: TyParam, bound: DeclaredType, pattern: DeclaredType)(using NewResolverState): Unit = @@ -694,10 +767,14 @@ class NewResolver: constrainTypes(ContextualType(actual.output, context), ContextualType(expected.output, Nil)) constrainTypes(ContextualType(expected.input, Nil), ContextualType(actual.input, context)) def constrainNominal(nominal: NominalInstanceView)(using NewResolverState): Unit = - if nominal.defn is cls then cls.tparams.zip(args).foreach: (param, pattern) => + if nominal.defn is cls then cls.tparams.zip(arguments).foreach: (param, pattern) => nominal.bindings.get(param.sym).foreach(constrain(param, _, pattern)) head match - case ds: DefnShape if ds.clsDef.contains(cls) => cls.tparams.zip(args).foreach: (param, pattern) => + case _: UnknownValueShape => + cls.tparams.zip(arguments).foreach: (param, argument) => + listenArgumentParts(argumentType(argument, param.vce)): parts => + inferTypeArguments(parts.output, value, marks) + case ds: DefnShape if ds.clsDef.contains(cls) => cls.tparams.zip(arguments).foreach: (param, pattern) => val bound = parameterType(TypeShape.Parameter(param.sym, param.sym.inferenceHost)) constrain(param, argumentType(bound, param.vce), pattern) case nominal: NominalInstanceView => constrainNominal(nominal) @@ -738,7 +815,9 @@ class NewResolver: case value: TermShape => receive(value) case NoShape => () case _ => () - member(actual.getMember(field.sym.nme), Map.empty) + actual match + case Marked(_: UnknownValueShape, _) => receive(actual) + case _ => member(actual.getMember(field.sym.nme), Map.empty) /** Function constraints reverse the parameter flow and preserve result flow. * Read an implementation only while checking it against its declared interface; @@ -798,6 +877,11 @@ class NewResolver: constrainRecord(record.fields, record.bindings, actual, marks) case _ => () actual match + case Marked(_: UnknownValueShape, _) => + listenTypeViews(expected): + case Marked(callable: CallableTypeShape, _) => + callable.result.foreach(ret => inferTypeArguments(ret, actual, marks)) + case _ => () case Marked(givenType: CallableTypeShape, actualContext) => listenTypeViews(expected): case Marked(wanted: CallableTypeShape, expectedContext) => @@ -1625,7 +1709,7 @@ class NewResolver: case N => // An explicit projection can name a narrower class, but // it cannot recover that class's implementation arguments. - val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs)), N) + val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation) case _ => info member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, Map.empty) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index aefd4b6853..f68e0eb47d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -252,8 +252,12 @@ final class NewResolverState private ( new Cache(inherited.map(_.extremeTypes), identity) val variantTypes: Cache[(DeclaredType, Bool), DeclaredType] = new Cache(inherited.map(_.variantTypes), identity) - val abstractTypes: Cache[(TypeResolution, Opt[VarSymbol]), DeclaredType] = + val abstractTypes: Cache[TypeResolution, DeclaredType] = new Cache(inherited.map(_.abstractTypes), identity) + val omittedTypes: Cache[(TypeResolution, VarSymbol), DeclaredType] = + new Cache(inherited.map(_.omittedTypes), identity) + val exposedTypeHoles: Seen[(DeclaredType, TermShape, Ls[Marks])] = + new Seen(inherited.map(_.exposedTypeHoles)) val signatureParameters: Cache[VarSymbol, DeclaredType] = new Cache(inherited.map(_.signatureParameters), identity) val capturedTypes: Cache[(DeclaredType, TermSymbol), DeclaredType] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index d3de942c1b..bb08a6084b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -23,6 +23,9 @@ enum TypeShape: // The same third-party symbol can have different inference in two exporters. // Retain its originating host so importing a result needs no whole-state copy. case Parameter(symbol: VarSymbol, host: Publisher.Data[Shape]) + // An omitted argument owns one source inference node. It is not a quantified + // binder and is never instantiated at a call site; marks distinguish its flows. + case Hole(host: Publisher.Data[TermShape]) // Synthesized generic arguments can retain inferred value shapes, for example // the element union of the Array supertype of a tuple. Written annotations // never introduce this case by inspecting their implementation. @@ -69,7 +72,19 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => shapes.foreach: case Alias(_, rhs) => rhs.foreach(_.validate(next)) case Captured(base, _) => base.validate(next) - case Applied(base, args) => base.validate(next); args.foreach(_.validate(next)) + case Applied(base, args) => + base.validate(next) + args.foreach(_.validate(next)) + def checkArity(base: TypeResolution, seen: Set[TypeResolution]): Unit = if !seen(base) then + def check(name: Str, count: Int, loc: Opt[Loc]): Unit = if args.length > count then + fail(msg"Type '$name' accepts at most $count type ${"argument".pluralized(count)}, but got ${args.length}." -> + source.toLoc :: (msg"The type parameters are declared here." -> loc) :: Nil) + base.shapes.toList match + case Nominal(defn) :: Nil => check(defn.sym.nme, defn.tparams.length, defn.toLoc) + case Alias(symbol, _) :: Nil => symbol.defn.foreach(d => check(symbol.nme, d.tparams.length, d.toLoc)) + case Captured(inner, _) :: Nil => checkArity(inner, seen + base) + case _ => () + checkArity(base, Set.empty) case Wildcard(input, output) => input.foreach(_.validate(next)); output.foreach(_.validate(next)) case Argument(parts) => parts.input.resolution.validate(next); parts.output.resolution.validate(next) case Function(_, result) => result.validate(next) diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 49d332190c..c62761f133 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -680,18 +680,24 @@ fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ l.678: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ╟── Type argument 'B' is omitted in this annotation, so its shape is unknown. +//│ ╟── Parameter 'x' admits values of unknown shape. //│ ║ l.678: fun omittedSecond(x: Pair[Int]) = x.value.1.oops -//│ ╙── ^^^^ +//│ ║ ^^^^^^^^^^^^ +//│ ╟── 'omittedSecond' is exposed by this compilation unit. +//│ ║ l.678: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // An unfilled omitted alias argument likewise cannot justify an arbitrary member. :ge type HalfPair[T] = Pair[Int, T] fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.690: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.693: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. -//│ ║ l.690: fun omittedAlias(x: HalfPair) = x.value.1.oops -//│ ╙── ^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.693: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ ^^^^^^^^^^^ +//│ ╟── 'omittedAlias' is exposed by this compilation unit. +//│ ║ l.693: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls new file mode 100644 index 0000000000..f47fcc05d7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls @@ -0,0 +1,184 @@ +:. + +// Each missing formal position and each annotation occurrence has its own hole. +:expect [1, 2, 3, 4] +class First(val first: Int) +class Second(val second: Int) +class Pair[A, B](val value: [A, B]) +private fun both(left: Pair, right: Pair) = + [left.value.0.first, left.value.1.second, right.value.0.second, right.value.1.first] +both(Pair([First(1), Second(2)]), Pair([Second(3), First(4)])) +//│ = [1, 2, 3, 4] + +// TODO: preserve distinct callers through a recursive hole reference. +// This hole must use ordinary inference, not fresh call-site parameter symbols. +:fixme +:expect [5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun append(xs: Array, value, n: Int) = + if n > 0 then append(xs, value, n - 1) else xs.push(value) +let xs = mut [] +let ys = mut [] +append(xs, First(5), 2) +append(ys, Second(6), 2) +[xs.0.first, ys.0.second] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.25: [xs.0.first, ys.0.second] +//│ ║ ^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.23: append(xs, First(5), 2) +//│ ╙── ^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.25: [xs.0.first, ys.0.second] +//│ ║ ^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.24: append(ys, Second(6), 2) +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[5, 6]', got: 'undefined' +//│ xs = [First(5)] +//│ ys = [Second(6)] + +// A fixed fragment still restricts the interface next to an inferred argument. +:ge +class Base +class Child(val extra: Int) extends Base +class Pair[A, B](val value: [A, B]) +private fun first(pair: Pair[Base]) = pair.value.0.extra +first(Pair([Child(7), 0])) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.48: private fun first(pair: Pair[Base]) = pair.value.0.extra +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// External inputs remain unknown even when local callers supply useful shapes. +:ge +class Item(val value: Int) +class Box[T](val item: T) +fun read(box: Box) = box.item.value +read(Box(Item(8))) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.60: fun read(box: Box) = box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'box' admits values of unknown shape. +//│ ║ l.60: fun read(box: Box) = box.item.value +//│ ║ ^^^^^^^^ +//│ ╟── 'read' is exposed by this compilation unit. +//│ ║ l.60: fun read(box: Box) = box.item.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Incomplete result annotations infer their holes from the implementation. +:expect 9 +class Item(val value: Int) +class Box[T](val item: T) +private fun make(): Box = Box(Item(9)) +make().item.value +//│ = 9 + +// An ascription preserves its written structure and infers its missing argument. +:expect 10 +class Item(val value: Int) +class Box[T](val item: T) +let box = Box(Item(10)) as Box +box.item.value +//│ = 10 +//│ box = Box(Item(10)) + +// TODO: omissions in an alias body need the alias use's contextual graph view. +:fixme +:expect [11, 12] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val item: T) +type SomeBox = Box +private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +both(Box(First(11)), Box(Second(12))) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.98: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.99: both(Box(First(11)), Box(Second(12))) +//│ ╙── ^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.98: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.99: both(Box(First(11)), Box(Second(12))) +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[11, 12]', got: 'undefined' + +// Only missing arguments are inferred; excess arguments remain invalid. +:ge +class Box[T](val item: T) +private fun excess(value: Box[Int, Str]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.118: private fun excess(value: Box[Int, Str]) = () +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Type 'Box' accepts at most 1 type argument, but got 2. +//│ ║ l.118: private fun excess(value: Box[Int, Str]) = () +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── The type parameters are declared here. +//│ ║ l.117: class Box[T](val item: T) +//│ ╙── ^ + +// A type alias checks its own argument list before interpreting the body. +:ge +class Box[T](val item: T) +type Alias[A] = Box[A] +private fun excess(value: Alias[Int, Str]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.133: private fun excess(value: Alias[Int, Str]) = () +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Type 'Alias' accepts at most 1 type argument, but got 2. +//│ ║ l.133: private fun excess(value: Alias[Int, Str]) = () +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── The type parameters are declared here. +//│ ║ l.132: type Alias[A] = Box[A] +//│ ╙── ^^^^^^ + +// Annotated results do not hide missing input types from exposure checking. +:ge +class Item(val value: Int) +class Box[T](val item: T) +fun read(box: Box): Int = box.item.value +read(Box(Item(13))) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.148: fun read(box: Box): Int = box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── 'read' is exposed by this compilation unit. +//│ ║ l.148: fun read(box: Box): Int = box.item.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// An external callback may supply an unknown value for its result's hole. +:ge +class Item(val value: Int) +class Box[T](val item: T) +fun read(callback: () -> Box) = callback().item.value +read(() => Box(Item(14))) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.163: fun read(callback: () -> Box) = callback().item.value +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'callback' admits values of unknown shape. +//│ ║ l.163: fun read(callback: () -> Box) = callback().item.value +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── 'read' is exposed by this compilation unit. +//│ ║ l.163: fun read(callback: () -> Box) = callback().item.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A result hole retains the call's explicit-binder view while observing its body. +:expect [15, 16] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val item: T) +private fun wrap[A](value: A): Box = Box(value) +[wrap(First(15)).item.first, wrap(Second(16)).item.second] +//│ = [15, 16] diff --git a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls index cb030c5f84..6bd9f4fa21 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls @@ -21,34 +21,17 @@ private fun apply[A](x: A, f) = f(x) [apply(First(5), x => x.first), apply(Second(6), x => x.second)] //│ = [5, 6] -// TODO: an omitted trailing type argument is a hole inferred from each caller. +// An omitted trailing type argument is a hole inferred from each caller. // The written Int argument and the Pair interface remain in place. -:fixme :expect [1, 2] class First(val first: Int) class Second(val second: Int) class Pair[A, B](val value: [A, B]) private fun second(pair: Pair[Int]) = pair.value.1 [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.32: [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'first' of a value with unknown shape. -//│ ╟── Type argument 'B' is omitted in this annotation, so its shape is unknown. -//│ ║ l.31: private fun second(pair: Pair[Int]) = pair.value.1 -//│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.32: [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'second' of a value with unknown shape. -//│ ╟── Type argument 'B' is omitted in this annotation, so its shape is unknown. -//│ ║ l.31: private fun second(pair: Pair[Int]) = pair.value.1 -//│ ╙── ^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '[1, 2]', got: 'undefined' +//│ = [1, 2] -// TODO: bare alias uses infer their omitted argument without losing the alias body. -:fixme +// Bare alias uses infer their omitted argument without losing the alias body. :expect [3, 4] class First(val first: Int) class Second(val second: Int) @@ -56,26 +39,10 @@ class Pair[A, B](val value: [A, B]) type HalfPair[T] = Pair[Int, T] private fun second(pair: HalfPair) = pair.value.1 [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.58: [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'first' of a value with unknown shape. -//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. -//│ ║ l.57: private fun second(pair: HalfPair) = pair.value.1 -//│ ╙── ^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.58: [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'second' of a value with unknown shape. -//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. -//│ ║ l.57: private fun second(pair: HalfPair) = pair.value.1 -//│ ╙── ^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '[3, 4]', got: 'undefined' +//│ = [3, 4] -// TODO: a bare Array annotation has an inferred element hole in both positions. +// A bare Array annotation has an inferred element hole in both positions. // This case has no declared type binder to instantiate at either call. -:fixme :expect [5, 6] class First(val first: Int) class Second(val second: Int) @@ -85,11 +52,7 @@ let ys = mut [] append(xs, First(5)) append(ys, Second(6)) [xs.0.first, ys.0.second] -//│ ╔══[COMPILATION ERROR] This selection of member 'first' has no resolved target -//│ ║ l.87: [xs.0.first, ys.0.second] -//│ ╙── ^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '[5, 6]', got: 'undefined' +//│ = [5, 6] //│ xs = [First(5)] //│ ys = [Second(6)] @@ -117,12 +80,12 @@ class Child extends Base with private fun read(x: Base) = x.item().value read(new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.117: private fun read(x: Base) = x.item().value -//│ ║ ^^^^^^^^^^^^^^ +//│ ║ l.80: private fun read(x: Base) = x.item().value +//│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.117: private fun read(x: Base) = x.item().value -//│ ╙── ^^^^^^ +//│ ║ l.80: private fun read(x: Base) = x.item().value +//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // A separate polymorphic signature retains binders for explicit type application. @@ -150,7 +113,7 @@ class Child extends Base with let identity = (x => x) as [A] -> A -> A identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.151: identity[Base](new Child).extra +//│ ║ l.114: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -161,10 +124,10 @@ identity[Base](new Child).extra let identity = (x => x) as [A] -> A -> A identity[Int, Int](1) //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.162: identity[Int, Int](1) +//│ ║ l.125: identity[Int, Int](1) //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Function with a declared signature expected 1 type argument, but got 2 -//│ ║ l.161: let identity = (x => x) as [A] -> A -> A +//│ ║ l.124: let identity = (x => x) as [A] -> A -> A //│ ╙── ^^^^^^ //│ = 1 //│ identity = fun identity diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index 81830d81b0..29f085f7b6 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -235,11 +235,14 @@ invokeUnknown(...opaque([() => 95])) //│ ║ l.232: fun invokeUnknown(f) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.173: fun opaque(values): Array = values -//│ ║ ^^^^^ -//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. -//│ ║ l.173: fun opaque(values): Array = values -//│ ╙── ^^^^^ +//│ ║ l.232: fun invokeUnknown(f) = f() +//│ ║ ^ +//│ ╟── Parameter 'f' admits values of unknown shape. +//│ ║ l.232: fun invokeUnknown(f) = f() +//│ ║ ^ +//│ ╟── 'invokeUnknown' is exposed by this compilation unit. +//│ ║ l.232: fun invokeUnknown(f) = f() +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 95 // Unknown elements must not silently resolve against another known candidate. @@ -248,19 +251,22 @@ fun inspectUnknown(x) = x.value inspectUnknown(Box(96)) inspectUnknown(...opaque([Box(96)])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.247: fun inspectUnknown(x) = x.value +//│ ║ l.250: fun inspectUnknown(x) = x.value //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. -//│ ║ l.173: fun opaque(values): Array = values -//│ ╙── ^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.250: fun inspectUnknown(x) = x.value +//│ ║ ^ +//│ ╟── 'inspectUnknown' is exposed by this compilation unit. +//│ ║ l.250: fun inspectUnknown(x) = x.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Too many known fields remain a proven mismatch even with an unknown spread. :e four(...opaque([]), 1, 2, 3, 4, 5) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.261: four(...opaque([]), 1, 2, 3, 4, 5) +//│ ║ l.267: four(...opaque([]), 1, 2, 3, 4, 5) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got at least 5 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -272,14 +278,17 @@ four(...opaque([]), 1, 2, 3, 4, 5) fun invokeRest(f, x, ...tail) = f() invokeRest(...opaque([() => 97]), Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.272: fun invokeRest(f, x, ...tail) = f() +//│ ║ l.278: fun invokeRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.173: fun opaque(values): Array = values -//│ ║ ^^^^^ -//│ ╟── Type argument 'T' is omitted in this annotation, so its shape is unknown. -//│ ║ l.173: fun opaque(values): Array = values -//│ ╙── ^^^^^ +//│ ║ l.278: fun invokeRest(f, x, ...tail) = f() +//│ ║ ^ +//│ ╟── Parameter 'f' admits values of unknown shape. +//│ ║ l.278: fun invokeRest(f, x, ...tail) = f() +//│ ║ ^ +//│ ╟── 'invokeRest' is exposed by this compilation unit. +//│ ║ l.278: fun invokeRest(f, x, ...tail) = f() +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 97 // When repeatedTuples receives [Box(98)], both a and b must acquire its element @@ -313,16 +322,16 @@ invokeTypedRest(...callbacks, Box(0)) fun invokePublicRest(f, x, ...tail) = f() invokePublicRest(...callbacks, Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.313: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.322: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.313: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.322: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.313: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.322: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── 'invokePublicRest' is exposed by this compilation unit. -//│ ║ l.313: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.322: fun invokePublicRest(f, x, ...tail) = f() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 97 @@ -358,7 +367,7 @@ class Child extends Base with fun readBase(xs: Array[Base]) = first(...xs).extra readBase([new Child]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.358: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ║ l.367: fun readBase(xs: Array[Base]) = first(...xs).extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -389,13 +398,13 @@ unknownCallbacks.push(() => 99) private fun invokeUnannotatedArray(f, ...tail) = f() invokeUnannotatedArray(...unknownCallbacks) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.389: private fun invokeUnannotatedArray(f, ...tail) = f() +//│ ║ l.398: private fun invokeUnannotatedArray(f, ...tail) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.390: invokeUnannotatedArray(...unknownCallbacks) +//│ ║ l.399: invokeUnannotatedArray(...unknownCallbacks) //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── This spread has no known element shape. -//│ ║ l.390: invokeUnannotatedArray(...unknownCallbacks) +//│ ║ l.399: invokeUnannotatedArray(...unknownCallbacks) //│ ╙── ^^^^^^^^^^^^^^^^ //│ = 99 //│ unknownCallbacks = [fun] @@ -406,16 +415,16 @@ invokeUnannotatedArray(...unknownCallbacks) private fun firstInArray(xs) = first(...xs) [firstInArray(lefts).left, firstInArray(rights).right] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.407: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.416: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Right' does not contain member 'left' -//│ ║ l.369: class Right(val right: Int) +//│ ║ l.378: class Right(val right: Int) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.407: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.416: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Left' does not contain member 'right' -//│ ║ l.368: class Left(val left: Int) +//│ ║ l.377: class Left(val left: Int) //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[43, 44]', got: 'undefined' From a442854cdb34cc1a9339c792ad3fa786979aa3ed Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 02:30:28 +0800 Subject: [PATCH 100/228] Instantiate constructor type parameters at application sites --- doc/new-resolution-design.md | 18 +- doc/new-resolution-suite-migration.md | 37 ++-- doc/new-resolution-type-value-flow.md | 70 +++++- .../scala/hkmc2/TypeInstantiationTest.scala | 53 ++++- .../scala/hkmc2/semantics/NewResolver.scala | 206 ++++++++++-------- .../hkmc2/semantics/NewResolverState.scala | 6 +- .../main/scala/hkmc2/semantics/Shape.scala | 3 +- .../mlscript/newres/ConstructorInstances.mls | 168 ++++++++++++++ .../src/test/mlscript/newres/SpreadCalls.mls | 17 +- 9 files changed, 423 insertions(+), 155 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 5aeb057e1b..d800eba876 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -39,10 +39,10 @@ application, and destructuring obtain specialized interfaces through adding an ordinary bound, the agreed variance rules, and the proposal to instantiate declared type parameters once per definition and syntactic call site are documented in [Instance types and parameter constraints](new-resolution-type-value-flow.md). -The description below records current generic inference, which still needs that -constraint extension. In particular, the implementation still treats omitted -generic arguments as unknown interfaces; the reviewed design makes them inference -holes and retains live inference for the missing parts of a partial signature. +The description below records current generic inference. Omitted generic arguments +use source-owned inference holes and retain live inference through partial +signatures. Recursive hole contexts, alias-body omissions, and missing nominal +member types still need the contextual reference work described in that document. Class bodies introduce lexical captures, and a class and its constructor share one resolution boundary. A method's reference to an outer constructor must include @@ -63,10 +63,16 @@ recursive calls, nested captures, partial construction, and independent field re Explicit and inferred function/constructor type arguments flow through the corresponding type-parameter symbols with entry/exit marks. Explicit arguments receive input constraints without adding value-argument shapes to their output -interface. Inference also connects nested +interface at ordinary call boundaries. Bodyless members of constructed instances +still use a positive-only conversion for supplied class arguments; the callback +input regression in `newres/ConstructorInstances.mls` records this limitation. +Inference also connects nested nominal parameters, such as `Foo[A]` containing a `Box[A]`. Generic methods use the same flow as free functions, including callback-result inference and curried -signatures. Declared callable shapes retain their type parameters. +signatures. Declared callable shapes retain their type parameters. Functions and +constructors allocate explicit binders once per original definition and syntactic +term application. Stored specializations and unapplied `new` retain supplied type +references until that application; subsequent curried lists retain its instance map. Nominal argument comparisons retain directed `ContextualType` endpoint pairs. Invariant arguments install both directions, rather than copying expanded diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index dc1ba602a3..6e6ebcdc9b 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -86,32 +86,21 @@ The inventory below separates these from compiler and prelude gaps. - **Mutation and control flow:** mutable-array reads use the element shapes collected from the initializer and subsequent writes, as described in the [resolver notes](new-resolution-design.md). Tracking individual positions and - lengths is out of scope. Decide how reassignment affects the inferred interface - of mutable storage, including across already compiled worksheet blocks. - `newres/MutationFlow.mls` records a reassigned array still checked - against its initializer's tuple length; accumulating both shapes would still - reject valid later indexing. `codegen/SetStmt` also lacks argument flow through - its update callback. Instance wrappers are in place; the - [type-argument constraint proposal](new-resolution-type-value-flow.md) records - the agreed variance rules and once-per-definition/call-site instantiation of - declared type parameters. Its design review specifies contextual views with - marks, partial signatures, and inference holes for omitted generic arguments. - The review also requires inferred missing member types through nominal - annotations, with receiver contexts and override constraints preserved. - Bidirectional nominal argument constraints now pass `appendTyped` and the - recursive cases in `newres/MutableArrays.mls`, including isolation between - callers. Stored specializations of inferred functions also preserve independent - call-site binders, including deferred records and curried calls. Complete - pre-application observations of specialized inferred results (the callback - regression in `newres/StoredSpecializations.mls`) and constructors. Omitted - arguments now infer through partial annotations, results, and ascriptions; - complete their recursive and alias-use contexts (`newres/InferenceHoles.mls`). - Verify the whole graph's termination bound. Member-variable - definitions still need work. Handler inference needs separate flows for the receiver, values + lengths is out of scope. Complete the + [type-argument constraint design](new-resolution-type-value-flow.md), including + specialized inferred results observed before application, supplied member-input + constraints, reconstructed receiver contexts, inferred missing member types, + and the whole graph's termination bound. Concrete outstanding cases are in + `newres/StoredSpecializations.mls`, `ConstructorInstances.mls`, + `InferenceHoles.mls`, and `PartialSignatures.mls`. + Decide how reassignment affects mutable storage's inferred interface, including + across compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned + array still checked against its initializer's tuple length; accumulating both + shapes would still reject valid later indexing. `codegen/SetStmt` also lacks + argument flow through its update callback. Member-variable definitions still + need work. Handler inference needs separate flows for the receiver, values passed to resumptions, and abortive results (`newres/HandlerResults.mls` and `codegen/ScopedBlocksAndHandlers`). Agree these designs before implementation. - Ordinary result-shape contracts are covered by `newres/ControlFlowResults.mls`; - `basics/MutVal` and `apps/IterTest` use new resolution. - **Captured activations:** preserve activation identity through captured functions, constructor aliases, reconstruction of the same class, and partial construction. Existing regressions include `CtxSens`, `ValCtxSens`, diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 2dcccf1c4d..b67118a7c1 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -18,10 +18,13 @@ substitutions through deferred tuples, records, callbacks, and closures. Nominal argument comparisons retain both endpoint references and apply declaration/use-site variance. The explicit-binder recursive array acceptance cases pass. Supplied function arguments receive input obligations while retaining their declared output interface. -Observing specialized inferred functions before application, replacing the remaining -explicit-argument flags and constructor path, inferred nominal member interfaces, -and general recursive alias environments still need integration. Omitted arguments -now use source-owned inference holes; recursive hole contexts and omissions inside +Constructors now select their binder groups at the first term application too, +including stored aliases and explicit `new`. Observing specialized inferred +functions before application, replacing the remaining explicit-argument flags +and positive-only member conversion, reconstructed receiver contexts, inferred +nominal member interfaces, and general recursive alias environments still need +integration. Omitted arguments use source-owned inference holes; recursive hole +contexts and omissions inside alias bodies still need the contextual reference work described below. The implementation order below remains the full design, not a claim that all its parts are complete. @@ -532,8 +535,44 @@ the original definition's type parameters. `newres/StoredSpecializations.mls` checks independent specializations through one stored alias, deferred record results, curried calls, and arity errors even when a specialization is unused. -Pre-application observation of an inferred result is still missing. The same -worksheet retains this concrete regression: +Constructors use the same `instantiateDefinition` operation as inferred functions. +`ClassReference` retains the selected class, marks, captured binder map, and supplied +type references through aliases and captures. `new C[T]` with parameter lists still +to consume retains a `NewShape` recipe; its first later term application owns the +binder group. A saturated zero-list `new C[T]` owns its group at the `new` site. +Later argument lists reuse the first group's map. Array spreads observe this +instantiated element parameter, including bounds inferred from inserted values. +`newres/ConstructorInstances.mls` checks independent aliases, explicit `new`, curried +and unapplied constructors, class/method binder separation, and a shared allocation +inside a generic function. Type application after the first consumed list cannot +rebind that scheme or turn a constructed object back into its constructor. + +Two constructor-related cases remain failing regressions in that worksheet. +Reconstruction with `class Box[T](val item: T) with { fun copy() = new Box[T](item) }` +must retain the receiver's view of `T` separately from the new constructor's view; +the first of two distinct receiver calls currently loses its resulting element +interface. Separately, this callback must receive the supplied input type: + +```mlscript +class Item(val value: Int) +let callbacks = new mut Array[Item -> Int](0) +callbacks.push((x) => x.value) +``` + +The expected input of `x` is `Item`, but its member target remains unresolved. +`instanceBindings` still expands explicitly supplied class arguments into +positive interfaces. Simply replacing this conversion with `TypeShape.Parameter` +is insufficient: nested nominal comparisons can reach that parameter with no +outer constraint marks, even though the incoming value retains class and member +entries. The explicit-argument flag then fails to identify the supplied slot and +incorrectly treats the input as another output candidate. The shared reference +graph must retain the supplied endpoint and its context through those comparisons; +it must replace this conversion and the flag-based routing together. Both failures +also occur before constructor call-site instantiation; they are not accepted +behavior or reasons to change marks or allocate additional variables. + +Pre-application observation of an inferred result is still missing. +`newres/StoredSpecializations.mls` retains this concrete regression: ```mlscript class Item(val value: Int) @@ -572,9 +611,11 @@ fields and closures as well as this scalar result. This regression remains a retaining their hosts, substitutions, and marks. Ordinary arguments contribute bounds; supplied types receive all obligations at their applicable polarity. Apply the InvalML variance rules above. -4. Replace `applyTypeArguments`, nominal inference, explicit-argument suppression, - and the positive-only conversion in `instanceBindings` together. Reuse the - same mechanism for functions, constructors, and declared array interfaces. +4. Complete supplied-argument references and replace explicit-argument suppression + and the positive-only conversion in `instanceBindings` together. Functions + and constructors now share call-site binder allocation and retain specialization + recipes; their remaining input/output obligations must use the same reference + mechanism as nominal arguments and declared array interfaces. 5. Route declared member lookup to partial member schemes, retaining receiver contexts for inferred fields and results. Check override compatibility before completing member targets. Convert the member-inference `:fixme`s together; @@ -634,6 +675,17 @@ are finitely many endpoint/map/mark combinations, and replay adds no listeners. That local bound does not establish the remaining whole-graph alias-environment bound, which must also prove that source references cannot proliferate. +Constructor recipes contain source type-argument nodes and flat binder maps, +not expanded argument candidates. There are finitely many such recipes per source +`new` expression. Their shape cache may distinguish recipes, but binder allocation +continues to use only the original class and term application site. Constructor +argument subscriptions are memoized by recipe and flat instance map before their +arguments are observed. `TypeInstantiationTest` checks zero allocation for bare +specialization and unapplied `new`, distinct groups for separate applications, +reuse across curried tails, and allocation for saturated zero-list construction. +These checks bound this constructor machinery; they do not establish the remaining +whole-graph alias bound or repair the contextual failures above. + For omitted arguments, each source type-use/formal-position pair allocates one `TypeShape.Hole` host. No recursive traversal or call allocates a parameter symbol for it. `TypeRelationTest` checks reuse across repeated observations, empty hosts diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala index 0b0cb23199..3a118797bc 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala @@ -7,18 +7,11 @@ import hkmc2.semantics.* class TypeInstantiationTest extends AnyFunSuite: - test("polymorphic declaration calls allocate by application rather than shared reference"): + private def allocatedInstances(source: String): Int = import io.PlatformPath.given val directory = os.temp.dir(prefix = "call-site-instances-") val file: io.Path = directory / "Library.mls" - os.write(directory / "Library.mls", - """|#lang(0.3.x, strictResolution: true) - |module Library with - | fun identity[A](x: A): A = x - | val shared = identity - | val first = shared(1) - | val second = shared("two") - |""".stripMargin) + os.write(directory / "Library.mls", "#lang(0.3.x, strictResolution: true)\n" + source) try val paths = TestFolders.compilerPaths(os.pwd) val compiler = CompilerCtx.fresh(io.FileSystem.default, paths, @@ -29,10 +22,50 @@ class TypeInstantiationTest extends AnyFunSuite: given Raise = diagnostic => fail(diagnostic.theMsg) val prelude = compiler.getPrelude(paths.preludeFile) val artifact = compiler.getElaboratedBlock(file, prelude.ctx) - assert(artifact.state.newResolverState.allocatedTypeInstanceCount == 2) assert(prelude.state.newResolverState.allocatedTypeInstanceCount == 0) + artifact.state.newResolverState.allocatedTypeInstanceCount finally os.remove.all(directory) + test("polymorphic declaration calls allocate by application rather than shared reference"): + assert(allocatedInstances( + """|module Library with + | fun identity[A](x: A): A = x + | val shared = identity + | val first = shared(1) + | val second = shared("two") + |""".stripMargin) == 2) + + test("constructor instances belong to the first term application, including stored new recipes"): + val cases = List( + """|class Box[T](val item: T) + |private val specialized = Box[Int] + |private val deferred = new Box[Int] + |""".stripMargin -> 0, + """|class Box[T](val item: T) + |private val shared = Box + |private val specialized = shared[Int] + |private val direct = specialized(1) + |private val explicit = new specialized(2) + |""".stripMargin -> 2, + """|class Box[T](val item: T) + |private val deferred = new Box[Int] + |private val first = deferred(1) + |private val second = deferred(2) + |""".stripMargin -> 2, + """|class Box[T](val item: T)(val other: Int) + |private val partial = new Box[Int](1) + |private val first = partial(2) + |private val second = partial(3) + |""".stripMargin -> 1, + """|class Box[T] with + | fun identity(x: T): T = x + |private val first = new Box[Int] + |private val second = new Box[Str] + |""".stripMargin -> 2) + cases.foreach: (source, count) => + withClue(source): + assert(allocatedInstances(source) == count) + test("a definition and static site allocate their binder group once"): given owner: Elaborator.State = new Elaborator.State val state = owner.newResolverState diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index a2e524aacf..fe8a175e0f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -616,25 +616,6 @@ class NewResolver: case value: TermShape => host.publish(value) case _ => () - private def applyTypeArguments(callee: DefnShape | CallableTypeShape, marks: Ls[Marks], args: Ls[Term], source: Term)(using NewResolverState): Unit = - def apply(params: Ls[DeclaredTypeParameter])(using NewResolverState): Unit = - if params.length != args.length then - resolError(source, msg"${callee.describe.capitalize} expected ${params.length} type ${ - "argument".pluralized(params.length)}, but got ${args.length}" -> callee.toLoc :: Nil) - bindTypeArguments(params.map(_.parameter), marks, - args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances))) - callee match - case callable: CallableTypeShape => apply(callable.tparams) - case defn: DefnShape => defn.clsDef match - case S(cls) => apply(cls.tparams.map(p => declaredParameter(p.sym))) - case N => defn.defn match - case td: TermDefinition if td.tparams.isEmpty && !td.flags.hasResultAnnotation && td.sign.nonEmpty => - listenTypeViews(declaredType(typeResolution(td.sign.get), Map.empty)): - case Marked(callable: CallableTypeShape, _) => apply(callable.tparams) - case _ => apply(Nil) - case td: TermDefinition => apply(td.tparams.toList.flatten.map(p => declaredParameter(p.sym))) - case _ => apply(Nil) - private def declaredParameter(symbol: VarSymbol)(using NewResolverState): DeclaredTypeParameter = DeclaredTypeParameter(TypeShape.Parameter(symbol, symbol.inferenceHost), N, N) @@ -1004,12 +985,13 @@ class NewResolver: val cls = prelude.builtins.Array.defn.get softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") val param = cls.tparams.head.sym - val Marked(value, _) = shape - // Explicit arguments belong to the allocation/application itself. Outer - // marks transport that instance through parameters; they must affect - // delivered element shapes, but not the lookup of its explicit arguments. - val (head, instanceContext) = value.applicationHead - val context = shape.applicationHead._2 + val (source, instances) = contextualParts(shape) + val Marked(value, _) = source + // Supplied and inferred element types share the allocation's parameter. + // Outer marks transport its bounds through callers without changing which + // parameter instance the allocation selected. + val head = value.applicationHead._1 + val context = source.applicationHead._2 def receive(element: TermShape)(using NewResolverState): Unit = element.exit(context) match case value: TermShape => listener(value) case NoShape => () @@ -1024,9 +1006,9 @@ class NewResolver: true case N => false case N => false - case defn: DefnShape if defn.clsDef.contains(cls) && shape.isInstanceOfClass(cls) - && rstate.hasExplicitTypeArgument(param, instanceContext) => - listenTypeArgument(param)(receive) + case defn: DefnShape if defn.clsDef.contains(cls) && source.isInstanceOfClass(cls) => + val parameter = instances.getOrElse(param, param) + listenTypeArgument(parameter)(receive) true case _ => false @@ -1385,6 +1367,36 @@ class NewResolver: def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern)(using NewResolverState): Unit = if newResolution then listenTerm(scrutinee)(sh => matchShapePat(sh, pattern)(_ => ())) + /** A definition's first term application selects its binder group. Both class + * interpretations use the original class owner; subsequent constructor lists + * retain the selected group through their contextual result shape. + */ + private def definitionParameters(definition: DefnShape): (AnyDefinitionSymbol, Ls[VarSymbol]) = + definition.clsDef match + case S(cls) => (cls.sym, cls.tparams.map(_.sym)) + case N => definition.defn match + case td: TermDefinition => (td.tsym, td.tparams.toList.flatten.map(_.sym)) + case _ => lastWords("A callable definition must have a class or term owner") + + private def instantiateDefinition(definition: DefnShape, site: FlowSymbol, marks: Ls[Marks], + captured: Map[VarSymbol, TypeParameterInstance], supplied: Opt[Ls[DeclaredType]]) + (using NewResolverState): Map[VarSymbol, TypeParameterInstance] = + val (owner, parameters) = definitionParameters(definition) + val substitution = captured ++ rstate.instantiateTypeParameters(owner, site, parameters) + supplied.foreach: arguments => + bindTypeArguments(parameters.map: parameter => + val instance = substitution(parameter) + TypeShape.Parameter(instance, instance.inferenceHost) + , marks, arguments) + if parameters.nonEmpty && rstate.inferredInstantiations.add((owner, site, substitution, marks)) then + parameters.foreach: parameter => + val instance = substitution(parameter) + listenTypeArgument(parameter): bound => + bound.applicationHead._1 match + case _: RigidTypeShape => () + case _ => inferTypeArguments(instanceType(instance), instantiateShape(bound, substitution), marks) + substitution + def appShape(source: TermShape, args: Term, res: App)(using NewResolverState): Unit = val callerInstances = rstate.instances val (original, outer) = contextualParts(source) @@ -1397,26 +1409,10 @@ class NewResolver: case _ => (original, outer, N) val captured = callerInstances ++ lexical val instances = lhs match - case Marked(definition: DefnShape, _) => definition.defn match - case td: TermDefinition if !td.tsym.isInstanceOf[ClassCtorSymbol] && td.tparams.exists(_.nonEmpty) => - val marks = lhs.applicationHead._2 - val parameters = td.tparams.get.map(_.sym) - val substitution = captured ++ rstate.instantiateTypeParameters(td.tsym, res.resSym, parameters) - supplied.foreach: arguments => - bindTypeArguments(parameters.map: parameter => - val instance = substitution(parameter) - TypeShape.Parameter(instance, instance.inferenceHost) - , marks, arguments) - if rstate.inferredInstantiations.add((td.tsym, res.resSym, substitution, marks)) then - parameters.foreach: parameter => - val instance = substitution(parameter) - if rstate.hasExplicitTypeArgument(parameter, marks) then rstate.markExplicitTypeArgument(instance, marks) - listenTypeArgument(parameter): bound => - bound.applicationHead._1 match - case _: RigidTypeShape => () - case _ => inferTypeArguments(instanceType(instance), instantiateShape(bound, substitution), marks) - substitution - case _ => captured + case Marked(definition: DefnShape, _) => + instantiateDefinition(definition, res.resSym, lhs.applicationHead._2, captured, supplied) + case Marked(construction: NewShape, _) if construction.argss.isEmpty && !construction.isSaturated => + instantiateDefinition(construction.receiver, res.resSym, lhs.applicationHead._2, captured, construction.supplied) case _ => captured def publish(value: TermShape)(using NewResolverState): Unit = publishViewed(res, value, instances)(using rstate.withInstances(callerInstances)) @@ -1625,29 +1621,49 @@ class NewResolver: /** Class interpretations wait for completed overload sets, independently of term * companions. Aliases can supply constructor shapes; applied instances cannot. * Capture marks are retained for subsequent instance-member lookup. */ - private def listenClass(trm: Term)(selected: (ClassDef, Ls[Marks]) => NewResolverState ?=> Unit, reject: ShapeListener[Shape])(using NewResolverState): Unit = - def select(cls: ClassDef, marks: Ls[Marks])(using NewResolverState): Unit = + private case class ClassReference(definition: ClassDef, marks: Ls[Marks], + instances: Map[VarSymbol, TypeParameterInstance], supplied: Opt[Ls[DeclaredType]]) + + private def listenClass(trm: Term)(selected: ShapeListener[ClassReference], reject: ShapeListener[Shape])(using NewResolverState): Unit = + def select(ref: ClassReference)(using NewResolverState): Unit = trm.classHead match - case ref: NewResolvable => - rstate.recordResolution(ref, ref.resolvedTargets.contains(cls.sym))(ref.resolvedTargets ::= cls.sym) + case target: NewResolvable => + rstate.recordResolution(target, target.resolvedTargets.contains(ref.definition.sym))( + target.resolvedTargets ::= ref.definition.sym) case _ => () - selected(cls, marks) - def value(sh: TermShape)(using NewResolverState): Unit = sh match - case Marked(ds: DefnShape, marks) => ds.defn match - case cls: ClassDef => select(cls, marks :: Nil) - case td: TermDefinition => td.tsym match - case ctor: ClassCtorSymbol => select(ctor.associatedCls.defn.get, marks :: Nil) + selected(ref) + def value(sh: TermShape)(using NewResolverState): Unit = + val (source, instances) = contextualParts(sh) + val marks = source.applicationHead._2 + def fromDefinition(ds: DefnShape, supplied: Opt[Ls[DeclaredType]], captured: Map[VarSymbol, TypeParameterInstance]): Unit = + ds.clsDef match + case S(cls: ClassDef) => select(ClassReference(cls, marks, instances ++ captured, supplied)) case _ => reject(sh) + source match + case Marked(ds: DefnShape, _) => fromDefinition(ds, N, Map.empty) + case Marked(specialized: SpecializedShape, _) => + fromDefinition(specialized.declaration, S(specialized.arguments), specialized.instances) case _ => reject(sh) - case _ => reject(sh) trm match - case TyApp(base, _) => listenClass(base)(select, reject) + case application @ TyApp(base, args) => listenClass(base)(ref => + val count = if ref.supplied.nonEmpty then 0 else ref.definition.tparams.length + if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then + resolError(trm, msg"Class '${ref.definition.sym.nme}' expected ${count} type ${ + "argument".pluralized(count)}, but got ${args.length}" -> ref.definition.toLoc :: Nil) + val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) + select(ref.copy(supplied = ref.supplied.orElse(S(supplied)))) + , reject) case Capture(base, thru) => - listenClass(base)((cls, marks) => select(cls, marks ::: EntryMark(ResolutionBoundary(thru), N, NoMarks) :: Nil), reject) + listenClass(base)(ref => select(ref.copy(marks = + ref.marks ::: EntryMark(ResolutionBoundary(thru), N, NoMarks) :: Nil)), reject) case _ => listen(trm): case sh: SymShape => - completedClass(sh)(cls => select(cls, - ExitMark(ResolutionBoundary(cls.sym), S(sh.resSym), NoMarks) :: sh.markss), + completedClass(sh)(cls => select(ClassReference(cls, + ExitMark(ResolutionBoundary(cls.sym), S(sh.resSym), NoMarks) :: sh.markss, + sh match + case contextual: ContextualSymShape => contextual.instances + case _ => Map.empty + , N)), () => fromSymbol(sh, value, trm, _ => (), false)) case sh: TermShape => value(sh) @@ -1687,7 +1703,9 @@ class NewResolver: log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc, Map.empty) case S(cls) => - listenClass(cls)((cd, marks) => + listenClass(cls)(ref => + val cd = ref.definition + val marks = ref.marks val candidate = cd.sym -> marks rstate.recordResolution(sel, sel.resolvedClasses.contains(candidate))(sel.resolvedClasses ::= candidate) listenExt(cd, ext => @@ -1753,20 +1771,23 @@ class NewResolver: // listenClass preserves captures and supplies the same class-body exit as a // constructor value. In particular, a captured constructor already carries // that exit; adding a second one here would duplicate its instance boundary. - listenClass(nw.cls)((cd, marks) => + listenClass(nw.cls)(ref => + val cd = ref.definition + val marks = ref.marks + val callerInstances = rstate.instances listenExt(cd, extsh => val dsh = constructorShape(cd, extsh) - val sh = newShapes.getOrElseUpdate((cd.sym, marks, nw.resSym), { - def typeArgs(term: Term)(using NewResolverState): Opt[Ls[Term]] = term match - case TyApp(_, args) => S(args) - case Capture(base, _) => typeArgs(base) - case _ => N - typeArgs(nw.cls).foreach(applyTypeArguments(dsh, marks, _, nw)) + val captured = callerInstances ++ ref.instances + // An unapplied constructor is a recipe. Its first later application + // selects the group; a saturated zero-list construction selects it here. + val instances = if nw.args.isEmpty && dsh.unappliedParams.nonEmpty then captured + else instantiateDefinition(dsh, nw.resSym, marks, captured, ref.supplied) + val sh = newShapes.getOrElseUpdate((cd.sym, marks, nw.resSym, ref.supplied), + NewShape(dsh, cd.sym, marks, nw.args, nw, ref.supplied)) + if rstate.constructorApplications.add((sh, instances)) then dsh.unappliedParams.lazyZip(nw.args).foreach: - case ((ps, _), args) => zipArgs(marks, ps.params, ps.restParam, args, nw, dsh) - NewShape(dsh, cd.sym, marks, nw.args, nw) - }) - publishViewed(nw, sh, rstate.instances) + case ((ps, _), args) => zipArgsIn(marks, ps.params, ps.restParam, args, nw, dsh, instances) + publishViewed(nw, sh, instances)(using rstate.withInstances(callerInstances)) ) , shape => if !rstate.hasError(nw) then @@ -1826,13 +1847,14 @@ class NewResolver: */ private def instanceBindings(td: TermDefinition, marks: Ls[Marks])(using NewResolverState): Map[VarSymbol, DeclaredType] = td.tsym.owner.toList.flatMap(_.asDefnSym.defn.toList).flatMap(_.tparams).map: param => - val explicit = rstate.hasExplicitTypeArgument(param.sym, marks) - val bound = rstate.instanceParameterTypes.getOrElseUpdate((param.sym, explicit), { + val symbol = rstate.instances.getOrElse(param.sym, param.sym) + val explicit = rstate.hasExplicitTypeArgument(symbol, marks) + val bound = rstate.instanceParameterTypes.getOrElseUpdate((symbol, explicit), { val source = SimpleRef(param.sym)(param.sym.id) val resolution = new TypeResolution(source, messages => resolError(source, messages)) - if explicit then listenTypeArgument(param.sym): value => + if explicit then listenTypeArgument(symbol): value => resolution.publish(TypeShape.Inferred(value)) - else resolution.publish(TypeShape.Parameter(param.sym, param.sym.inferenceHost)) + else resolution.publish(TypeShape.Parameter(symbol, symbol.inferenceHost)) declaredType(resolution, Map.empty) }) param.sym -> bound @@ -2068,17 +2090,21 @@ class NewResolver: case value: TermShape => listener(value) case NoShape => () case (callee: DefnShape, marks) => - callee.defn match - case td: TermDefinition if !td.tsym.isInstanceOf[ClassCtorSymbol] => - val parameters = td.tparams.toList.flatten - checkArity(callee, parameters.length) - val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) - SpecializedShape(callee, supplied, captured).exit(marks) match - case specialized: TermShape => listener(specialized) - case NoShape => () - case _ => - applyTypeArguments(callee, marks, args, trm) - listener(value) + val (_, parameters) = definitionParameters(callee) + // The first consumed parameter list has already bound this scheme. + // Looking through an application's head must not turn its result + // (especially a constructed object) back into a generic function. + val unapplied = actual match + case Marked(_: DefnShape, _) => true + case Marked(construction: NewShape, _) => construction.argss.isEmpty && !construction.isSaturated + case _ => false + checkArity(callee, if unapplied then parameters.length else 0) + if !unapplied then listener(value) + else + val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) + SpecializedShape(callee, supplied, captured).exit(marks) match + case specialized: TermShape => listener(specialized) + case NoShape => () case _ => listener(value) shape match case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => (), false) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index f68e0eb47d..c73cb41988 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -190,8 +190,10 @@ final class NewResolverState private ( new Cache(inherited.map(_.reportedArities), _.clone()) val appShapes: Cache[(TermShape, FlowSymbol), AppShape] = new Cache(inherited.map(_.appShapes), identity) - val newShapes: Cache[(ClassLikeSymbol, Ls[Marks], FlowSymbol), NewShape] = + val newShapes: Cache[(ClassLikeSymbol, Ls[Marks], FlowSymbol, Opt[Ls[DeclaredType]]), NewShape] = new Cache(inherited.map(_.newShapes), identity) + val constructorApplications: Seen[(NewShape, Map[VarSymbol, TypeParameterInstance])] = + new Seen(inherited.map(_.constructorApplications)) val introShapes: Cache[Identity[IntroTerm], IntroShape] = new Cache(inherited.map(_.introShapes), identity) val symShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = @@ -214,7 +216,7 @@ final class NewResolverState private ( new Cache(inherited.map(_.shapeViews), identity) val activatedSymbols: Cache[(SymShape, Map[VarSymbol, TypeParameterInstance]), ActivatedSymShape] = new Cache(inherited.map(_.activatedSymbols), identity) - val inferredInstantiations: Seen[(TermSymbol, FlowSymbol, Map[VarSymbol, TypeParameterInstance], Ls[Marks])] = + val inferredInstantiations: Seen[(AnyDefinitionSymbol, FlowSymbol, Map[VarSymbol, TypeParameterInstance], Ls[Marks])] = new Seen(inherited.map(_.inferredInstantiations)) // A scheme is owned by its source definition, or by the original interpretation // of an anonymous quantified annotation. Neither a view nor an instance is an owner. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 086996dc3c..b158e14b28 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -335,7 +335,8 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using override def toString: String = s"AppShape($receiver, ${args.showDbg})" // def target: Opt[AppTarget] -class NewShape(val receiver: DefnShape, val cls: ClassLikeSymbol, val clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New)(using DebugPrinter) extends NonMarkedShape: +class NewShape(val receiver: DefnShape, val cls: ClassLikeSymbol, val clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New, + val supplied: Opt[Ls[DeclaredType]])(using DebugPrinter) extends NonMarkedShape: protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = if isSaturated then receiver.getInstanceMember(name).withMarks(clsMarks) else MemberLookup.Missing diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls new file mode 100644 index 0000000000..d62bd1dcba --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls @@ -0,0 +1,168 @@ +:. + +// Specializing one stored constructor must not mutate its original parameters. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val item: T) +private val shared = Box +private val first = shared[First] +private val second = shared[Second] +[first(First(1)).item.first, second(Second(2)).item.second] +//│ = [1, 2] +//│ first = fun Box { class: class Box } +//│ second = fun Box { class: class Box } +//│ shared = fun Box { class: class Box } + +// Explicit new uses the same specialization recipe through a stored alias. +:expect [3, 4] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val item: T) +private val shared = Box +private val first = shared[First] +private val second = shared[Second] +[(new first(First(3))).item.first, (new second(Second(4))).item.second] +//│ = [3, 4] +//│ first = fun Box { class: class Box } +//│ second = fun Box { class: class Box } +//│ shared = fun Box { class: class Box } + +// Later constructor lists retain the instances chosen by the first list. +:expect [5, 6, 7, 8] +class First(val first: Int) +class Second(val second: Int) +class Pair[T](val item: T)(val other) +private val shared = Pair +private val left = shared[First](First(5)) +private val right = shared[Second](Second(7)) +[left(Second(6)).item.first, left(Second(6)).other.second, + right(First(8)).item.second, right(First(8)).other.first] +//│ = [5, 6, 7, 8] +//│ left = fun +//│ right = fun +//│ shared = fun Pair { class: class Pair } + +// An unapplied new expression keeps supplied types until its first application. +:expect [9, 10] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val item: T) +private val first = new Box[First] +private val second = new Box[Second] +[first(First(9)).item.first, second(Second(10)).item.second] +//│ = [9, 10] +//│ first = fun +//│ second = fun + +// Class and method binders retain independent views of the receiver and argument. +:expect [11, 12] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val item: T) with + fun pair[U](other: U) = [item, other] +let box = new Box[First](First(11)) +[box.pair(Second(12)).0.first, box.pair(Second(12)).1.second] +//│ = [11, 12] +//│ box = Box(First(11)) + +// The receiver's Array parameter must not specialize a literal in a call argument. +:expect 14 +class First(val first: Int) +class Second(val second: Int) +let xs = new mut Array[First](0) +xs.push(First(13)) +xs.map((item, ...) => (mut [Second(14)]).0.second).0 +//│ = 14 +//│ xs = [First(13)] + +// A shared allocation site retains the enclosing function's distinct activations. +:expect [15, 16] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val item: T) +private fun boxed[A](value: A) = new Box[A](value) +[boxed(First(15)).item.first, boxed(Second(16)).item.second] +//│ = [15, 16] + +// Reconstructing a receiver's class retains the supplied argument's outer view. +:fixme +:expect [17, 18] +class First(val first: Int) +class Second(val second: Int) +class Box[T](val item: T) with + fun copy() = new Box[T](item) +let first = new Box[First](First(17)) +let second = new Box[Second](Second(18)) +[first.copy().item.first, second.copy().item.second] +//│ ╔══[COMPILATION ERROR] This selection of member 'first' has no resolved target +//│ ║ l.97: [first.copy().item.first, second.copy().item.second] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[17, 18]', got: 'undefined' +//│ first = Box(First(17)) +//│ second = Box(Second(18)) + +// Specialization checks its arity without waiting for a constructor application. +:e +:ge +class Box[T](val item: T) +private val invalid = Box[Int, Str] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.110: private val invalid = Box[Int, Str] +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Class 'Box' expected 1 type argument, but got 2 +//│ ║ l.109: class Box[T](val item: T) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ invalid = fun Box { class: class Box } + +// Supplied member types must eventually receive callback input obligations too. +:fixme +:expect 19 +class Item(val value: Int) +let callbacks = new mut Array[Item -> Int](0) +callbacks.push((x) => x.value) +private fun first(callback, ...tail) = callback +first(...callbacks)(Item(19)) +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.124: callbacks.push((x) => x.value) +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '19', got: 'undefined' +//│ callbacks = [fun] + +// Type application cannot turn a constructed object back into its constructor. +:e +:re +class Box[T](val item: T) +let box = new Box[Int](1) +box[Str]("bad") +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.139: box[Str]("bad") +//│ ║ ^^^^^^^^ +//│ ╟── Class 'Box' expected 0 type arguments, but got 1 +//│ ║ l.137: class Box[T](val item: T) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.139: box[Str]("bad") +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Box' cannot receive more argument lists. +//│ ║ l.138: let box = new Box[Int](1) +//│ ╙── ^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] TypeError: box1 is not a function +//│ box = Box(1) + +// A partial constructor has already bound its type parameters at its first list. +:e +:ge +class Pair[T](val item: T)(val other: T) +private val partial = Pair[Int](1) +private val invalid = partial[Str] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.160: private val invalid = partial[Str] +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Class 'Pair' expected 0 type arguments, but got 1 +//│ ║ l.158: class Pair[T](val item: T)(val other: T) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ invalid = fun +//│ partial = fun diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index 29f085f7b6..a1d12eaaa3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -391,21 +391,12 @@ rights.push(Right(44)) //│ lefts = [Left(43)] //│ rights = [Right(44)] -// An unannotated allocation cannot borrow another array's element type or its pushed values. -:e +// An unannotated allocation infers its own element type from pushed values. +:expect 99 let unknownCallbacks = new mut Array(0) unknownCallbacks.push(() => 99) private fun invokeUnannotatedArray(f, ...tail) = f() invokeUnannotatedArray(...unknownCallbacks) -//│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.398: private fun invokeUnannotatedArray(f, ...tail) = f() -//│ ║ ^^^ -//│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.399: invokeUnannotatedArray(...unknownCallbacks) -//│ ║ ^^^^^^^^^^^^^^^^ -//│ ╟── This spread has no known element shape. -//│ ║ l.399: invokeUnannotatedArray(...unknownCallbacks) -//│ ╙── ^^^^^^^^^^^^^^^^ //│ = 99 //│ unknownCallbacks = [fun] @@ -415,13 +406,13 @@ invokeUnannotatedArray(...unknownCallbacks) private fun firstInArray(xs) = first(...xs) [firstInArray(lefts).left, firstInArray(rights).right] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.416: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.407: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Right' does not contain member 'left' //│ ║ l.378: class Right(val right: Int) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.416: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.407: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Left' does not contain member 'right' //│ ║ l.377: class Left(val left: Int) From cc8f635722d50c72975987955e9b9565e8072359 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 02:47:52 +0800 Subject: [PATCH 101/228] Record recursive type-graph termination failures and scope proposal --- doc/new-resolution-suite-migration.md | 2 +- doc/new-resolution-type-value-flow.md | 54 +++++++++++++++++++ .../mlscript/newres/TypeGraphTermination.mls | 23 ++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 6e6ebcdc9b..3cafa984a9 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -92,7 +92,7 @@ The inventory below separates these from compiler and prelude gaps. constraints, reconstructed receiver contexts, inferred missing member types, and the whole graph's termination bound. Concrete outstanding cases are in `newres/StoredSpecializations.mls`, `ConstructorInstances.mls`, - `InferenceHoles.mls`, and `PartialSignatures.mls`. + `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. Decide how reassignment affects mutable storage's inferred interface, including across compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned array still checked against its initializer's tuple length; accumulating both diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index b67118a7c1..0db3d57a39 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -629,6 +629,60 @@ references), `NewResolverState.scala` (consumer-owned memo tables), (transporting views through deferred children). Elaborator signature registration must preserve binders and missing positions before the body is observed. +### Type-reference scope audit (proposal requiring review) + +The remaining graph work has a concrete termination regression in +`newres/TypeGraphTermination.mls`: + +```mlscript +type Chain[A] = {value: A, next: Chain[Array[A]]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () +``` + +Compilation currently overflows the stack, even without a call to `walk`. +The variant whose `next` is simply `Chain[A]` fails too. Enabling the existing +`checkMarkPaths` assertions exposes repeated exits before the stack overflows. +Thus this failure is not evidence only of growing argument environments: +value-flow paths themselves violate the finite-mark premise. Even the finite selection `chain.next.next.value` in a function with a written +`Int` result currently overflows; that third regression needs no recursive call. +Both constraint expansion and mark paths need validation, independently of runtime +recursion depth. + +The proposed correction distinguishes these operations: + +- Expanding a type alias substitutes references to its arguments in its source + type graph. The alias declaration introduces no value invocation. Keep each + argument's caller context; do not add a value entry/exit for the alias's own + lexical qualification. +- Selecting a structural record type's field follows its written type reference. + `RcdField.make` creates a synthetic captured definition for value-field lookup; + that synthetic value boundary must not be introduced by a type-field projection. + Keep the field symbol as the resolved selection target. +- Function and class captures still cross actual value scopes and use the existing + mark operations. These paths must remain on references to captured parameters, + even when aliases or structural fields forward them. + +This is a proposal about which operations cross a value scope, not permission to +truncate repeated marks, widen an alias, or allocate more inference variables. +It must be reviewed before changing the type interpreter. Validation must include +nested aliases inside generic functions and classes, fields containing callbacks, +separate uses of one recursive alias, and imported annotations. All three +regression blocks must eventually pass without `:fixme`, with mark checks enabled and +stable relation/listener counts. Fixing the mark paths alone is insufficient if +`DeclaredType.bindings` can still construct unbounded nested environments. + +The same graph audit must distinguish an open definition template from an already +interpreted reference. In `Box[T].copy() = new Box[T](item)`, the source `T` in the +supplied argument belongs to the receiver, while the constructed result refers to +the new site's instance of `T`. One merged map cannot choose both meanings after +the argument reference has been interpreted in the wrong view. Observe saved +source-graph operations with their caller references before attaching the callee's +instance map. Do not implement this by rerunning `resolveNew` over arbitrary syntax: +legacy constructions can have value shapes without a new-resolution producer, +and completed reference targets must remain immutable. A saved graph operation +must retain its selected definition and interpreted argument references. + ### Fixed points and implementation checks There are at most as many allocated parameter instances as the sum of each diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls new file mode 100644 index 0000000000..ea7874b546 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -0,0 +1,23 @@ +:. + +// A finite observation of a recursive alias has a finite declared interface. +// It must also preserve the no-repeated-boundary invariant when mark checks are enabled. +:fixme +type Chain[A] = {value: A, next: Chain[A]} +fun read(chain: Chain[Int]): Int = chain.next.next.value +//│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// Relating a recursive structural alias must reach a graph fixed point. +:fixme +type Chain[A] = {value: A, next: Chain[A]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () +//│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// Changing recursive arguments must retain symbolic edges, not grow mark paths +// or expand Array[Array[...]] environments at each recursive comparison. +:fixme +type Chain[A] = {value: A, next: Chain[Array[A]]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () +//│ /!!!\ Uncaught error: java.lang.StackOverflowError From 83cdaaa18495c4fde21abbc5a6ae2e3afe772259 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 03:13:19 +0800 Subject: [PATCH 102/228] Clarify type-flow design review and nested variance regressions --- doc/new-resolution-suite-migration.md | 6 +- doc/new-resolution-type-value-flow.md | 92 ++++++++++++++++++- .../mlscript/newres/VarianceSubstitution.mls | 76 +++++++++++++++ 3 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 3cafa984a9..f9ba4c59db 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -90,9 +90,11 @@ The inventory below separates these from compiler and prelude gaps. [type-argument constraint design](new-resolution-type-value-flow.md), including specialized inferred results observed before application, supplied member-input constraints, reconstructed receiver contexts, inferred missing member types, - and the whole graph's termination bound. Concrete outstanding cases are in + occurrence-sensitive variance substitution, and the whole graph's termination + bound. Concrete outstanding cases are in `newres/StoredSpecializations.mls`, `ConstructorInstances.mls`, - `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. + `InferenceHoles.mls`, `PartialSignatures.mls`, `VarianceSubstitution.mls`, and + `TypeGraphTermination.mls`. Decide how reassignment affects mutable storage's inferred interface, including across compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned array still checked against its initializer's tuple length; accumulating both diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 0db3d57a39..3b620e2008 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -6,6 +6,42 @@ nominal-only representation at annotation boundaries: parameter and result annotations, ascriptions, generic arguments, and annotated tuple fields retain a type reference until an operation requests its interface. +## Review outcome + +The following semantic decisions are settled: + +- Wrap instance values, retaining references to their annotated types. Supplying + a type argument affects both its input and output uses; an ordinary value + argument contributes a lower bound. +- Instantiate explicit binders once per original definition and syntactic term + application. Reuse those symbols during recursion and retain marks to separate + enclosing activations of the same site. +- Infer every missing signature part through ordinary marked flow, including + holes within annotations and omitted generic arguments. These positions do not + introduce quantified binders or receive fresh symbols at calls. +- Apply the same rule to selected members' missing parameter, result, and field + types through nominal annotations. Retain the receiver context and the selected + declaration's member set; check overrides against that interface. +- Follow InvalML's declaration/use-site variance and occurrence-sensitive + substitution, including nested function types. +- Mutable arrays collect element shapes in their element type parameter under + marks. Tracking positions and lengths is outside this design. + +The implementation plan is in [constraint propagation and implementation +order](#constraint-propagation-and-implementation-order). Two representation +proposals still require review: [supplied type references in deferred +views](#partial-application-and-explicit-specialization), and [the distinction +between type-reference operations and value-scope crossings](#type-reference-scope-audit-proposal-requiring-review). +They must preserve caller and receiver contexts without recursively growing +binding environments. The variance substitution rules below are settled +semantics; their integration into those views remains implementation work. + +The finite call-site symbol bound alone is not a termination proof for the whole +resolver. Recursive alias regressions currently overflow, and the [fixed-point +conditions](#fixed-points-and-implementation-checks) remain completion gates. + +## Implementation status + Instance wrappers and preservation of quantified signature binders and bounds are implemented. Explicit type application also observes annotated polymorphic values. Calls through complete callable signatures use the bounded binder-instance @@ -16,7 +52,8 @@ inferred functions retain supplied arguments until application. Inline binders o functions also instantiate at application sites. Their shared bodies retain flat substitutions through deferred tuples, records, callbacks, and closures. Nominal argument comparisons retain both endpoint references and apply declaration/use-site -variance. The explicit-binder recursive array acceptance cases pass. Supplied function arguments +variance, with the nested substitution gap described below. The explicit-binder +recursive array acceptance cases pass. Supplied function arguments receive input obligations while retaining their declared output interface. Constructors now select their binder groups at the first term application too, including stored aliases and explicit `new`. Observing specialized inferred @@ -181,6 +218,59 @@ the same source binder can then resolve back to its own wrapper. Reusing the argument reference and normalizing repeated synthesized wrappers bounds this construction. The recursive covariant `Tree` regression exercises this invariant. +### Substitute at the occurrence before applying argument variance + +There are two distinct steps in InvalML's `typeAndSubstType`: + +1. Interpret an argument expression at the occurrence's polarity. A reference to + a parameter bound to `in L out U` selects `U` at positive polarity and `L` at + negative polarity. Function domains and written wildcard input parts reverse + this polarity; function results and wildcard output parts preserve it. +2. For an unqualified nominal argument, apply the formal parameter's declared + variance to that interpreted type. An invariant formal uses the same result + for both parts. A written wildcard instead interprets its two explicit parts + at their respective polarities and supplies its own variance. + +In particular, the formal's `in` annotation does not itself choose the input +part of a substituted parameter. InvalML first evaluates `mono(t, pol)`, then +constructs the argument according to `tp.vce`. Nor should selecting a supplied +function type re-substitute it in the enclosing member's environment: it already +denotes a type in the supplier's context. + +For example, with `Child <: Base`: + +```mlscript +class Box[T](val item: T) +class Receiver[T] with + fun accept(box: Box[T]): () = () +// On Receiver[in Child out Base], accept expects Box[Child]. +``` + +The `T` in the method input has negative polarity, so substitute `Child` first. +The invariant `Box` argument then has `Child` in both parts. Retaining the outer +`in Child out Base` pair inside `Box` would incorrectly introduce `Base` as an +output bound of an argument that originally contained only `Child`. + +Conversely, in `use(f: Sink[T] -> Int)`, `T` has positive polarity: the method +input and callback input reverse it twice. For a receiver argument +`in (Base -> Int) out (Child -> Int)`, the invariant `Sink` argument must therefore +be the single type `Child -> Int`, in both parts. + +[`newres/VarianceSubstitution.mls`](../hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls) +contains both examples and passing controls with the projected types written +directly. The substituted versions currently fail and are marked `:fixme`. +The current interpreter delays choosing the pair's part until a later constraint +or interface observation, after the nominal argument has been formed; these +operations can then choose different parts of what should be one substituted +type. Changing only the direction of nominal argument comparison cannot fix this. + +The contextual-view implementation must retain the occurrence polarity while +interpreting source type expressions and preserve the resulting reference for +later input and output uses. A polarity has only two possible values, so including +it in an otherwise finite view key does not threaten termination. This does not +solve the separate problem of unbounded argument-binding environments, and is not +permission to expand or copy a supplied type graph during substitution. + ## Recursive acceptance example This passing regression is retained in diff --git a/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls new file mode 100644 index 0000000000..19b4fc9276 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls @@ -0,0 +1,76 @@ +:. + +// A type parameter inside a callback input is in the member's output polarity. +// Substitute the output type before using it as an invariant nominal argument. +:fixme +:expect 1 +class Base +class Child(val value: Int) extends Base +class Sink[T] with + fun put(value: T): () = () +class Producer[T] with + fun use(f: Sink[T] -> Int): Int = f(new Sink[T]) +let producer = (new Producer as Producer[in (Base -> Int) out (Child -> Int)]) +producer.use(sink => + sink.put(x => x.value) + 1 +) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.15: sink.put(x => x.value) +//│ ║ ^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'value' +//│ = 1 +//│ producer = Producer + +// An invariant argument in a member input uses the outer parameter's input type +// in both directions, so it must not contribute the unrelated output supertype. +:fixme +:expect 2 +class Base +class Child(val value: Int) extends Base +class Box[T](val item: T) +class Receiver[T] with + fun accept(box: Box[T]): () = () +let receiver = (new Receiver as Receiver[in Child out Base]) +let box = Box(Child(2)) +receiver.accept(box) +box.item.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.37: box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'value' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' +//│ box = Box(Child(2)) +//│ receiver = Receiver + +// Control: writing the positive substitution directly gives the callback Child. +:expect 3 +class Base +class Child(val value: Int) extends Base +class Sink[T] with + fun put(value: T): () = () +class Producer with + fun use(f: Sink[Child -> Int] -> Int): Int = f(new Sink[Child -> Int]) +let producer = new Producer +producer.use(sink => + sink.put(x => x.value) + 3 +) +//│ = 3 +//│ producer = Producer + +// Control: writing the negative substitution directly preserves Box[Child]. +:expect 4 +class Base +class Child(val value: Int) extends Base +class Box[T](val item: T) +class Receiver with + fun accept(box: Box[Child]): () = () +let receiver = new Receiver +let box = Box(Child(4)) +receiver.accept(box) +box.item.value +//│ = 4 +//│ box = Box(Child(4)) +//│ receiver = Receiver From 26673b05a656e973829ff62313b534e78e895ed8 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 03:43:24 +0800 Subject: [PATCH 103/228] Preserve occurrence polarity during type argument substitution --- doc/new-resolution-design.md | 7 +- doc/new-resolution-suite-migration.md | 6 +- doc/new-resolution-type-value-flow.md | 48 +++--- .../test/scala/hkmc2/TypeRelationTest.scala | 63 +++++++- .../scala/hkmc2/semantics/NewResolver.scala | 142 ++++++++++++------ .../hkmc2/semantics/NewResolverState.scala | 6 +- .../main/scala/hkmc2/semantics/Shape.scala | 16 +- .../scala/hkmc2/semantics/TypeShape.scala | 8 +- .../mlscript/newres/VarianceSubstitution.mls | 79 ++++++++-- 9 files changed, 283 insertions(+), 92 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index d800eba876..5bf34ec494 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -77,7 +77,12 @@ references until that application; subsequent curried lists retain its instance Nominal argument comparisons retain directed `ContextualType` endpoint pairs. Invariant arguments install both directions, rather than copying expanded candidates. `in`/`out` arguments use InvalML's input/output comparison rules; -written wildcards override declaration variance. A parameter receives a symbolic +written wildcards override declaration variance. Substitution selects a bound +argument's part at the occurrence's polarity before applying the enclosing +nominal declaration's variance. Deferred selections retain that choice for both +directions of later constraints; see the +[substitution rules](new-resolution-type-value-flow.md#substitute-at-the-occurrence-before-applying-argument-variance). +A parameter receives a symbolic instance wrapper, and structured/concrete targets retain listeners for later bounds. `newres/MutableArrays.mls` includes passing direct and recursive append cases, including distinct callers through one stored function reference. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index f9ba4c59db..3cafa984a9 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -90,11 +90,9 @@ The inventory below separates these from compiler and prelude gaps. [type-argument constraint design](new-resolution-type-value-flow.md), including specialized inferred results observed before application, supplied member-input constraints, reconstructed receiver contexts, inferred missing member types, - occurrence-sensitive variance substitution, and the whole graph's termination - bound. Concrete outstanding cases are in + and the whole graph's termination bound. Concrete outstanding cases are in `newres/StoredSpecializations.mls`, `ConstructorInstances.mls`, - `InferenceHoles.mls`, `PartialSignatures.mls`, `VarianceSubstitution.mls`, and - `TypeGraphTermination.mls`. + `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. Decide how reassignment affects mutable storage's inferred interface, including across compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned array still checked against its initializer's tuple length; accumulating both diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 3b620e2008..2cdb7b10bc 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -33,8 +33,9 @@ proposals still require review: [supplied type references in deferred views](#partial-application-and-explicit-specialization), and [the distinction between type-reference operations and value-scope crossings](#type-reference-scope-audit-proposal-requiring-review). They must preserve caller and receiver contexts without recursively growing -binding environments. The variance substitution rules below are settled -semantics; their integration into those views remains implementation work. +binding environments. The variance substitution rules below are implemented for +declared type views; integrating supplied references into deferred value views +remains separate work. The finite call-site symbol bound alone is not a termination proof for the whole resolver. Recursive alias regressions currently overflow, and the [fixed-point @@ -52,8 +53,8 @@ inferred functions retain supplied arguments until application. Inline binders o functions also instantiate at application sites. Their shared bodies retain flat substitutions through deferred tuples, records, callbacks, and closures. Nominal argument comparisons retain both endpoint references and apply declaration/use-site -variance, with the nested substitution gap described below. The explicit-binder -recursive array acceptance cases pass. Supplied function arguments +variance, including substitution inside nested function and nominal types. +The explicit-binder recursive array acceptance cases pass. Supplied function arguments receive input obligations while retaining their declared output interface. Constructors now select their binder groups at the first term application too, including stored aliases and explicit `new`. Observing specialized inferred @@ -257,19 +258,32 @@ input and callback input reverse it twice. For a receiver argument be the single type `Child -> Int`, in both parts. [`newres/VarianceSubstitution.mls`](../hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls) -contains both examples and passing controls with the projected types written -directly. The substituted versions currently fail and are marked `:fixme`. -The current interpreter delays choosing the pair's part until a later constraint -or interface observation, after the nominal argument has been formed; these -operations can then choose different parts of what should be one substituted -type. Changing only the direction of nominal argument comparison cannot fix this. - -The contextual-view implementation must retain the occurrence polarity while -interpreting source type expressions and preserve the resulting reference for -later input and output uses. A polarity has only two possible values, so including -it in an otherwise finite view key does not threaten termination. This does not -solve the separate problem of unbounded argument-binding environments, and is not -permission to expand or copy a supplied type graph during substitution. +contains both passing examples and controls with the projected types written +directly. It also checks tuples, structural fields, declaration contravariance, +and the polarity reversal inside a written wildcard input. + +`DeclaredType.positive` records the polarity for interpreting substitutions in +its source expression. It is independent of the direction of a later constraint. +Function domains and written wildcard input parts reverse this flag. Structural +field views retain it; nominal member lookup starts from the nominal arguments +already interpreted in the enclosing occurrence. Substituting a bound parameter +selects its part without replacing that part's original lexical environment. + +When the pair is not yet available, `TypeShape.SelectedArgument` retains the +argument reference and the requested part. Both interface observations and +constraints follow that same part, including when it arrives later. The consumer +caches selections by argument reference and polarity; selecting an existing +selection returns it unchanged. Resolved parts are reused directly, except when +a missing wildcard part needs its original source for diagnostics. A forwarding +cycle which exposes no interface stops at the repeated selection, while retaining +subscriptions for later evidence. + +For a fixed finite set of argument references, there are at most two cached +selection nodes per reference. Polarity likewise only doubles an otherwise finite +view domain; no parameter symbol or expanded type tree is allocated by selection. +`TypeRelationTest` checks delayed parts, cyclic selections, identity reuse, and +stable listener counts on relation replay. These are local bounds: they do not +establish the separate whole-graph bound for recursive binding environments. ## Recursive acceptance example diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index fb6e71ee45..419a5b670f 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -22,7 +22,7 @@ class TypeRelationTest extends AnyFunSuite: def tpe(shape: TypeShape): DeclaredType = val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) resolution.publish(shape) - DeclaredType(resolution, Map.empty, Map.empty) + DeclaredType(resolution, Map.empty, Map.empty, true) def parameter(name: String): (VarSymbol, DeclaredType) = val symbol = VarSymbol(Tree.Ident(name)) (symbol, tpe(TypeShape.Parameter(symbol, symbol.inferenceHost))) @@ -56,6 +56,67 @@ class TypeRelationTest extends AnyFunSuite: assert((a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) assert(seen.toList == List(first, second)) + test("delayed argument selection fixes one endpoint for both constraint directions"): + val h = new Harness + import h.given + val (input, inputType) = h.parameter("Input") + val (output, outputType) = h.parameter("Output") + val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val argument = DeclaredType(resolution, Map.empty, Map.empty, true) + val negative = h.resolver.selectArgument(argument, false) + val positive = h.resolver.selectArgument(argument, true) + val neg = ContextualType(negative, Nil) + val pos = ContextualType(positive, Nil) + val negativeValues = h.observe(neg) + val positiveValues = h.observe(pos) + val first = IntroShape(Term.UnitVal(), N) + val second = DynShape() + val lower = ContextualType(h.tpe(TypeShape.Inferred(first)), Nil) + h.resolver.constrainTypes(lower, neg) + assert(negativeValues.isEmpty && positiveValues.isEmpty) + resolution.publish(TypeShape.Argument(TypeArgument(inputType, outputType))) + assert(negativeValues.toList == List(first)) + assert(positiveValues.isEmpty) + h.resolver.publishParameter(output, second) + assert(positiveValues.toList == List(second)) + val counts = (resolution.inferenceHost.listeners.size, input.inferenceHost.listeners.size, output.inferenceHost.listeners.size) + (1 to 1000).foreach: _ => + // Re-selecting an interpreted reference must not change its meaning or + // allocate another node, even when the later use has opposite polarity. + assert(h.resolver.selectArgument(argument, false) eq negative) + assert(h.resolver.selectArgument(argument, true) eq positive) + assert(h.resolver.selectArgument(negative, true) eq negative) + assert(h.resolver.selectArgument(positive, false) eq positive) + h.resolver.constrainTypes(lower, neg) + assert((resolution.inferenceHost.listeners.size, input.inferenceHost.listeners.size, output.inferenceHost.listeners.size) == counts) + assert(negativeValues.toList == List(first)) + assert(positiveValues.toList == List(second)) + assert(h.state.allocatedTypeInstanceCount == 0) + + test("cyclic argument selections wait for productive bounds without expanding the cycle"): + val h = new Harness + import h.given + val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val argument = DeclaredType(resolution, Map.empty, Map.empty, true) + val selected = h.resolver.selectArgument(argument, true) + val reference = ContextualType(selected, Nil) + val seen = h.observe(reference) + resolution.publish(TypeShape.Argument(TypeArgument(selected, selected))) + assert(seen.isEmpty) + val (parameter, bound) = h.parameter("Later") + resolution.publish(TypeShape.Argument(TypeArgument(bound, bound))) + val value = IntroShape(Term.UnitVal(), N) + h.resolver.publishParameter(parameter, value) + assert(seen.toList == List(value)) + h.resolver.constrainTypes(reference, reference) + val counts = (resolution.inferenceHost.listeners.size, parameter.inferenceHost.listeners.size) + (1 to 1000).foreach: _ => + h.resolver.constrainTypes(reference, reference) + assert(h.resolver.selectArgument(argument, true) eq selected) + assert((resolution.inferenceHost.listeners.size, parameter.inferenceHost.listeners.size) == counts) + assert(seen.toList == List(value)) + assert(h.state.allocatedTypeInstanceCount == 0) + test("omitted arguments reuse source-owned holes and wait for evidence"): val h = new Harness import h.given diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index fe8a175e0f..48999b913b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -170,12 +170,40 @@ class NewResolver: * A-to-A environments every time a recursive field is selected. */ private[semantics] def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType])(using NewResolverState): DeclaredType = - resolution.currentShapes.toList match - case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => bindings(symbol) - case _ => DeclaredType(resolution, bindings, Map.empty) + declaredType(resolution, bindings, true) + + private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], positive: Bool) + (using NewResolverState): DeclaredType = resolution.currentShapes.toList match + case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) + case _ => DeclaredType(resolution, bindings, Map.empty, positive) private def declaredType(resolution: TypeResolution, context: DeclaredType)(using NewResolverState): DeclaredType = - declaredType(resolution, context.bindings).instantiate(context.instances) + declaredType(resolution, context.bindings, context.positive).instantiate(context.instances) + + /** A substituted argument denotes one type at this occurrence. Freeze its + * selected part before it becomes an invariant nominal argument; later input + * and output uses must not select different parts of the original wildcard. + * Only delayed/captured pairs need a node, and selecting a selection is inert. + */ + private[semantics] def selectArgument(argument: DeclaredType, positive: Bool)(using NewResolverState): DeclaredType = + def deferred(using NewResolverState): DeclaredType = + rstate.selectedArguments.getOrElseUpdate((argument, positive), { + val resolution = new TypeResolution(argument.resolution.source, argument.resolution.fail) + resolution.publish(TypeShape.SelectedArgument(argument, positive)) + declaredType(resolution, Map.empty) + }) + rstate.selectedArguments.get((argument, positive)).getOrElse: + argument.resolution.currentShapes.toList match + case TypeShape.SelectedArgument(_, _) :: Nil => argument + case TypeShape.Argument(parts) :: Nil => + (if positive then parts.output else parts.input).instantiate(argument.instances) + case TypeShape.Wildcard(input, output) :: Nil => + // Keep the wildcard's source for a missing part's diagnostics; the + // shared extreme node itself has no source location. + (if positive then output else input).fold(deferred): part => + declaredType(part, argument.copy(positive = if positive then argument.positive else !argument.positive)) + case Nil | TypeShape.Captured(_, _) :: Nil => deferred + case _ => argument private def instanceType(instance: TypeParameterInstance)(using NewResolverState): DeclaredType = declaredType(typeResolution(instance.reference), Map.empty) @@ -199,12 +227,15 @@ class NewResolver: * the declared variance; an invariant argument supplies both identical parts. */ private def argumentType(tpe: DeclaredType, variance: Opt[Bool])(using NewResolverState): DeclaredType = - if variance.isEmpty || tpe.resolution.source.withoutCaptures.isInstanceOf[WildcardTy] then tpe else - // A substituted argument is used as a type at this occurrence. Select its - // output before applying the new declaration's variance; do not rebind a - // reference to its own formal or stack repeated variance wrappers. + val selected = tpe.resolution.currentShapes.exists: + case TypeShape.SelectedArgument(_, _) => true + case _ => false + if variance.isEmpty || (!selected && tpe.resolution.source.withoutCaptures.isInstanceOf[WildcardTy]) then tpe else + // Apply declaration variance to the interpreted type, not another pair. + // Reusing a synthesized argument must select it first, so recursive + // substitutions cannot accumulate repeated variance wrappers. tpe.resolution.currentShapes.toList match - case TypeShape.Argument(parts) :: Nil => argumentType(parts.output.instantiate(tpe.instances), variance) + case TypeShape.Argument(_) :: Nil => argumentType(selectArgument(tpe, tpe.positive), variance) case _ => rstate.variantTypes.getOrElseUpdate((tpe, variance.get), { val resolution = new TypeResolution(tpe.resolution.source, tpe.resolution.fail) val parts = if variance.get then TypeArgument(extremeType(false), tpe) @@ -216,10 +247,16 @@ class NewResolver: private def listenArgumentParts(tpe: DeclaredType)(listener: ShapeListener[TypeArgument])(using NewResolverState): Unit = tpe.resolution.listen: case TypeShape.Wildcard(input, output) => - listener(TypeArgument(input.fold(extremeType(false))(declaredType(_, tpe)), + listener(TypeArgument(input.fold(extremeType(false))(declaredType(_, tpe.copy(positive = !tpe.positive))), output.fold(extremeType(true))(declaredType(_, tpe)))) case TypeShape.Argument(parts) => listener(TypeArgument(parts.input.instantiate(tpe.instances), parts.output.instantiate(tpe.instances))) + case TypeShape.Captured(base, thru) => + listenArgumentParts(declaredType(base, tpe)): parts => + listener(TypeArgument(captureType(parts.input, thru), captureType(parts.output, thru))) + case TypeShape.Parameter(symbol, _) if tpe.bindings.contains(symbol) => + val selected = selectArgument(tpe.bindings(symbol).instantiate(tpe.instances), tpe.positive) + listener(TypeArgument(selected, selected)) case _ => listener(TypeArgument(tpe, tpe)) private def effectiveBindings(tpe: DeclaredType)(using NewResolverState): Map[VarSymbol, DeclaredType] = @@ -280,13 +317,13 @@ class NewResolver: case value: TermShape => host.publish(value) case NoShape => () case TypeShape.Parameter(symbol, _) => - current.bindings.get(symbol).foreach(bound => follow(bound.instantiate(current.instances), Nil, captures, next)) + current.bindings.get(symbol).foreach(bound => follow(selectArgument(bound.instantiate(current.instances), current.positive), Nil, captures, next)) case TypeShape.Captured(base, thru) => follow(declaredType(base, current), args, EntryMark(ResolutionBoundary(thru), N, NoMarks) :: captures, next) case TypeShape.Applied(base, arguments) => follow(declaredType(base, current), arguments.map(declaredType(_, current)), captures, next) case TypeShape.Alias(symbol, S(rhs)) => - follow(declaredType(rhs, typeBindings(current, args, symbol.defn.get.tparams)), Nil, captures, next) + follow(declaredType(rhs, typeBindings(current, args, symbol.defn.get.tparams), current.positive), Nil, captures, next) case TypeShape.Nominal(cls) => cls.tparams.zip(typeArguments(current, args, cls.tparams)).foreach: (param, argument) => listenArgumentParts(argumentType(argument, param.vce)): parts => @@ -300,6 +337,9 @@ class NewResolver: follow(declaredType(body, lexical), args, captures, next) case TypeShape.Wildcard(_, output) => output.foreach(res => follow(declaredType(res, current), args, captures, next)) case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), args, captures, next) + case TypeShape.SelectedArgument(argument, positive) => + listenArgumentParts(argument.instantiate(current.instances)): parts => + follow(if positive then parts.output else parts.input, args, captures, next) case TypeShape.Union(left, right) => follow(declaredType(left, current), Nil, captures, next) follow(declaredType(right, current), Nil, captures, next) @@ -405,6 +445,13 @@ class NewResolver: case TypeShape.Wildcard(_, output) => output.fold(publish(OpaqueTypeShape(tpe.resolution.source)))(next) case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), Nil, aliases, publish) + case TypeShape.SelectedArgument(argument, positive) => + // An argument cycle can forward this selection without exposing + // an interface. Keep the subscription for later parts, but stop + // traversing the same unproductive reference in this observation. + if !aliases(current.resolution) then + listenArgumentParts(argument.instantiate(current.instances)): parts => + follow(if positive then parts.output else parts.input, args, aliases + current.resolution, publish) case TypeShape.Inferred(value) => listenInstanceViews(instantiateShape(value, current.instances))(publish) case TypeShape.Hole(host) => host.subscribe: value => listenInstanceViews(instantiateShape(value, current.instances))(publish) @@ -424,13 +471,13 @@ class NewResolver: // This also terminates aliases that recursively grow their arguments. if aliases(current.resolution) then publish(OpaqueTypeShape(tpe.resolution.source)) else rhs match - case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams)), Nil, + case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams), current.positive), Nil, aliases + current.resolution, publish) case N => publish(OpaqueTypeShape(tpe.resolution.source)) case TypeShape.Applied(base, params) => follow(declaredType(base, current), params.map(declaredType(_, current)), aliases, publish) case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match - case S(bound) => follow(bound.instantiate(current.instances), Nil, aliases, publish) + case S(bound) => follow(selectArgument(bound.instantiate(current.instances), current.positive), Nil, aliases, publish) case N => val target = current.instances.get(symbol).fold(host)(_.inferenceHost) listenTypeArgument(target): value => @@ -458,9 +505,9 @@ class NewResolver: case TypeShape.Function(params, ret) => val ps = params match case Tup(fields) => DeclaredParams(fields.collect: - case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current)) + case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current.copy(positive = !current.positive))) , fields.exists(!_.isInstanceOf[Fld]), N) - case single => DeclaredParams(S(declaredType(typeResolution(single), current)) :: Nil, false, N) + case single => DeclaredParams(S(declaredType(typeResolution(single), current.copy(positive = !current.positive))) :: Nil, false, N) publish(CallableTypeShape(tpe.resolution.source, ps :: Nil, S(declaredType(ret, current)), N, N, N)) case TypeShape.Polymorphic(params, _, body) => @@ -479,7 +526,7 @@ class NewResolver: case TypeShape.Tuple(fields) => publish(TupleShape(current.resolution.source, fields.map(field => TupleShape.TypedField(declaredType(field, current), Nil)))(this)) - case TypeShape.Record(source, fields) => publish(RecordTypeShape(source, fields, effectiveBindings(current))) + case TypeShape.Record(source, fields) => publish(RecordTypeShape(source, fields, effectiveBindings(current), current.positive)) case TypeShape.Union(left, right) => next(left); next(right) case TypeShape.Intersection(left, right) => next(left); next(right) case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) @@ -510,7 +557,7 @@ class NewResolver: case _ => td.resultSignature private def capturedTypes(using rs: NewResolverState) = rs.capturedTypes - private def captureType(bound: DeclaredType, scope: TermSymbol)(using NewResolverState): DeclaredType = + private def captureType(bound: DeclaredType, scope: AnyDefinitionSymbol)(using NewResolverState): DeclaredType = capturedTypes.getOrElseUpdate((bound, scope), { val captured = new TypeResolution(bound.resolution.source, bound.resolution.fail) captured.publish(TypeShape.Captured(bound.resolution, scope)) @@ -518,7 +565,7 @@ class NewResolver: }) private def listenDeclaredMember(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], flow: FlowSymbol, - source: Term, annotation: Opt[Term], selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(listener: Listener)(using NewResolverState): Unit = + source: Term, annotation: Opt[Term], selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool, positive: Bool)(listener: Listener)(using NewResolverState): Unit = member.onComplete: () => valueTarget(member, receiver) match case S(symbol: TermSymbol) if !symbol.isInstanceOf[ClassCtorSymbol] => @@ -544,7 +591,7 @@ class NewResolver: exited match case value: TermShape => listener(value) case NoShape => () - def signature(sign: Term)(using NewResolverState): DeclaredType = + def signature(sign: Term, polarity: Bool)(using NewResolverState): DeclaredType = // Legacy annotations have plain references instead of Capture nodes. // Rebase substituted class parameters into this member's scope, just // as a new-resolution Capture does, before the member exits it. @@ -555,14 +602,14 @@ class NewResolver: if !td.tsym.decl.exists(_.isInstanceOf[Param]) then visit(sign) val scopedBindings = capturedBindings.map: (symbol, bound) => symbol -> (if legacyParameters(symbol) then captureType(bound, td.tsym) else bound) - declaredType(typeResolution(sign), scopedBindings) - val result = resultSignature(td).map(signature) + declaredType(typeResolution(sign), scopedBindings, polarity) + val result = resultSignature(td).map(signature(_, positive)) if td.sign.nonEmpty && (td.k is syntax.Fun) && !td.flags.hasResultAnnotation then - listenTypeViews(signature(td.sign.get))(publish) + listenTypeViews(signature(td.sign.get, positive))(publish) else if td.params.nonEmpty then publish(CallableTypeShape(source, - td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature)), - ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature))), result, N, N, S(td))) + td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature(_, !positive))), + ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature(_, !positive)))), result, N, N, S(td))) else result match case S(tpe) => listenTypeInstances(tpe)(publish) case N => @@ -692,7 +739,7 @@ class NewResolver: case NoShape => () tpe.resolution.listen: case TypeShape.Parameter(symbol, host) => tpe.bindings.get(symbol) match - case S(bound) => follow(bound.instantiate(tpe.instances), Nil, captures, next) + case S(bound) => follow(selectArgument(bound.instantiate(tpe.instances), tpe.positive), Nil, captures, next) case N => val target = tpe.instances.get(symbol) val destination = target.fold(host)(_.inferenceHost) @@ -715,7 +762,7 @@ class NewResolver: case TypeShape.Applied(base, params) => follow(declaredType(base, tpe), params.map(declaredType(_, tpe)), captures, next) case TypeShape.Alias(symbol, S(rhs)) => - follow(declaredType(rhs, typeBindings(tpe, args, symbol.defn.get.tparams)), Nil, captures, next) + follow(declaredType(rhs, typeBindings(tpe, args, symbol.defn.get.tparams), tpe.positive), Nil, captures, next) case TypeShape.Hole(host) => value.enter(captures) match case value: TermShape => host.publish(value) case NoShape => () @@ -732,12 +779,15 @@ class NewResolver: case NoShape => () case _ => () case _ => () - case TypeShape.Record(_, fields) => observe(constrainRecord(fields, effectiveBindings(tpe), _, marks)) + case TypeShape.Record(_, fields) => observe(constrainRecord(fields, effectiveBindings(tpe), tpe.positive, _, marks)) case TypeShape.Function(_, _) => observe(constrainFunction(tpe, _, marks)) case TypeShape.Polymorphic(_, _, body) => follow(declaredType(body, tpe), args, captures, next) - case TypeShape.Wildcard(input, _) => input.foreach(res => follow(declaredType(res, tpe), args, captures, next)) + case TypeShape.Wildcard(input, _) => input.foreach(res => follow(declaredType(res, tpe.copy(positive = !tpe.positive)), args, captures, next)) case TypeShape.Argument(parts) => follow(parts.input.instantiate(tpe.instances), args, captures, next) + case TypeShape.SelectedArgument(argument, positive) => + listenArgumentParts(argument.instantiate(tpe.instances)): parts => + follow(if positive then parts.output else parts.input, args, captures, next) case TypeShape.Nominal(cls) => listenInstanceViews(value): value => val arguments = typeArguments(tpe, args, cls.tparams) val (actual, instances) = contextualParts(value) @@ -776,10 +826,10 @@ class NewResolver: * argument inference and callback checking without refining the annotation * with additional properties from an actual record. */ - private def constrainRecord(fields: Ls[(RcdField, TypeResolution)], bindings: Map[VarSymbol, DeclaredType], + private def constrainRecord(fields: Ls[(RcdField, TypeResolution)], bindings: Map[VarSymbol, DeclaredType], positive: Bool, actual: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = fields.foreach: (field, sign) => - val expected = declaredType(sign, bindings) + val expected = declaredType(sign, bindings, positive) val flow = FlowSymbol.memSym(field.sym)(using rstate.owner) def receive(value: TermShape)(using NewResolverState): Unit = inferTypeArguments(expected, value, marks) def member(info: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match @@ -787,8 +837,8 @@ class NewResolver: case MemberLookup.Found(RecordMember(member, true), _) => receive(UnknownValueShape.at(member.rhs)) case MemberLookup.Found(member, inner) => fromBMS(member.memberSymbol, flow, inner, value => receive(instantiateShape(value, instances)), field.rhs, _ => (), false)(using rstate.withInstances(instances)) - case MemberLookup.Declared(member, bound, inner, annotation) => - listenDeclaredMember(member, bound, flow, field.rhs, annotation, _ => (), false): value => + case MemberLookup.Declared(member, bound, inner, annotation, positive) => + listenDeclaredMember(member, bound, flow, field.rhs, annotation, _ => (), false, positive): value => instantiateShape(value, instances).exit(inner) match case value: TermShape => receive(value) case NoShape => () @@ -855,7 +905,7 @@ class NewResolver: case _ => matchLists(ret, tail) case Nil => () case Marked(record: RecordTypeShape, _) => - constrainRecord(record.fields, record.bindings, actual, marks) + constrainRecord(record.fields, record.bindings, record.positive, actual, marks) case _ => () actual match case Marked(_: UnknownValueShape, _) => @@ -1233,8 +1283,8 @@ class NewResolver: case MemberLookup.Found(sym: BlockMemberSymbol, marks) => fromBMS(sym, resSym, marks, field => matchShapePat(instantiateShape(field, instances), pat)(_ => ()), ctor.target, _ => (), false) - case MemberLookup.Declared(member, bindings, marks, annotation) => - listenDeclaredMember(member, bindings, resSym, ctor.target, annotation, _ => (), false): field => + case MemberLookup.Declared(member, bindings, marks, annotation, positive) => + listenDeclaredMember(member, bindings, resSym, ctor.target, annotation, _ => (), false, positive): field => listenInstanceViews(instantiateShape(field, instances)): field => field.exit(marks) match case value: TermShape => @@ -1561,12 +1611,12 @@ class NewResolver: publish(UnknownValueShape.at(field.rhs)) private def publishDeclared(host: NewResolvable & ShapeHost, member: BlockMemberSymbol, flow: FlowSymbol, - bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term], + bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term], positive: Bool, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = // Keep one diagnostic witness per semantic candidate, as for unknown values. // A different annotation location must not create additional inference flow. - val shape = declaredSymShapes.getOrElseUpdate((member, flow, marks, bindings), - DeclaredSymShape(member, flow, marks, bindings, annotation)) + val shape = declaredSymShapes.getOrElseUpdate((member, flow, marks, bindings, positive), + DeclaredSymShape(member, flow, marks, bindings, annotation, positive)) publishViewed(host, shape, instances) private def publishDynamic(host: NewResolvable & ShapeHost, marks: Ls[Marks])(using NewResolverState): Unit = @@ -1593,10 +1643,10 @@ class NewResolver: val candidate = prefix -> member.memberSymbol rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) publishMember(ref, member, ref.resSym, marks, instances) - case MemberLookup.Declared(member, bindings, marks, annotation) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member) => + case MemberLookup.Declared(member, bindings, marks, annotation, positive) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member) => val candidate = prefix -> member rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) - publishDeclared(ref, member, ref.resSym, bindings, marks, annotation, instances) + publishDeclared(ref, member, ref.resSym, bindings, marks, annotation, positive, instances) case MemberLookup.Indexed(_, _) if rstate.canResolve(ref) => resolError(ref, msg"Tuple elements must be selected by index." -> ref.toLoc :: Nil) case MemberLookup.Dynamic(marks) if rstate.canResolve(ref) || ref.dynamicPrefixes.contains(prefix) => @@ -1676,9 +1726,9 @@ class NewResolver: log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") rstate.recordResolution(sel, sel.resolvedMembers.contains(bms.memberSymbol))(sel.resolvedMembers ::= bms.memberSymbol) publishMember(sel, bms, sel.resSym, marks, instances) - case MemberLookup.Declared(member, bindings, marks, annotation) if rstate.canResolve(sel) || sel.resolvedMembers.contains(member) => + case MemberLookup.Declared(member, bindings, marks, annotation, positive) if rstate.canResolve(sel) || sel.resolvedMembers.contains(member) => rstate.recordResolution(sel, sel.resolvedMembers.contains(member))(sel.resolvedMembers ::= member) - publishDeclared(sel, member, sel.resSym, bindings, marks, annotation, instances) + publishDeclared(sel, member, sel.resSym, bindings, marks, annotation, positive, instances) case MemberLookup.Indexed(field, marks) if rstate.canResolve(sel) || sel.tupleIndex == sel.id.name.toIntOption => val index = sel.id.name.toIntOption softAssert(index.exists(_ >= 0), "Tuple lookup must identify a nonnegative index") @@ -1728,7 +1778,7 @@ class NewResolver: // An explicit projection can name a narrower class, but // it cannot recover that class's implementation arguments. val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) - MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation) + MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation, true) case _ => info member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, Map.empty) case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc, Map.empty)) @@ -1865,7 +1915,7 @@ class NewResolver: case contextual: ContextualSymShape => fromSymbol(contextual.source, value => listener(instantiateShape(value, contextual.instances)), source, selected, receiver)(using rstate.withInstances(contextual.instances)) case declared: DeclaredSymShape => - listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, declared.annotation, selected, receiver): value => + listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, declared.annotation, selected, receiver, declared.positive): value => value.exit(shape.markss) match case value: TermShape => listener(value) case NoShape => () @@ -1922,7 +1972,7 @@ class NewResolver: ((d.sign.nonEmpty && (!d.flags.hasResultAnnotation || d.params.forall(ps => (ps.params ::: ps.restParam.toList).forall(_.sign.nonEmpty)))) || (d.body.isEmpty && d.tsym.owner.exists(_.asDefnSym.defn.exists(_.tparams.nonEmpty)))) => - listenDeclaredMember(bms, instanceBindings(d, markss), resSym, trm, N, selected, receiver): value => + listenDeclaredMember(bms, instanceBindings(d, markss), resSym, trm, N, selected, receiver, true): value => value.exit(markss) match case value: TermShape => listener(value) case NoShape => () diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index c73cb41988..21e8c09ac3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -198,7 +198,7 @@ final class NewResolverState private ( new Cache(inherited.map(_.introShapes), identity) val symShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = new Cache(inherited.map(_.symShapes), identity) - val declaredSymShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks], Map[VarSymbol, DeclaredType]), DeclaredSymShape] = + val declaredSymShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks], Map[VarSymbol, DeclaredType], Bool), DeclaredSymShape] = new Cache(inherited.map(_.declaredSymShapes), identity) val selfShapes: Cache[InnerSymbol, BaseShape] = new Cache(inherited.map(_.selfShapes), identity) @@ -254,6 +254,8 @@ final class NewResolverState private ( new Cache(inherited.map(_.extremeTypes), identity) val variantTypes: Cache[(DeclaredType, Bool), DeclaredType] = new Cache(inherited.map(_.variantTypes), identity) + val selectedArguments: Cache[(DeclaredType, Bool), DeclaredType] = + new Cache(inherited.map(_.selectedArguments), identity) val abstractTypes: Cache[TypeResolution, DeclaredType] = new Cache(inherited.map(_.abstractTypes), identity) val omittedTypes: Cache[(TypeResolution, VarSymbol), DeclaredType] = @@ -262,7 +264,7 @@ final class NewResolverState private ( new Seen(inherited.map(_.exposedTypeHoles)) val signatureParameters: Cache[VarSymbol, DeclaredType] = new Cache(inherited.map(_.signatureParameters), identity) - val capturedTypes: Cache[(DeclaredType, TermSymbol), DeclaredType] = + val capturedTypes: Cache[(DeclaredType, AnyDefinitionSymbol), DeclaredType] = new Cache(inherited.map(_.capturedTypes), identity) val tupleArrayParents: Cache[Identity[TupleShape], NominalInstanceView] = new Cache(inherited.map(_.tupleArrayParents), identity) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index b158e14b28..5c543f0ef9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -269,7 +269,7 @@ enum MemberLookup: case Found(member: BlockMemberSymbol | RecordMember, marks: Ls[Marks]) // Declared members expose signatures only. Their marks transport dependent // type arguments; they never authorize reading an implementation's value flow. - case Declared(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term]) + case Declared(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term], positive: Bool) case Indexed(field: TupleShape.Fixed, marks: Ls[Marks]) case Dynamic(marks: Ls[Marks]) case Missing @@ -278,7 +278,7 @@ enum MemberLookup: def withMarks(marks: Ls[Marks]): MemberLookup = this match case Contextual(source, instances) => Contextual(source.withMarks(marks), instances) case Found(member, inner) => Found(member, inner ::: marks) - case Declared(member, bindings, inner, annotation) => Declared(member, bindings, inner ::: marks, annotation) + case Declared(member, bindings, inner, annotation, positive) => Declared(member, bindings, inner ::: marks, annotation, positive) case Indexed(field, inner) => Indexed(field, inner ::: marks) case Dynamic(inner) => Dynamic(inner ::: marks) case _ => this @@ -286,7 +286,7 @@ enum MemberLookup: /** The receiver's annotation remains the diagnostic origin when lookup visits a parent. */ def withAnnotation(annotation: Opt[Term]): MemberLookup = this match case Contextual(source, instances) => Contextual(source.withAnnotation(annotation), instances) - case Declared(member, bindings, marks, _) => Declared(member, bindings, marks, annotation) + case Declared(member, bindings, marks, _, positive) => Declared(member, bindings, marks, annotation, positive) case _ => this def instantiate(instances: Map[VarSymbol, TypeParameterInstance]): MemberLookup = @@ -369,7 +369,7 @@ extends SymShape(source.sym, source.resSym, source.markss) * type, and constructor interpretations of the same overload set. */ final class DeclaredSymShape(symbol: BlockMemberSymbol, site: FlowSymbol, marks: Ls[Marks], - val bindings: Map[VarSymbol, DeclaredType], val annotation: Opt[Term]) extends SymShape(symbol, site, marks) + val bindings: Map[VarSymbol, DeclaredType], val annotation: Opt[Term], val positive: Bool) extends SymShape(symbol, site, marks) /* class ThisShape(val defn: Definition) extends NonAppTermShape: @@ -391,12 +391,12 @@ class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppT * to the annotation, not to whichever record happens to be passed by a caller. */ final case class RecordTypeShape(source: Term.Rcd, fields: Ls[(RcdField, TypeResolution)], - bindings: Map[VarSymbol, DeclaredType]) extends NonAppTermShape: + bindings: Map[VarSymbol, DeclaredType], positive: Bool) extends NonAppTermShape: def describe: Str = "record type" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = fields.reverseIterator.collectFirst { - case (field, _) if field.sym.nme == name => MemberLookup.Declared(field.sym, bindings, Nil, S(source)) + case (field, _) if field.sym.nme == name => MemberLookup.Declared(field.sym, bindings, Nil, S(source), positive) }.getOrElse(MemberLookup.Missing) /** An instance described by a type, in either position of a constraint. Keep the @@ -457,7 +457,7 @@ final case class NominalInstanceView(defn: ClassLikeDef, bindings: Map[VarSymbol case _ => N protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = defn.body.members.get(name) match - case S(member) => MemberLookup.Declared(member, bindings, Nil, annotation) + case S(member) => MemberLookup.Declared(member, bindings, Nil, annotation, true) case N => (name.toIntOption, resolver.arrayElementType(this)) match case (S(index), S(element)) if index >= 0 => MemberLookup.Indexed(TupleShape.TypedField(element, Nil), Nil) @@ -718,7 +718,7 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] case Missing => N case Found(member: RecordMember, inner) => S(Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil)) - case Found(_: BlockMemberSymbol, _) | Declared(_, _, _, _) | Indexed(_, _) => + case Found(_: BlockMemberSymbol, _) | Declared(_, _, _, _, _) | Indexed(_, _) => // Record spreads recursively look up RecordShapes, which only create RecordMembers. lastWords("Record lookup returned a nominal member") spread(shape.getMember(name)).getOrElse(loop(rest)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index bb08a6084b..dce62951ec 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -20,6 +20,9 @@ enum TypeShape: case Wildcard(input: Opt[TypeResolution], output: Opt[TypeResolution]) // Synthesized declaration variance retains the argument's lexical environment. case Argument(parts: TypeArgument) + // Substitution selects one part before the type is used in further constraints. + // The argument stays in its own lexical environment, including delayed parts. + case SelectedArgument(argument: DeclaredType, positive: Bool) // The same third-party symbol can have different inference in two exporters. // Retain its originating host so importing a result needs no whole-state copy. case Parameter(symbol: VarSymbol, host: Publisher.Data[Shape]) @@ -87,6 +90,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => checkArity(base, Set.empty) case Wildcard(input, output) => input.foreach(_.validate(next)); output.foreach(_.validate(next)) case Argument(parts) => parts.input.resolution.validate(next); parts.output.resolution.validate(next) + case SelectedArgument(argument, _) => argument.resolution.validate(next) case Function(_, result) => result.validate(next) case Polymorphic(params, _, body) => params.foreach: param => @@ -103,7 +107,9 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => * interfaces, not constructor arguments or value-flow capture paths. */ final case class DeclaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], - instances: Map[VarSymbol, TypeParameterInstance]): + instances: Map[VarSymbol, TypeParameterInstance], positive: Bool): + // This polarity interprets substitutions in the source expression; it is not + // the direction of a constraint subsequently applied to the interpreted type. require(instances.forall((source, instance) => instance.origin eq source), "A substitution must map original binders to their call-site instances") /** Instantiation changes references to source binders, never expands their bounds. diff --git a/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls index 19b4fc9276..8ce1ee48cc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls @@ -2,7 +2,6 @@ // A type parameter inside a callback input is in the member's output polarity. // Substitute the output type before using it as an invariant nominal argument. -:fixme :expect 1 class Base class Child(val value: Int) extends Base @@ -15,16 +14,11 @@ producer.use(sink => sink.put(x => x.value) 1 ) -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.15: sink.put(x => x.value) -//│ ║ ^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'value' //│ = 1 //│ producer = Producer // An invariant argument in a member input uses the outer parameter's input type // in both directions, so it must not contribute the unrelated output supertype. -:fixme :expect 2 class Base class Child(val value: Int) extends Base @@ -35,12 +29,7 @@ let receiver = (new Receiver as Receiver[in Child out Base]) let box = Box(Child(2)) receiver.accept(box) box.item.value -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.37: box.item.value -//│ ║ ^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'value' -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' +//│ = 2 //│ box = Box(Child(2)) //│ receiver = Receiver @@ -74,3 +63,69 @@ box.item.value //│ = 4 //│ box = Box(Child(4)) //│ receiver = Receiver + +// Tuple fields retain the enclosing input polarity for nested nominal arguments. +:expect 5 +class Base +class Child(val value: Int) extends Base +class Box[T](val item: T) +class Receiver[T] with + fun accept(boxes: [Box[T]]): () = () +let receiver = (new Receiver as Receiver[in Child out Base]) +let box = Box(Child(5)) +receiver.accept([box]) +box.item.value +//│ = 5 +//│ box = Box(Child(5)) +//│ receiver = Receiver + +// Structural fields retain the same substitution polarity as their record. +:expect 6 +class Base +class Child(val value: Int) extends Base +class Box[T](val item: T) +class Receiver[T] with + fun accept(record: {item: Box[T]}): () = () +let receiver = (new Receiver as Receiver[in Child out Base]) +let box = Box(Child(6)) +receiver.accept({item: box}) +box.item.value +//│ = 6 +//│ box = Box(Child(6)) +//│ receiver = Receiver + +// Declaration-site contravariance applies after the occurrence selects Child -> Int. +:expect 7 +class Base +class Child(val value: Int) extends Base +class Sink[in T] with + fun put(value: T): () = () +class Producer[T] with + fun use(f: Sink[T] -> Int): Int = f(new Sink[T]) +let producer = (new Producer as Producer[in (Base -> Int) out (Child -> Int)]) +producer.use(sink => + sink.put(x => x.value) + 7 +) +//│ = 7 +//│ producer = Producer + +// A written wildcard input reverses the member input polarity: T selects Base. +// Its input constraint contributes Base to the argument, invalidating .value. +:ge +class Base +class Child(val value: Int) extends Base +class Box[T](val item: T) +class Receiver[T] with + fun accept(box: Box[in T]): () = () +let receiver = (new Receiver as Receiver[in Child out Base]) +let box = Box(Child(8)) +receiver.accept(box) +box.item.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.124: box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'value' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ box = Box(Child(8)) +//│ receiver = Receiver From 7a93af52a73647b58623040d948b5b0b21936c44 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 09:55:59 +0800 Subject: [PATCH 104/228] Correct instantiation sites for specialization and by-name calls --- doc/new-resolution-design.md | 14 +- doc/new-resolution-suite-migration.md | 5 +- doc/new-resolution-type-value-flow.md | 217 +++++++++++------- .../mlscript/newres/InstantiationSites.mls | 69 ++++++ .../mlscript/newres/StoredSpecializations.mls | 6 +- 5 files changed, 224 insertions(+), 87 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/InstantiationSites.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 5bf34ec494..5215af9b7a 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -71,8 +71,10 @@ nominal parameters, such as `Foo[A]` containing a `Box[A]`. Generic methods use the same flow as free functions, including callback-result inference and curried signatures. Declared callable shapes retain their type parameters. Functions and constructors allocate explicit binders once per original definition and syntactic -term application. Stored specializations and unapplied `new` retain supplied type -references until that application; subsequent curried lists retain its instance map. +term application in the current implementation. This delays explicit specialization +and by-name invocation too far: the [instantiation-site rules](new-resolution-type-value-flow.md#finite-call-site-instantiation-and-marks) +require those operations to own their groups immediately and later applications +to reuse them. Subsequent curried lists retain the selected instance map. Nominal argument comparisons retain directed `ContextualType` endpoint pairs. Invariant arguments install both directions, rather than copying expanded @@ -112,9 +114,11 @@ written binders. `newres/InferenceHoles.mls` also retains unresolved cases for recursive holes and omissions shared through an alias body. No hole creates a call-site parameter instance. -Standalone specializations of inferred functions retain their supplied types in -`SpecializedShape` until application. Each application binds its own memoized -parameter instances; the original generic binder remains unchanged. Argument +Standalone specializations of inferred functions currently retain supplied types +in `SpecializedShape` until application, which then binds its own parameter +instances. This must change: the specialization owns the group and consumes that +scheme; subsequent uses retain it. By-name functions and methods likewise +instantiate at their implicit invocation before their result is observed. Argument arity is checked even for unused specializations. `newres/StoredSpecializations.mls` covers stored aliases, inferred record results, and curried calls. It also records the remaining gap in observing an inferred specialized result during callback diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 3cafa984a9..0e24781ff7 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -88,10 +88,11 @@ The inventory below separates these from compiler and prelude gaps. [resolver notes](new-resolution-design.md). Tracking individual positions and lengths is out of scope. Complete the [type-argument constraint design](new-resolution-type-value-flow.md), including - specialized inferred results observed before application, supplied member-input + authoritative instantiation at explicit type applications and by-name invocations, + specialized inferred results observed before term application, supplied member-input constraints, reconstructed receiver contexts, inferred missing member types, and the whole graph's termination bound. Concrete outstanding cases are in - `newres/StoredSpecializations.mls`, `ConstructorInstances.mls`, + `newres/InstantiationSites.mls`, `StoredSpecializations.mls`, `ConstructorInstances.mls`, `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. Decide how reassignment affects mutable storage's inferred interface, including across compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 2cdb7b10bc..3a53de2b2b 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -13,8 +13,10 @@ The following semantic decisions are settled: - Wrap instance values, retaining references to their annotated types. Supplying a type argument affects both its input and output uses; an ordinary value argument contributes a lower bound. -- Instantiate explicit binders once per original definition and syntactic term - application. Reuse those symbols during recursion and retain marks to separate +- Instantiate explicit binders once per original definition and authoritative + instantiation site: an explicit term-level type application, an implicit + by-name invocation, or otherwise the first term application. Retain that group + on the resulting value. Reuse it during recursion and use marks to distinguish enclosing activations of the same site. - Infer every missing signature part through ordinary marked flow, including holes within annotations and omitted generic arguments. These positions do not @@ -28,14 +30,15 @@ The following semantic decisions are settled: marks. Tracking positions and lengths is outside this design. The implementation plan is in [constraint propagation and implementation -order](#constraint-propagation-and-implementation-order). Two representation -proposals still require review: [supplied type references in deferred -views](#partial-application-and-explicit-specialization), and [the distinction -between type-reference operations and value-scope crossings](#type-reference-scope-audit-proposal-requiring-review). -They must preserve caller and receiver contexts without recursively growing -binding environments. The variance substitution rules below are implemented for -declared type views; integrating supplied references into deferred value views -remains separate work. +order](#constraint-propagation-and-implementation-order). Correcting +[instantiation sites](#partial-application-and-explicit-specialization) takes +precedence over extending deferred value views to arbitrary supplied-type +references: an explicit type application already provides a site and parameter +instances for those views. The [distinction between type-reference operations and +value-scope crossings](#type-reference-scope-audit-proposal-requiring-review) +remains a proposal requiring review. Recursive reference graphs must preserve +caller and receiver contexts without growing binding environments. The variance +substitution rules below are implemented for declared type views. The finite call-site symbol bound alone is not a termination proof for the whole resolver. Recursive alias regressions currently overflow, and the [fixed-point @@ -45,7 +48,9 @@ conditions](#fixed-points-and-implementation-checks) remain completion gates. Instance wrappers and preservation of quantified signature binders and bounds are implemented. Explicit type application also observes annotated polymorphic -values. Calls through complete callable signatures use the bounded binder-instance +values. The implementation still delays explicit specialization until a term +application and can preserve a by-name definition's binders on its result; both +must be corrected to follow the instantiation-site rules below. Calls through complete callable signatures use the bounded binder-instance cache. `DeclaredType.instances` carries a flat substitution through their declared components; curried tails retain it, and independently quantified returned callables instantiate their own binders. Stored specializations of complete signatures and @@ -105,17 +110,35 @@ are one application of the rules. ## Finite call-site instantiation and marks **Instantiate each definition's explicitly declared type parameters once per -syntactic call site.** This includes binders in inline annotations and separate -type signatures, whether the call supplies type arguments or infers them. A call -`f(x)` therefore instantiates a declared `f[A]` just as `f[Int](x)` does. +syntactic instantiation site.** This includes binders in inline annotations and +separate signatures. A site need not contain a term argument list: + +| Operation on an uninstantiated scheme | Authoritative site | +| --- | --- | +| Explicit type application `f[T]` | That type application | +| By-name invocation `make` or `obj.make` | That reference or selection, unless its enclosing type application supplies arguments | +| Ordinary function application `f(x)` | The first term application | +| Constructor application | Its type application if explicit; otherwise its first term application, or saturated zero-list `new` | + +This table concerns term-level instantiation. A type expression such as `Array[Int]` +inside an annotation does not invoke a computation or allocate a call-site group. +A reference to an ordinary function with parameter lists, such as `let g = f`, +does not invoke that function and may retain its uninstantiated scheme. Memoize a complete group of parameter symbols using this key: ```text -(original definition, syntactic application site) +(original definition, authoritative syntactic instantiation site) -> {original parameter -> instantiated parameter} ``` +After instantiation, carry the group on the resulting value. Neither passing that +value through an alias nor supplying later term arguments instantiates the same +scheme again. In `f[T](x)`, the type application owns the group; in `make[T]`, do +not instantiate once at the reference and again at the type application. A +separately quantified result scheme is a different scheme, not a reappearance of +the definition's consumed binders. + Different sites receive different symbols. Revisiting the same definition at the same site reuses its group, including during recursion. The key must not contain the supplied types, incoming bounds, marks, or an already instantiated definition. @@ -142,7 +165,7 @@ This follows the finite-allocation idea in §3 of [Tate's type-outference paper](https://rosstate.org/publications/outference/outference-tate-oopsla25.pdf): label listeners introduce signature unknowns once per invocation site and nominal label, reusing them as bounds arrive rather than expanding every concrete type. -Here the proposed key uses the original definition and application site. The +Here the key uses the original definition and authoritative instantiation site. The paper's formal calculus has monomorphic methods; its termination result does not directly establish termination for our generic functions and marks. @@ -330,8 +353,8 @@ An application must not overwrite both endpoints with the callee substitution. ### Definition and application identities -Use the stable `Term.App.resSym` as application identity, rather than taking a -site from the callee's marks. Marks can identify a function reference shared by +For an ordinary implicit instantiation, use the stable `Term.App.resSym` as +application identity, rather than taking a site from the callee's marks. Marks can identify a function reference shared by several applications: ```mlscript @@ -344,8 +367,16 @@ These applications need distinct parameter instances. The two-array regression following the recursive `append` block in `newres/MutableArrays.mls` checks this case with different element interfaces. -For construction, use `Term.New.resSym` and canonicalize the constructor and -class to the same original owner. For an anonymous polymorphic annotation, the +An explicit term-level `Term.TyApp` needs its own stable site identity, memoized +by the original syntax node, never allocated afresh during observation. By-name +invocations use the source reference/selection's site. Resolving an explicit +application must choose that site before interpreting an underlying by-name +reference, so its implicit invocation cannot allocate a competing group. + +For construction without explicit type arguments, use `Term.New.resSym` when it +consumes arguments or saturates a zero-list class; an unapplied constructor uses +its later first application. Canonicalize the constructor and class to the same +original owner. For an anonymous polymorphic annotation, the owner is its original quantified declaration node. An overload resolving to more than one definition instantiates each original definition at that site. Where the language implicitly invokes a getter, its source reference/selection is the @@ -621,37 +652,66 @@ Preserve the existing member-target completion checks for imported code as well. ### Partial application and explicit specialization -For curried calls, instantiate when the first parameter -list is consumed and retain that substitution in the partially applied callable. -Later lists reuse it. A separately quantified returned callable has its own -binders to instantiate at its later application. A standalone specialization such -as `let g = f[Int]` retains its supplied type arguments until application; -each application of `g` then binds its own site instances to `Int`. This avoids -introducing a second allocation policy at type-application nodes. Check argument -arity against the retained scheme immediately, and allow observations of the -specialized interface to use its supplied type references before a term call. - -`SpecializedShape` retains the original inferred function, supplied argument -references, and captured call-site instances. Application unwraps that recipe, -allocates or reuses the definition/site binder group, and attaches the supplied -references before replaying body constraints. It does not publish arguments into -the original definition's type parameters. `newres/StoredSpecializations.mls` -checks independent specializations through one stored alias, deferred record -results, curried calls, and arity errors even when a specialization is unused. - -Constructors use the same `instantiateDefinition` operation as inferred functions. -`ClassReference` retains the selected class, marks, captured binder map, and supplied -type references through aliases and captures. `new C[T]` with parameter lists still -to consume retains a `NewShape` recipe; its first later term application owns the -binder group. A saturated zero-list `new C[T]` owns its group at the `new` site. -Later argument lists reuse the first group's map. Array spreads observe this -instantiated element parameter, including bounds inferred from inserted values. -`newres/ConstructorInstances.mls` checks independent aliases, explicit `new`, curried -and unapplied constructors, class/method binder separation, and a shared allocation -inside a generic function. Type application after the first consumed list cannot -rebind that scheme or turn a constructed object back into its constructor. - -Two constructor-related cases remain failing regressions in that worksheet. +Explicit type application is an instantiation site, even before any term +arguments are supplied. For `let g = f[Int]`, allocate the definition's group at +`f[Int]`, attach `Int` to its parameter instance, and retain that map on `g`. +Later applications of `g` reuse the map. They still have their own value-flow +marks; sharing the type-parameter group does not make their term arguments or +results the same runtime value. Check type-argument arity at specialization, +including when the result is unused. + +Without explicit type arguments, a function with parameter lists instantiates +when its first list is consumed. A curried tail retains that group for later +lists. By contrast, referencing a by-name function or selecting a by-name method +already invokes its body, so instantiate before observing its result. For example: + +```mlscript +private fun make[A] = + let cells = mut [] + (value: A) => + cells.push(value) + cells.0 +let shared = make +``` + +`shared` closes over one array allocated by the invocation at `make`. Its later +calls must all use that invocation's instance of `A`. Giving each call to +`shared` an independent `A` would treat one shared array as unrelated element +types. Separate expressions `let left = make` and `let right = make` are separate +invocation sites. `newres/InstantiationSites.mls` checks shared storage, independent +by-name method invocations, and rejection of an incompatible result selection +through an inferred closure. Its annotated variant, `make[A]: A -> A`, currently +accepts `shared(First(3)); shared(Second(4)).second` (written on separate lines in +the test), although the second call returns the stored `First`. That case is a +`:breakme` soundness regression: a result annotation must not re-generalize the +by-name definition's binder. The same rule applies to explicitly specialized +`make[T]`. Only a separately quantified result scheme retains its own binders, +subject to ordinary generic-body checking. + +The existing `SpecializedShape` delays supplied arguments until a term call and +`appShape` then allocates a group. Replace that recipe with a value whose scheme +has already been instantiated at the type application. The same distinction is +needed for complete callable signatures and inferred bodies: a shape carrying +captured instances alone must not be mistaken for a consumed scheme, nor may a +consumed scheme be instantiated again. Preserve the chosen substitution through +callback checking, tuple/record results, closures, and constructor aliases. + +Constructors use the same site policy. `C[T]` and `new C[T]` instantiate at their +explicit type application, including when term arguments remain to be supplied. +Without explicit arguments, an unapplied `new C` waits for its first term +application; a saturated zero-list `new C` invokes construction at the `new` site. +Later lists and aliases reuse the selected group. Array spreads observe that +element parameter, including bounds inferred from inserted values. + +`newres/StoredSpecializations.mls` covers independent specializations, deferred +record results, curried calls, and unused arity errors. These behavioral tests +must keep passing when ownership moves to specialization. The compiler-level +allocation checks must change too: two explicit specializations allocate two +groups even when unused; repeated calls through one specialization allocate only +one group for that scheme. Add corresponding by-name function/method cases and +verify shared mutation, independently quantified results, and replay under marks. + +`newres/ConstructorInstances.mls` still contains two failing regressions. Reconstruction with `class Box[T](val item: T) with { fun copy() = new Box[T](item) }` must retain the receiver's view of `T` separately from the new constructor's view; the first of two distinct receiver calls currently loses its resulting element @@ -685,19 +745,15 @@ private fun use[B](f: Item -> B): B = f(Item(9)) use(identity[Item]).value ``` -The expected result is `9`. Checking the argument against `Item -> B` must observe -`identity`'s inferred result with `A` referring to the supplied `Item`. Currently, -the deferred body view can map `A` only to a call-site parameter symbol, and no -call to `identity` has been observed at this point. Its result therefore fails to -constrain `B`. Allocating an instance during callback checking or writing `Item` -into the shared original `A` would violate the design. +The expected result is `9`. `identity[Item]` itself supplies the authoritative +instantiation site. Checking it against `Item -> B` can therefore observe the +inferred result under `A -> A_specialization`, with `Item` already attached to +that instance. No call of the specialized value needs to have been observed. -The proposed extension, pending review, is to let deferred views also reference -supplied type-argument nodes. Such references must remain shared graph edges: -recursively substituting `DeclaredType.bindings` maps into one another would lose -the finite-domain argument below. The representation must support deferred tuple -fields and closures as well as this scalar result. This regression remains a -`:fixme` until that representation and its termination invariant are settled. +This example does not require deferred views that map binders directly to +supplied type expressions. Use the existing finite map to parameter instances; +do not introduce that broader representation solely to repair this regression. Shared reference graphs remain necessary for compound +arguments and recursive environments, independently of this instantiation fix. ### Constraint propagation and implementation order @@ -705,7 +761,10 @@ fields and closures as well as this scalar result. This regression remains a including links to inferred portions. Distinguish inference holes from intentionally abstract types and variance wildcards. Add a consumer-owned cache of complete call-site instances, with origin links back - to the declared binders. Keep omissions source-owned; do not add mutable caches + to the declared binders. Select the authoritative site before observing the + value: explicit specialization and by-name invocation consume the definition's + scheme, and later applications preserve its group. Fix the annotated by-name + shared-storage soundness regression alongside the allocation-count tests. Keep omissions source-owned; do not add mutable caches to symbols. Preserve `Forall` binders and their bounds instead of erasing them. 2. Transport the memoized substitution through callable and result views before expanding instance wrappers. Keep the existing marks for value flow and @@ -790,7 +849,9 @@ must retain its selected definition and interpreted argument references. ### Fixed points and implementation checks There are at most as many allocated parameter instances as the sum of each -reachable definition's binder count over its syntactic call sites. Source hole +reachable definition's binder count over its syntactic instantiation sites. +Explicit type applications and implicit by-name invocation sites are finite source +occurrences too; observing them repeatedly does not add sites. Source hole nodes and written type/term nodes are finite independently of recursive visits. View substitutions range over the finite original and instantiated binders in lexical scope. With the existing no-repeated-boundary mark invariant, their @@ -833,16 +894,15 @@ are finitely many endpoint/map/mark combinations, and replay adds no listeners. That local bound does not establish the remaining whole-graph alias-environment bound, which must also prove that source references cannot proliferate. -Constructor recipes contain source type-argument nodes and flat binder maps, -not expanded argument candidates. There are finitely many such recipes per source -`new` expression. Their shape cache may distinguish recipes, but binder allocation -continues to use only the original class and term application site. Constructor -argument subscriptions are memoized by recipe and flat instance map before their -arguments are observed. `TypeInstantiationTest` checks zero allocation for bare -specialization and unapplied `new`, distinct groups for separate applications, -reuse across curried tails, and allocation for saturated zero-list construction. -These checks bound this constructor machinery; they do not establish the remaining -whole-graph alias bound or repair the contextual failures above. +Constructor views retain source type-argument nodes and flat binder maps, not +expanded argument candidates. Binder allocation must use the original class and +the authoritative instantiation site. Constructor argument subscriptions are +memoized before observing their arguments. The current `TypeInstantiationTest` +assertions expecting zero groups for explicit specialization or a fresh group for +each later call describe the old implementation and must be replaced with the +site counts above. Reusing groups across curried tails and saturated zero-list +construction remains required. These local allocation checks do not establish +the remaining whole-graph alias bound or repair reconstructed receiver contexts. For omitted arguments, each source type-use/formal-position pair allocates one `TypeShape.Hole` host. No recursive traversal or call allocates a parameter symbol @@ -866,8 +926,11 @@ requires a separate concrete example and proposal. - Two calls using different arrays and incompatible element interfaces remain independent, including two callers of the recursive example above. - A repeated visit to the same definition/site reuses its parameter symbols; - two application sites through one alias obtain distinct instances. An inner - site shared by different enclosing activations remains distinguished by marks. + two applications through an uninstantiated ordinary function alias obtain + distinct instances, whereas calls through one explicit specialization reuse + its group. By-name references/selections instantiate before exposing their + results, without re-generalizing shared mutable state. An inner site shared by + different enclosing activations remains distinguished by marks. - Inline and separate polymorphic signatures instantiate consistently, with or without explicit call-site type arguments. Partial applications retain their substitutions and captured outer binders retain their original contexts. diff --git a/hkmc2/shared/src/test/mlscript/newres/InstantiationSites.mls b/hkmc2/shared/src/test/mlscript/newres/InstantiationSites.mls new file mode 100644 index 0000000000..2042dad418 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/InstantiationSites.mls @@ -0,0 +1,69 @@ +:. + +// A by-name invocation allocates storage before the returned closure is called. +// Later calls through one stored result share that storage and its type instance. +:expect [1, 2] +private fun make[A] = + let cells = mut [] + (value: A) => + cells.push(value) + cells.length +private val shared = make +[shared(1), shared(2)] +//│ = [1, 2] +//│ shared = fun + +// Distinct by-name invocation sites create distinct storage, even in one class. +:expect [1, 1, 2] +class Factory with + fun make[A] = + let cells = mut [] + (value: A) => + cells.push(value) + cells.length +private val factory = new Factory +private val left = factory.make +private val right = factory.make +[left(1), right(2), left(3)] +//│ = [1, 1, 2] +//│ factory = Factory +//│ left = fun +//│ right = fun + +// One stored result cannot acquire independent element types on its later calls. +// The second invocation returns the first inserted value, which has no .second. +:ge +class First(val first: Int) +class Second(val second: Int) +private fun make[A] = + let cells = mut [] + (value: A) => + cells.push(value) + cells.0 +private val shared = make +shared(First(1)) +shared(Second(2)).second +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.45: shared(Second(2)).second +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.44: shared(First(1)) +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ shared = fun + +// An annotated result must not re-generalize the by-name definition's binder. +// Both calls use one allocated array; the result of the second is still First. +:breakme +:e +class First(val first: Int) +class Second(val second: Int) +private fun make[A]: A -> A = + let cells = mut [] + (value: A) => + cells.push(value) + cells.0 +private val shared = make +shared(First(3)) +shared(Second(4)).second +//│ shared = fun diff --git a/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls b/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls index af1fa1611a..f9bdf2b9a3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls +++ b/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls @@ -1,6 +1,6 @@ :. -// Specializations through one shared reference bind only their eventual call instances. +// Explicit specializations through one shared reference remain independent. :expect [1, 2] class First(val first: Int) class Second(val second: Int) @@ -28,7 +28,7 @@ private val second = shared[Second] //│ second = fun delayed //│ shared = fun delayed -// Curried tails keep the first application's specialization and inferred arguments. +// Curried tails retain their specialization and captured argument flows. :expect [5, 6, 7, 8] class First(val first: Int) class Second(val second: Int) @@ -44,7 +44,7 @@ private val right = shared[Second](Second(7)) //│ shared = fun pair // Callback checking must observe the specialization before an implementation call. -// TODO: Deferred inferred body views need references to supplied type arguments. +// TODO: Instantiate at the type application before checking the callback. :fixme :expect 9 class Item(val value: Int) From 304f7ab12a5c12f6c3b05a89109cd39c591d07b0 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 10:49:44 +0800 Subject: [PATCH 105/228] Clarify regular structural types and remaining termination obligations --- doc/new-resolution-suite-migration.md | 3 +- doc/new-resolution-type-value-flow.md | 83 ++++++++++++++++--- .../mlscript/newres/TypeGraphTermination.mls | 4 +- 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 0e24781ff7..920722e989 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -91,7 +91,8 @@ The inventory below separates these from compiler and prelude gaps. authoritative instantiation at explicit type applications and by-name invocations, specialized inferred results observed before term application, supplied member-input constraints, reconstructed receiver contexts, inferred missing member types, - and the whole graph's termination bound. Concrete outstanding cases are in + regular structural interfaces, and the whole graph's termination bound. + Concrete outstanding cases are in `newres/InstantiationSites.mls`, `StoredSpecializations.mls`, `ConstructorInstances.mls`, `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. Decide how reassignment affects mutable storage's inferred interface, including diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 3a53de2b2b..3a7033a002 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -35,11 +35,16 @@ order](#constraint-propagation-and-implementation-order). Correcting precedence over extending deferred value views to arbitrary supplied-type references: an explicit type application already provides a site and parameter instances for those views. The [distinction between type-reference operations and -value-scope crossings](#type-reference-scope-audit-proposal-requiring-review) -remains a proposal requiring review. Recursive reference graphs must preserve -caller and receiver contexts without growing binding environments. The variance +value-scope crossings](#type-reference-scope-audit) is approved. Recursive reference +graphs must preserve caller and receiver contexts without growing binding environments. The variance substitution rules below are implemented for declared type views. +Require structural types to have a finite recursive representation: the proposed +[regularity restriction](#regular-structural-types) rules out structural unfolding +that keeps producing distinct types. The exact check remains to be reviewed before +implementation. This restriction and sharing regular recursive references are +separate obligations; neither follows from the bound on call-site symbols. + The finite call-site symbol bound alone is not a termination proof for the whole resolver. Recursive alias regressions currently overflow, and the [fixed-point conditions](#fixed-points-and-implementation-checks) remain completion gates. @@ -792,7 +797,7 @@ references), `NewResolverState.scala` (consumer-owned memo tables), (transporting views through deferred children). Elaborator signature registration must preserve binders and missing positions before the body is observed. -### Type-reference scope audit (proposal requiring review) +### Type-reference scope audit The remaining graph work has a concrete termination regression in `newres/TypeGraphTermination.mls`: @@ -812,7 +817,7 @@ value-flow paths themselves violate the finite-mark premise. Even the finite sel Both constraint expansion and mark paths need validation, independently of runtime recursion depth. -The proposed correction distinguishes these operations: +The approved correction distinguishes these operations: - Expanding a type alias substitutes references to its arguments in its source type graph. The alias declaration introduces no value invocation. Keep each @@ -826,13 +831,14 @@ The proposed correction distinguishes these operations: mark operations. These paths must remain on references to captured parameters, even when aliases or structural fields forward them. -This is a proposal about which operations cross a value scope, not permission to +This approval concerns which operations cross a value scope, not permission to truncate repeated marks, widen an alias, or allocate more inference variables. -It must be reviewed before changing the type interpreter. Validation must include +The interpreter has not yet been changed. Validation must include nested aliases inside generic functions and classes, fields containing callbacks, -separate uses of one recursive alias, and imported annotations. All three +separate uses of one recursive alias, and imported annotations. The two regular regression blocks must eventually pass without `:fixme`, with mark checks enabled and -stable relation/listener counts. Fixing the mark paths alone is insufficient if +stable relation/listener counts. The expanding third block needs the regularity +diagnostic described below. Fixing the mark paths alone is insufficient if `DeclaredType.bindings` can still construct unbounded nested environments. The same graph audit must distinguish an open definition template from an already @@ -846,6 +852,60 @@ legacy constructions can have value shapes without a new-resolution producer, and completed reference targets must remain immutable. A saved graph operation must retain its selected definition and interpreted argument references. +### Regular structural types + +The proposed restriction is that unfolding a structural type must admit a finite +graph of distinct type components, with recursive occurrences represented by +back-edges. A finite alias definition alone does not establish this property: + +```mlscript +type Chain[A] = {value: A, next: Chain[A]} +type Growing[A] = {value: A, next: Growing[Array[A]]} +``` + +`Chain[Int]` repeats the same interface and can be represented by a cycle. +`Growing[Int]` exposes successive `value` types `Int`, `Array[Int]`, +`Array[Array[Int]]`, and so on. It is non-regular and should be diagnosed rather +than expanded indefinitely or silently approximated. The third block of +`newres/TypeGraphTermination.mls` currently records this latter failure as a stack +overflow; it is no longer an acceptance case for unrestricted structural recursion. + +Regularity is not a requirement that recursive arguments be textually unchanged. +For example, `{value: A, next: Alternating[B, A]}` as the body of +`Alternating[A, B]` has a finite two-state unfolding. Mutually recursive aliases +and finite changes of arguments must also be considered. The precise check, +including how it handles aliases and inferred holes, remains a design review +item. It must terminate on rejected inputs too; waiting for an unfolding cache to +stop growing is not a decision procedure. Do not impose a depth limit. + +There is also a representation obligation for accepted types. Currently +`DeclaredType.bindings` is a recursive map of `DeclaredType` values, and the whole +reference is used as a view-cache key. `declaredType` substitutes a directly bound +parameter, but retains the environment for a compound expression. Schematically, +repeatedly binding `A` to the source expression `Array[A]` can build: + +```text +E0 = {A -> Int} +E1 = {A -> (Array[A], E0)} +E2 = {A -> (Array[A], E1)} +... +``` + +The source expression and binder identities stay fixed while the environments +grow. This is a separate risk identified from the representation, not a claim +that it is the sole cause of the existing stack overflows: mark assertions fail +on those regressions first. Removing synthetic value-scope crossings does not +bound these environments. Accepted regular types need canonical recursive +references and finite keys, including their caller contexts, rather than a fresh +nested environment on each visit. + +Keep structural alias unfolding distinct from inferred constraints at a recursive +generic function call. The latter reuses a site's parameter symbol and can add an +edge from the source `Array[A]` expression to that symbol without eagerly +substituting its accumulated bounds. A regularity restriction on written +structural interfaces does not by itself establish or replace the finite-domain +argument for that constraint propagation. + ### Fixed points and implementation checks There are at most as many allocated parameter instances as the sum of each @@ -856,8 +916,9 @@ nodes and written type/term nodes are finite independently of recursive visits. View substitutions range over the finite original and instantiated binders in lexical scope. With the existing no-repeated-boundary mark invariant, their normalized contexts also range over a finite domain. These facts bound the set -of memoized views and endpoint pairs **provided** compound arguments remain shared -graph edges and no cache key includes a growing substitution or capture history. +of memoized views and endpoint pairs **provided** structural interfaces satisfy +the regularity restriction, compound arguments remain shared graph edges and no +cache key includes a growing substitution or capture history. Monotone, deduplicated propagation then reaches a fixed point. Check this at the graph layer as well as with worksheets. Replaying an existing diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index ea7874b546..e257c2a3d3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -14,8 +14,8 @@ private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () //│ /!!!\ Uncaught error: java.lang.StackOverflowError -// Changing recursive arguments must retain symbolic edges, not grow mark paths -// or expand Array[Array[...]] environments at each recursive comparison. +// This non-regular structural alias must eventually receive a regularity diagnostic: +// its value types expand to Array[Array[...]]. It must not overflow during checking. :fixme type Chain[A] = {value: A, next: Chain[Array[A]]} private fun walk[A](chain: Chain[A], n: Int) = From 1092faa67c1ef25639f1f9b31f5efd96ab290528 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 10:59:29 +0800 Subject: [PATCH 106/228] Instantiate schemes at specialization and by-name invocation sites --- doc/new-resolution-design.md | 35 +-- doc/new-resolution-suite-migration.md | 3 +- doc/new-resolution-type-value-flow.md | 109 ++++---- .../src/test/scala/hkmc2/CompilerTest.scala | 7 +- .../scala/hkmc2/TypeInstantiationTest.scala | 52 +++- .../scala/hkmc2/semantics/Elaborator.scala | 11 +- .../scala/hkmc2/semantics/NewResolver.scala | 249 ++++++++++++------ .../hkmc2/semantics/NewResolverState.scala | 9 + .../main/scala/hkmc2/semantics/Shape.scala | 4 +- .../mlscript/newres/InstantiationSites.mls | 62 ++++- .../mlscript/newres/StoredSpecializations.mls | 20 +- 11 files changed, 376 insertions(+), 185 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 5215af9b7a..ba9cd83432 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -36,8 +36,8 @@ exported mapped arrays and imported generic functions and methods. `InstanceShape` retains a type reference at annotation boundaries; member lookup, application, and destructuring obtain specialized interfaces through `listenInstanceViews`. The distinction between supplying a type argument and -adding an ordinary bound, the agreed variance rules, and the proposal to instantiate -declared type parameters once per definition and syntactic call site are documented +adding an ordinary bound, variance rules, and instantiating declared type parameters +once per definition and authoritative syntactic site are documented in [Instance types and parameter constraints](new-resolution-type-value-flow.md). The description below records current generic inference. Omitted generic arguments use source-owned inference holes and retain live inference through partial @@ -70,11 +70,11 @@ Inference also connects nested nominal parameters, such as `Foo[A]` containing a `Box[A]`. Generic methods use the same flow as free functions, including callback-result inference and curried signatures. Declared callable shapes retain their type parameters. Functions and -constructors allocate explicit binders once per original definition and syntactic -term application in the current implementation. This delays explicit specialization -and by-name invocation too far: the [instantiation-site rules](new-resolution-type-value-flow.md#finite-call-site-instantiation-and-marks) -require those operations to own their groups immediately and later applications -to reuse them. Subsequent curried lists retain the selected instance map. +constructors allocate explicit binders once per original definition and authoritative +syntactic site: explicit type application, by-name invocation, or otherwise the +first term application. The [instantiation-site rules](new-resolution-type-value-flow.md#finite-call-site-instantiation-and-marks) +require later applications and curried tails to retain that group. Ordinary +references to functions with parameter lists keep their schemes until instantiated. Nominal argument comparisons retain directed `ContextualType` endpoint pairs. Invariant arguments install both directions, rather than copying expanded @@ -114,16 +114,17 @@ written binders. `newres/InferenceHoles.mls` also retains unresolved cases for recursive holes and omissions shared through an alias body. No hole creates a call-site parameter instance. -Standalone specializations of inferred functions currently retain supplied types -in `SpecializedShape` until application, which then binds its own parameter -instances. This must change: the specialization owns the group and consumes that -scheme; subsequent uses retain it. By-name functions and methods likewise -instantiate at their implicit invocation before their result is observed. Argument -arity is checked even for unused specializations. `newres/StoredSpecializations.mls` -covers stored aliases, inferred record results, and curried calls. It also records -the remaining gap in observing an inferred specialized result during callback -checking before application; see the -[type-flow reference](new-resolution-type-value-flow.md#partial-application-and-explicit-specialization). +Standalone specializations of inferred functions retain their consumed group in +`SpecializedShape`; complete callable signatures remove their scheme after +instantiation. By-name functions and methods instantiate before observing their +result. Their explicitly supplied type arguments select the same invocation's +site, without also allocating a group for the underlying reference. Separate +definition signatures consume their binders too; independently quantified result +annotations retain their own schemes. Argument arity is checked even for unused +specializations. `newres/StoredSpecializations.mls` covers stored aliases, inferred +record results, curried calls, and callback checking before a term application. +`newres/InstantiationSites.mls` checks shared mutation and implicit invocations; +see the [type-flow reference](new-resolution-type-value-flow.md#partial-application-and-explicit-specialization). ## Declared interfaces and exposure checking diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 920722e989..fffb7b0a71 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -88,8 +88,7 @@ The inventory below separates these from compiler and prelude gaps. [resolver notes](new-resolution-design.md). Tracking individual positions and lengths is out of scope. Complete the [type-argument constraint design](new-resolution-type-value-flow.md), including - authoritative instantiation at explicit type applications and by-name invocations, - specialized inferred results observed before term application, supplied member-input + supplied member-input constraints, reconstructed receiver contexts, inferred missing member types, regular structural interfaces, and the whole graph's termination bound. Concrete outstanding cases are in diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 3a7033a002..04c41b993d 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -30,11 +30,10 @@ The following semantic decisions are settled: marks. Tracking positions and lengths is outside this design. The implementation plan is in [constraint propagation and implementation -order](#constraint-propagation-and-implementation-order). Correcting -[instantiation sites](#partial-application-and-explicit-specialization) takes -precedence over extending deferred value views to arbitrary supplied-type -references: an explicit type application already provides a site and parameter -instances for those views. The [distinction between type-reference operations and +order](#constraint-propagation-and-implementation-order). The +[instantiation-site implementation](#partial-application-and-explicit-specialization) +uses the existing finite maps to parameter instances for deferred value views: +an explicit type application already provides their site and parameter group. The [distinction between type-reference operations and value-scope crossings](#type-reference-scope-audit) is approved. Recursive reference graphs must preserve caller and receiver contexts without growing binding environments. The variance substitution rules below are implemented for declared type views. @@ -52,27 +51,28 @@ conditions](#fixed-points-and-implementation-checks) remain completion gates. ## Implementation status Instance wrappers and preservation of quantified signature binders and bounds -are implemented. Explicit type application also observes annotated polymorphic -values. The implementation still delays explicit specialization until a term -application and can preserve a by-name definition's binders on its result; both -must be corrected to follow the instantiation-site rules below. Calls through complete callable signatures use the bounded binder-instance -cache. `DeclaredType.instances` carries a flat substitution through their declared -components; curried tails retain it, and independently quantified returned callables -instantiate their own binders. Stored specializations of complete signatures and -inferred functions retain supplied arguments until application. Inline binders on partial and inferred -functions also instantiate at application sites. Their shared bodies retain flat -substitutions through deferred tuples, records, callbacks, and closures. Nominal -argument comparisons retain both endpoint references and apply declaration/use-site -variance, including substitution inside nested function and nominal types. -The explicit-binder recursive array acceptance cases pass. Supplied function arguments -receive input obligations while retaining their declared output interface. -Constructors now select their binder groups at the first term application too, -including stored aliases and explicit `new`. Observing specialized inferred -functions before application, replacing the remaining explicit-argument flags -and positive-only member conversion, reconstructed receiver contexts, inferred -nominal member interfaces, and general recursive alias environments still need -integration. Omitted arguments use source-owned inference holes; recursive hole -contexts and omissions inside +are implemented. Explicit type applications consume schemes before term arguments +are supplied. By-name references and selections consume their definition's binders +before observing its result; explicit arguments choose the site without also +instantiating the underlying reference. Separate definition signatures follow the +same rule, while independently quantified result annotations retain their schemes. +Functions and constructors share the bounded binder-instance cache. Stored +specializations and curried tails retain their groups. Allocation-count tests +cover unused specializations, repeated calls, by-name methods through nominal +views, and distinct invocation sites. + +`DeclaredType.instances` carries a flat substitution through declared components. +Shared inferred bodies retain substitutions through deferred tuples, records, +callbacks, and closures. Nominal argument comparisons retain both endpoint +references and apply declaration/use-site variance, including substitution inside +nested function and nominal types. The explicit-binder recursive array acceptance +cases pass. Supplied function arguments receive input obligations while retaining +their declared output interface. + +Replacing the remaining explicit-argument flags and positive-only member +conversion, reconstructed receiver contexts, inferred nominal member interfaces, +and general recursive alias environments still need integration. Omitted arguments +use source-owned inference holes; recursive hole contexts and omissions inside alias bodies still need the contextual reference work described below. The implementation order below remains the full design, not a claim that all its parts are complete. @@ -685,21 +685,20 @@ calls must all use that invocation's instance of `A`. Giving each call to types. Separate expressions `let left = make` and `let right = make` are separate invocation sites. `newres/InstantiationSites.mls` checks shared storage, independent by-name method invocations, and rejection of an incompatible result selection -through an inferred closure. Its annotated variant, `make[A]: A -> A`, currently -accepts `shared(First(3)); shared(Second(4)).second` (written on separate lines in -the test), although the second call returns the stored `First`. That case is a -`:breakme` soundness regression: a result annotation must not re-generalize the -by-name definition's binder. The same rule applies to explicitly specialized +through both inferred and annotated closures. The annotated variant, +`make[A]: A -> A`, rejects `shared(First(3)); shared(Second(4)).second` (written on +separate lines in the test), since the second call returns the stored `First`. +A result annotation must not re-generalize the by-name definition's binder. The same rule applies to explicitly specialized `make[T]`. Only a separately quantified result scheme retains its own binders, subject to ordinary generic-body checking. -The existing `SpecializedShape` delays supplied arguments until a term call and -`appShape` then allocates a group. Replace that recipe with a value whose scheme -has already been instantiated at the type application. The same distinction is -needed for complete callable signatures and inferred bodies: a shape carrying -captured instances alone must not be mistaken for a consumed scheme, nor may a -consumed scheme be instantiated again. Preserve the chosen substitution through -callback checking, tuple/record results, closures, and constructor aliases. +`SpecializedShape` retains the group consumed by a type application of an inferred +definition. Complete callable signatures remove their scheme when instantiated. +Both distinguish a consumed scheme from an open scheme carrying lexical captures; +later calls must not instantiate the consumed scheme again. The substitution is +retained through callback checking, tuple/record results, closures, and constructor +aliases. Elaboration defers observing the base reference of a type application +until its arguments are available, preventing a second by-name instantiation. Constructors use the same site policy. `C[T]` and `new C[T]` instantiate at their explicit type application, including when term arguments remain to be supplied. @@ -709,12 +708,12 @@ Later lists and aliases reuse the selected group. Array spreads observe that element parameter, including bounds inferred from inserted values. `newres/StoredSpecializations.mls` covers independent specializations, deferred -record results, curried calls, and unused arity errors. These behavioral tests -must keep passing when ownership moves to specialization. The compiler-level -allocation checks must change too: two explicit specializations allocate two -groups even when unused; repeated calls through one specialization allocate only -one group for that scheme. Add corresponding by-name function/method cases and -verify shared mutation, independently quantified results, and replay under marks. +record results, curried calls, and unused arity errors. Compiler-level allocation +checks assert that two explicit specializations allocate two groups even when +unused, whereas repeated calls through one specialization allocate only one group +for that scheme. By-name function/method cases, separate signatures, independently +quantified results, shared mutation, and specialization inside generic bodies are +also covered. Broader graph convergence remains a separate completion gate. `newres/ConstructorInstances.mls` still contains two failing regressions. Reconstruction with `class Box[T](val item: T) with { fun copy() = new Box[T](item) }` @@ -750,7 +749,7 @@ private fun use[B](f: Item -> B): B = f(Item(9)) use(identity[Item]).value ``` -The expected result is `9`. `identity[Item]` itself supplies the authoritative +This regression now returns `9`. `identity[Item]` itself supplies the authoritative instantiation site. Checking it against `Item -> B` can therefore observe the inferred result under `A -> A_specialization`, with `Item` already attached to that instance. No call of the specialized value needs to have been observed. @@ -768,8 +767,9 @@ arguments and recursive environments, independently of this instantiation fix. consumer-owned cache of complete call-site instances, with origin links back to the declared binders. Select the authoritative site before observing the value: explicit specialization and by-name invocation consume the definition's - scheme, and later applications preserve its group. Fix the annotated by-name - shared-storage soundness regression alongside the allocation-count tests. Keep omissions source-owned; do not add mutable caches + scheme, and later applications preserve its group. The annotated by-name + shared-storage regression and allocation-count tests now pass. Keep omissions + source-owned; do not add mutable caches to symbols. Preserve `Forall` binders and their bounds instead of erasing them. 2. Transport the memoized substitution through callable and result views before expanding instance wrappers. Keep the existing marks for value flow and @@ -781,8 +781,8 @@ arguments and recursive environments, independently of this instantiation fix. Apply the InvalML variance rules above. 4. Complete supplied-argument references and replace explicit-argument suppression and the positive-only conversion in `instanceBindings` together. Functions - and constructors now share call-site binder allocation and retain specialization - recipes; their remaining input/output obligations must use the same reference + and constructors share instantiation-site binder allocation and retain consumed + groups; their remaining input/output obligations must use the same reference mechanism as nominal arguments and declared array interfaces. 5. Route declared member lookup to partial member schemes, retaining receiver contexts for inferred fields and results. Check override compatibility before @@ -958,11 +958,12 @@ bound, which must also prove that source references cannot proliferate. Constructor views retain source type-argument nodes and flat binder maps, not expanded argument candidates. Binder allocation must use the original class and the authoritative instantiation site. Constructor argument subscriptions are -memoized before observing their arguments. The current `TypeInstantiationTest` -assertions expecting zero groups for explicit specialization or a fresh group for -each later call describe the old implementation and must be replaced with the -site counts above. Reusing groups across curried tails and saturated zero-list -construction remains required. These local allocation checks do not establish +memoized before observing their arguments. `TypeInstantiationTest` checks the +site counts above, including reuse across curried tails and saturated zero-list +construction. `typeApplicationSite` caches one site identity per source `TyApp` +node across activation views. It allocates neither a new source occurrence nor a +type-parameter group on repeated observation; by-name references reuse their +existing reference/selection site identity. These local allocation checks do not establish the remaining whole-graph alias bound or repair reconstructed receiver contexts. For omitted arguments, each source type-use/formal-position pair allocates one diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index 1b83aa8bf3..f7d8ee0de7 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -374,8 +374,11 @@ class CompilerTest extends AnyFunSuite: assert(calls.length == 1) val state = unit.state.newResolverState assert(state.allocatedTypeInstanceCount == 1) - val instance = state.instantiateTypeParameters(definition.tsym, calls.head.resSym, parameter :: Nil)(parameter) - assert(state.allocatedTypeInstanceCount == 1, "Observation must reuse the application's binder") + val site = calls.head.lhs match + case application: Term.TyApp => state.typeApplicationSite(application) + case _ => fail("Expected an explicit type application") + val instance = state.instantiateTypeParameters(definition.tsym, site, parameter :: Nil)(parameter) + assert(state.allocatedTypeInstanceCount == 1, "Observation must reuse the specialization's binder") assert(!parameter.shapes.exists: case Marked(_: InstanceShape, _) => true case _ => false diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala index 3a118797bc..98f956ed19 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala @@ -35,23 +35,23 @@ class TypeInstantiationTest extends AnyFunSuite: | val second = shared("two") |""".stripMargin) == 2) - test("constructor instances belong to the first term application, including stored new recipes"): + test("constructor instances belong to explicit specializations or unspecialized term applications"): val cases = List( """|class Box[T](val item: T) |private val specialized = Box[Int] |private val deferred = new Box[Int] - |""".stripMargin -> 0, + |""".stripMargin -> 2, """|class Box[T](val item: T) |private val shared = Box |private val specialized = shared[Int] |private val direct = specialized(1) |private val explicit = new specialized(2) - |""".stripMargin -> 2, + |""".stripMargin -> 1, """|class Box[T](val item: T) |private val deferred = new Box[Int] |private val first = deferred(1) |private val second = deferred(2) - |""".stripMargin -> 2, + |""".stripMargin -> 1, """|class Box[T](val item: T)(val other: Int) |private val partial = new Box[Int](1) |private val first = partial(2) @@ -66,6 +66,50 @@ class TypeInstantiationTest extends AnyFunSuite: withClue(source): assert(allocatedInstances(source) == count) + test("specializations consume binders before any later calls or observations"): + val definitions = List( + "private fun identity[A](x: A) = x", + "private fun identity[A](x: A): A = x") + definitions.foreach: definition => + val source = definition + "\nprivate val specialized = identity[Int]\n" + assert(allocatedInstances(source) == 1) + assert(allocatedInstances(source + + "private val first = specialized(1)\nprivate val second = specialized(2)\n") == 1) + + test("by-name invocations consume binders once, with explicit arguments choosing the site"): + val definitions = List( + "private fun make[A] = (x: A) => x", + "private fun make[A]: A -> A = (x: A) => x", + "private fun make: [A] -> A -> A\nprivate fun make = x => x") + definitions.foreach: definition => + List("make", "make[Int]").foreach: invocation => + val source = definition + "\nprivate val shared = " + invocation + "\n" + withClue(source): + assert(allocatedInstances(source) == 1) + assert(allocatedInstances(source + + "private val first = shared(1)\nprivate val second = shared(2)\n") == 1) + assert(allocatedInstances(source + "private val other = " + invocation + "\n") == 2) + + test("selected by-name methods retain the invocation's group through nominal views"): + List("factory.make", "factory.make[Int]").foreach: invocation => + val source = """|class Factory with + | fun make[A]: A -> A = (x: A) => x + |private val factory: Factory = new Factory + |""".stripMargin + "private val shared = " + invocation + "\n" + withClue(source): + assert(allocatedInstances(source) == 1) + assert(allocatedInstances(source + + "private val first = shared(1)\nprivate val second = shared(2)\n") == 1) + assert(allocatedInstances(source + "private val other = " + invocation + "\n") == 2) + + test("a separately quantified result retains its own scheme after a by-name invocation"): + val source = """|private fun make: [A] -> A -> A = x => x + |private val shared = make + |""".stripMargin + assert(allocatedInstances(source) == 0) + assert(allocatedInstances(source + + "private val first = shared(1)\nprivate val second = shared(2)\n") == 2) + test("a definition and static site allocate their binder group once"): given owner: Elaborator.State = new Elaborator.State val state = owner.newResolverState diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 1882e0fadf..ada8abfbd7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -482,7 +482,7 @@ object Elaborator: * a runtime value merely because they use the same reference syntax. */ enum Interpretation: - case Trm, Receiver, Clss, Ptrn, Tpe + case Trm, Receiver, Specialization, Clss, Ptrn, Tpe enum Mode: case Full @@ -719,7 +719,7 @@ end Elaborator import Elaborator.* -import Elaborator.Interpretation.{Trm, Receiver, Clss, Ptrn, Tpe} +import Elaborator.Interpretation.{Trm, Receiver, Specialization, Clss, Ptrn, Tpe} class Elaborator(val tl: TraceLogger, val wd: io.Path, val prelude: Ctx) @@ -1380,7 +1380,12 @@ extends Importer: raise(ErrorReport(msg"Name not found: $name" -> id.toLoc :: Nil)) error case TyApp(lhs, targs) => - val result = Term.TyApp(subterm(lhs, interp), targs.map { + // The whole type application owns a by-name invocation's binder group. + // Do not observe its underlying reference before its arguments are available. + val baseInterp = interp match + case Trm | Receiver | Specialization => Specialization + case _ => interp + val result = Term.TyApp(subterm(lhs, baseInterp), targs.map { case Modified(Keywrd(Keyword.`in`), arg) => Term.WildcardTy(S(subterm(arg, Tpe)), N) case Modified(Keywrd(Keyword.`out`), arg) => Term.WildcardTy(N, S(subterm(arg, Tpe))) case Tup(Modified(Keywrd(Keyword.`in`), arg1) :: Modified(Keywrd(Keyword.`out`), arg2) :: Nil) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 48999b913b..5cd254f8b4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -426,6 +426,19 @@ class NewResolver: , instances) case _ => (value, Map.empty) + /** Operations on a specialized declaration reuse its consumed binder group. + * Keep that distinction separate from lexical captures of an open scheme. + */ + private def callableParts(value: TermShape): (TermShape, Map[VarSymbol, TypeParameterInstance], Bool) = + val (original, captured) = contextualParts(value) + original match + case Marked(specialized: SpecializedShape, marks) => + val declaration = marks match + case NoMarks => specialized.declaration + case marks: SomeMarks => MarkedShape(specialized.declaration, marks) + (declaration, captured ++ specialized.instances, true) + case _ => (original, captured, false) + private def listenTypeViews(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = typeViews.get(tpe) match case S(host) => host.listen(listener) @@ -565,22 +578,26 @@ class NewResolver: }) private def listenDeclaredMember(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], flow: FlowSymbol, - source: Term, annotation: Opt[Term], selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool, positive: Bool)(listener: Listener)(using NewResolverState): Unit = + source: Term, annotation: Opt[Term], selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool, positive: Bool, + specialization: Opt[(FlowSymbol, Ls[DeclaredType])])(listener: Listener)(using NewResolverState): Unit = member.onComplete: () => valueTarget(member, receiver) match case S(symbol: TermSymbol) if !symbol.isInstanceOf[ClassCtorSymbol] => selected(symbol) val td = symbol.defn.get + val callerInstances = rstate.instances + val instances = if isByName(td) then instantiateByName(td, flow, + ExitMark(ResolutionBoundary(td.tsym), S(flow), NoMarks) :: Nil, specialization) else callerInstances // A selected generic method binds its own parameters anew. Receiver // bindings can mention an earlier call of that same source method in // their values, but cannot bind the new method's local names. - val capturedBindings = bindings -- td.tparams.toList.flatten.map(_.sym) + val capturedBindings = bindings -- definitionBinders(td) def publish(shape: TermShape)(using NewResolverState): Unit = val generic = shape match - case callable: CallableTypeShape => + case callable: CallableTypeShape if !isByName(td) => val parameters = td.tparams.toList.flatten.map(p => declaredParameter(p.sym)) ::: callable.tparams callable.copy(scheme = if parameters.isEmpty then N else S(TypeScheme(td.tsym, parameters)), declaration = S(td)) - case instance: InstanceShape => + case instance: InstanceShape if !isByName(td) => InstanceShape(quantifiedType(instance.tpe, td.tparams.toList.flatten.map(_.sym))) case _ => shape // A synthesized field's signature is in the constructor parameter's @@ -589,7 +606,7 @@ class NewResolver: case S(_: Param) => generic case _ => MarkedShape.exit(generic, ResolutionBoundary(td.tsym), S(flow)) exited match - case value: TermShape => listener(value) + case value: TermShape => listener(instantiateShape(value, instances))(using rstate.withInstances(callerInstances)) case NoShape => () def signature(sign: Term, polarity: Bool)(using NewResolverState): DeclaredType = // Legacy annotations have plain references instead of Capture nodes. @@ -602,9 +619,10 @@ class NewResolver: if !td.tsym.decl.exists(_.isInstanceOf[Param]) then visit(sign) val scopedBindings = capturedBindings.map: (symbol, bound) => symbol -> (if legacyParameters(symbol) then captureType(bound, td.tsym) else bound) - declaredType(typeResolution(sign), scopedBindings, polarity) + val tpe = declaredType(typeResolution(sign), scopedBindings, polarity) + if isByName(td) then tpe.instantiate(instances) else tpe val result = resultSignature(td).map(signature(_, positive)) - if td.sign.nonEmpty && (td.k is syntax.Fun) && !td.flags.hasResultAnnotation then + if td.sign.nonEmpty && (td.k is syntax.Fun) && !td.flags.hasResultAnnotation && !isByName(td) then listenTypeViews(signature(td.sign.get, positive))(publish) else if td.params.nonEmpty then publish(CallableTypeShape(source, @@ -625,7 +643,7 @@ class NewResolver: case _ => // A nested nominal declaration denotes its statically selected symbol; // selecting it does not inspect an instance field or method body. - fromBMS(member, flow, Nil, listener, source, selected, receiver) + fromBMSAt(member, flow, Nil, listener, source, selected, receiver, specialization) def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])])(using rs: NewResolverState): Unit = rs.report: ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) @@ -674,8 +692,8 @@ class NewResolver: parameters.zip(arguments).foreach: (parameter, argument) => publishParameter(parameter.host, InstanceShape(argument).enter(marks)) - /** The first term application owns the binder group. Curried tails carry the - * instantiated references and no scheme, so applying a later list reuses it. + /** Consume the scheme at its authoritative site. Specialized values and curried + * tails carry instantiated references and no scheme, so later calls reuse it. */ private def instantiateCallable(callable: CallableTypeShape, site: FlowSymbol, marks: Ls[Marks])(using NewResolverState): CallableTypeShape = callable.scheme match @@ -838,7 +856,7 @@ class NewResolver: case MemberLookup.Found(member, inner) => fromBMS(member.memberSymbol, flow, inner, value => receive(instantiateShape(value, instances)), field.rhs, _ => (), false)(using rstate.withInstances(instances)) case MemberLookup.Declared(member, bound, inner, annotation, positive) => - listenDeclaredMember(member, bound, flow, field.rhs, annotation, _ => (), false, positive): value => + listenDeclaredMember(member, bound, flow, field.rhs, annotation, _ => (), false, positive, N): value => instantiateShape(value, instances).exit(inner) match case value: TermShape => receive(value) case NoShape => () @@ -855,7 +873,7 @@ class NewResolver: * calls through that interface continue to expose only the declared result. */ private[semantics] def constrainFunction(expected: DeclaredType, source: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = - val (actual, instances) = contextualParts(source) + val (actual, instances, _) = callableParts(source) actual match case Marked(_: InstanceShape, _) => listenInstanceViews(actual)(constrainFunction(expected, _, marks)) @@ -1284,7 +1302,7 @@ class NewResolver: fromBMS(sym, resSym, marks, field => matchShapePat(instantiateShape(field, instances), pat)(_ => ()), ctor.target, _ => (), false) case MemberLookup.Declared(member, bindings, marks, annotation, positive) => - listenDeclaredMember(member, bindings, resSym, ctor.target, annotation, _ => (), false, positive): field => + listenDeclaredMember(member, bindings, resSym, ctor.target, annotation, _ => (), false, positive, N): field => listenInstanceViews(instantiateShape(field, instances)): field => field.exit(marks) match case value: TermShape => @@ -1417,21 +1435,25 @@ class NewResolver: def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern)(using NewResolverState): Unit = if newResolution then listenTerm(scrutinee)(sh => matchShapePat(sh, pattern)(_ => ())) - /** A definition's first term application selects its binder group. Both class - * interpretations use the original class owner; subsequent constructor lists - * retain the selected group through their contextual result shape. + /** Both class interpretations use the original class as their scheme owner. + * Instantiated views must never become new owners of the same source binders. */ private def definitionParameters(definition: DefnShape): (AnyDefinitionSymbol, Ls[VarSymbol]) = definition.clsDef match case S(cls) => (cls.sym, cls.tparams.map(_.sym)) case N => definition.defn match - case td: TermDefinition => (td.tsym, td.tparams.toList.flatten.map(_.sym)) + case td: TermDefinition => (td.tsym, definitionBinders(td)) case _ => lastWords("A callable definition must have a class or term owner") private def instantiateDefinition(definition: DefnShape, site: FlowSymbol, marks: Ls[Marks], captured: Map[VarSymbol, TypeParameterInstance], supplied: Opt[Ls[DeclaredType]]) (using NewResolverState): Map[VarSymbol, TypeParameterInstance] = val (owner, parameters) = definitionParameters(definition) + instantiateParameters(owner, parameters, site, marks, captured, supplied) + + private def instantiateParameters(owner: AnyDefinitionSymbol, parameters: Ls[VarSymbol], site: FlowSymbol, + marks: Ls[Marks], captured: Map[VarSymbol, TypeParameterInstance], supplied: Opt[Ls[DeclaredType]]) + (using NewResolverState): Map[VarSymbol, TypeParameterInstance] = val substitution = captured ++ rstate.instantiateTypeParameters(owner, site, parameters) supplied.foreach: arguments => bindTypeArguments(parameters.map: parameter => @@ -1449,20 +1471,13 @@ class NewResolver: def appShape(source: TermShape, args: Term, res: App)(using NewResolverState): Unit = val callerInstances = rstate.instances - val (original, outer) = contextualParts(source) - val (lhs, lexical, supplied) = original match - case Marked(specialized: SpecializedShape, marks) => - val declaration = marks match - case NoMarks => specialized.declaration - case marks: SomeMarks => MarkedShape(specialized.declaration, marks) - (declaration, outer ++ specialized.instances, S(specialized.arguments)) - case _ => (original, outer, N) + val (lhs, lexical, specialized) = callableParts(source) val captured = callerInstances ++ lexical val instances = lhs match - case Marked(definition: DefnShape, _) => - instantiateDefinition(definition, res.resSym, lhs.applicationHead._2, captured, supplied) - case Marked(construction: NewShape, _) if construction.argss.isEmpty && !construction.isSaturated => - instantiateDefinition(construction.receiver, res.resSym, lhs.applicationHead._2, captured, construction.supplied) + case Marked(definition: DefnShape, _) if !specialized => + instantiateDefinition(definition, res.resSym, lhs.applicationHead._2, captured, N) + case Marked(construction: NewShape, _) if construction.argss.isEmpty && !construction.isSaturated && construction.supplied.isEmpty => + instantiateDefinition(construction.receiver, res.resSym, lhs.applicationHead._2, captured, N) case _ => captured def publish(value: TermShape)(using NewResolverState): Unit = publishViewed(res, value, instances)(using rstate.withInstances(callerInstances)) @@ -1701,7 +1716,11 @@ class NewResolver: resolError(trm, msg"Class '${ref.definition.sym.nme}' expected ${count} type ${ "argument".pluralized(count)}, but got ${args.length}" -> ref.definition.toLoc :: Nil) val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) - select(ref.copy(supplied = ref.supplied.orElse(S(supplied)))) + if ref.supplied.nonEmpty then select(ref) + else + val instances = instantiateParameters(ref.definition.sym, ref.definition.tparams.map(_.sym), + rstate.typeApplicationSite(application), ref.marks, rstate.instances ++ ref.instances, S(supplied)) + select(ref.copy(instances = instances, supplied = S(supplied))) , reject) case Capture(base, thru) => listenClass(base)(ref => select(ref.copy(marks = @@ -1828,10 +1847,10 @@ class NewResolver: listenExt(cd, extsh => val dsh = constructorShape(cd, extsh) val captured = callerInstances ++ ref.instances - // An unapplied constructor is a recipe. Its first later application - // selects the group; a saturated zero-list construction selects it here. - val instances = if nw.args.isEmpty && dsh.unappliedParams.nonEmpty then captured - else instantiateDefinition(dsh, nw.resSym, marks, captured, ref.supplied) + // Explicit type arguments already consumed this scheme. Otherwise an + // unapplied constructor waits for its first term application. + val instances = if ref.supplied.nonEmpty || (nw.args.isEmpty && dsh.unappliedParams.nonEmpty) then captured + else instantiateDefinition(dsh, nw.resSym, marks, captured, N) val sh = newShapes.getOrElseUpdate((cd.sym, marks, nw.resSym, ref.supplied), NewShape(dsh, cd.sym, marks, nw.args, nw, ref.supplied)) if rstate.constructorApplications.add((sh, instances)) then @@ -1911,18 +1930,50 @@ class NewResolver: .toMap private def fromSymbol(shape: SymShape, listener: Listener, source: Term, - selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(using NewResolverState): Unit = shape match + selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(using NewResolverState): Unit = + fromSymbolAt(shape, listener, source, selected, receiver, N) + + private def fromSymbolAt(shape: SymShape, listener: Listener, source: Term, + selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool, + specialization: Opt[(FlowSymbol, Ls[DeclaredType])])(using NewResolverState): Unit = shape match case contextual: ContextualSymShape => - fromSymbol(contextual.source, value => listener(instantiateShape(value, contextual.instances)), source, selected, receiver)(using rstate.withInstances(contextual.instances)) + fromSymbolAt(contextual.source, value => listener(instantiateShape(value, contextual.instances)), + source, selected, receiver, specialization)(using rstate.withInstances(contextual.instances)) case declared: DeclaredSymShape => - listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, declared.annotation, selected, receiver, declared.positive): value => + listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, declared.annotation, selected, receiver, declared.positive, specialization): value => value.exit(shape.markss) match case value: TermShape => listener(value) case NoShape => () - case _ => fromBMS(shape.sym, shape.resSym, shape.markss, listener, source, selected, receiver) + case _ => fromBMSAt(shape.sym, shape.resSym, shape.markss, listener, source, selected, receiver, specialization) def fromBMS(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: Listener, - trm: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(using NewResolverState) = + trm: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool)(using NewResolverState): Unit = + fromBMSAt(bms, resSym, markss, listener, trm, selected, receiver, N) + + // External declarations with no written parameter lists derive their calling + // convention from the signature. An ordinary parameterless function executes + // at its reference, before any returned callable receives term arguments. + private def isByName(td: TermDefinition): Bool = + (td.k is syntax.Fun) && td.params.isEmpty && Annot.declareModifierOf(td.annotations).isEmpty + + /** A separate signature quantifies the definition. A quantified result + * annotation instead belongs to the returned value and remains unconsumed. + */ + private def definitionBinders(td: TermDefinition): Ls[VarSymbol] = + def signatureBinders(sign: Term): Ls[VarSymbol] = sign match + case Forall(parameters, _, body) => parameters.map(_.sym) ::: signatureBinders(body) + case _ => Nil + td.tparams.toList.flatten.map(_.sym) ::: + (if td.flags.hasResultAnnotation then Nil else td.sign.toList.flatMap(signatureBinders)) + + private def instantiateByName(td: TermDefinition, site: FlowSymbol, marks: Ls[Marks], + specialization: Opt[(FlowSymbol, Ls[DeclaredType])])(using NewResolverState): Map[VarSymbol, TypeParameterInstance] = + instantiateParameters(td.tsym, definitionBinders(td), specialization.fold(site)(_._1), + marks, rstate.instances, specialization.map(_._2)) + + private def fromBMSAt(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: Listener, + trm: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool, + specialization: Opt[(FlowSymbol, Ls[DeclaredType])])(using NewResolverState): Unit = log(s"listenBMS: bms = ${bms.describe}") bms.onComplete: () => log(s"listenedBMS: bms = ${bms.describe}") @@ -1952,14 +2003,22 @@ class NewResolver: sym.defn match case S(td: TermDefinition) if td.params.isEmpty => log(s"listenTerm: td.body = ${td.body.fold("N")(_.showDbg)}") + val callerInstances = rstate.instances + val instances = if isByName(td) then instantiateByName(td, resSym, + ExitMark(ResolutionBoundary(td.tsym), S(resSym), NoMarks) :: markss, specialization) + else callerInstances -- td.tparams.toList.flatten.map(_.sym) + def receive(shape: TermShape)(using NewResolverState): Unit = + wrappedListener(instantiateShape(shape, instances))(using rstate.withInstances(callerInstances)) resultSignature(td) match - case S(sign) => listenTypeInstances(quantifiedType( - declaredType(typeResolution(sign), Map.empty), td.tparams.toList.flatten.map(_.sym))): shape => - td.tsym.decl match - case S(_: Param) => wrappedListener(MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N)) - case _ => wrappedListener(shape) + case S(sign) => + val result = declaredType(typeResolution(sign), Map.empty).instantiate(instances) + val tpe = if isByName(td) then result else quantifiedType(result, td.tparams.toList.flatten.map(_.sym)) + listenTypeInstances(tpe): shape => + td.tsym.decl match + case S(_: Param) => receive(MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N)) + case _ => receive(shape) case N => td.body.foreach: body => - listenTerm(body): shape => + listenTerm(body)(shape => // Legacy synthesized fields use a plain reference to their // constructor parameter. Supply the field capture explicitly; // new-resolution fields already carry it in their body syntax. @@ -1967,12 +2026,13 @@ class NewResolver: case (S(_: Param), Ref(_: VarSymbol)) => MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N) case _ => shape - wrappedListener(captured) + receive(captured) + )(using rstate.withInstances(instances)) case S(d: TermDefinition) if !d.tsym.isInstanceOf[ClassCtorSymbol] && ((d.sign.nonEmpty && (!d.flags.hasResultAnnotation || d.params.forall(ps => (ps.params ::: ps.restParam.toList).forall(_.sign.nonEmpty)))) || (d.body.isEmpty && d.tsym.owner.exists(_.asDefnSym.defn.exists(_.tparams.nonEmpty)))) => - listenDeclaredMember(bms, instanceBindings(d, markss), resSym, trm, N, selected, receiver, true): value => + listenDeclaredMember(bms, instanceBindings(d, markss), resSym, trm, N, selected, receiver, true, specialization): value => value.exit(markss) match case value: TermShape => listener(value) case NoShape => () @@ -2072,12 +2132,13 @@ class NewResolver: case _ => listen(trm): case sh: TermShape => listener(sh) case ss: SymShape => - fromSymbol(ss, listener, trm, sym => - trm.withoutCaptures match - case ref: NewResolvable => - rstate.recordResolution(ref, ref.resolvedTargets.contains(sym))(ref.resolvedTargets ::= sym) - case _ => () - , receiver) + fromSymbol(ss, listener, trm, recordValueTarget(trm, _), receiver) + + private def recordValueTarget(term: Term, symbol: DefinitionSymbol[?])(using NewResolverState): Unit = + term.withoutCaptures match + case ref: NewResolvable => + rstate.recordResolution(ref, ref.resolvedTargets.contains(symbol))(ref.resolvedTargets ::= symbol) + case _ => () /** Subscribe to spread operands once for each tuple or record AST node. Record * the node before calling start: a recursive spread can call listen on the same @@ -2123,42 +2184,56 @@ class NewResolver: if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then resolError(trm, msg"${callee.describe.capitalize} expected ${count} type ${ "argument".pluralized(count)}, but got ${args.length}" -> callee.toLoc :: Nil) - listen(underlying, discardMarks): shape => - def instantiate(value: TermShape)(using NewResolverState): Unit = listenInstanceViews(value): viewed => - val (actual, captured) = contextualParts(viewed) - actual.applicationHead match - case (specialized: SpecializedShape, _) => - checkArity(specialized, 0) - listener(value) - case (callable: CallableTypeShape, _) if callable.supplied.nonEmpty => - checkArity(callable, 0) - listener(value) - case (callable: CallableTypeShape, marks) => - checkArity(callable, callable.tparams.length) + def instantiate(value: TermShape)(receive: Listener)(using NewResolverState): Unit = listenInstanceViews(value): viewed => + val (actual, captured) = contextualParts(viewed) + actual.applicationHead match + case (specialized: SpecializedShape, _) => + checkArity(specialized, 0) + receive(value) + case (callable: CallableTypeShape, marks) => + checkArity(callable, callable.tparams.length) + val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) + instantiateCallable(callable.copy(supplied = S(supplied)), + rstate.typeApplicationSite(application), marks).exit(marks) match + case value: TermShape => receive(value) + case NoShape => () + case (callee: DefnShape, marks) => + val (_, parameters) = definitionParameters(callee) + // The first consumed parameter list has already bound this scheme. + // Looking through an application's head must not turn its result + // (especially a constructed object) back into a generic function. + val unapplied = actual match + case Marked(_: DefnShape, _) => true + case Marked(construction: NewShape, _) => construction.argss.isEmpty && !construction.isSaturated + case _ => false + checkArity(callee, if unapplied then parameters.length else 0) + if !unapplied then receive(value) + else val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) - callable.copy(supplied = S(supplied)).exit(marks) match - case value: TermShape => listener(value) + val instances = instantiateDefinition(callee, rstate.typeApplicationSite(application), marks, + rstate.instances ++ captured, S(supplied)) + SpecializedShape(callee, supplied, instances).exit(marks) match + case specialized: TermShape => receive(specialized) case NoShape => () - case (callee: DefnShape, marks) => - val (_, parameters) = definitionParameters(callee) - // The first consumed parameter list has already bound this scheme. - // Looking through an application's head must not turn its result - // (especially a constructed object) back into a generic function. - val unapplied = actual match - case Marked(_: DefnShape, _) => true - case Marked(construction: NewShape, _) => construction.argss.isEmpty && !construction.isSaturated - case _ => false - checkArity(callee, if unapplied then parameters.length else 0) - if !unapplied then listener(value) - else - val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) - SpecializedShape(callee, supplied, captured).exit(marks) match - case specialized: TermShape => listener(specialized) - case NoShape => () - case _ => listener(value) - shape match - case sym: SymShape => fromSymbol(sym, instantiate, underlying, _ => (), false) - case value: TermShape => instantiate(value) + case _ => receive(value) + // Preserve lexical captures while supplying the invocation's arguments + // before a by-name reference publishes its result. Observing the result + // first would either consume the scheme twice or specialize a result scheme. + def observe(base: Term)(receive: Listener)(using NewResolverState): Unit = base match + case Capture(inner, thru) => + observe(inner)(shape => receive(if discardMarks then shape else MarkedShape.enter(shape, ResolutionBoundary(thru), N))) + case _ => listen(base, discardMarks): + case sym: SymShape => sym.sym.onComplete: () => + valueTarget(sym.sym, false).flatMap(_.defn) match + case S(td: TermDefinition) if isByName(td) => + val definition = defnShapes.getOrElseUpdate(td.tsym, DefnShape(td, N)) + checkArity(definition, definitionBinders(td).length) + val arguments = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) + fromSymbolAt(sym, receive, base, recordValueTarget(base, _), false, + S((rstate.typeApplicationSite(application), arguments))) + case _ => fromSymbol(sym, value => instantiate(value)(receive), base, recordValueTarget(base, _), false) + case value: TermShape => instantiate(value)(receive) + observe(underlying)(listener) case mut @ Mut(underlying: Tup) => listener(mutableArray(mut, underlying)) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => listenAggregate(tuple, listener): publish => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 21e8c09ac3..e9ea7eb0c2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -224,6 +224,15 @@ final class NewResolverState private ( new Cache(inherited.map(_.typeInstances), identity) private var allocatedTypeInstances: Int = 0 private[hkmc2] def allocatedTypeInstanceCount: Int = root.allocatedTypeInstances + private val typeApplicationSites: Cache[Identity[TyApp], FlowSymbol] = + new Cache(inherited.map(_.typeApplicationSites), identity) + /** A type application consumes a scheme even when no term argument follows. + * Its site is shared across observations and activation views of the source. + */ + def typeApplicationSite(application: TyApp): FlowSymbol = + val key = new Identity(application) + root.typeApplicationSites.getOrElseUpdate(key, typeApplicationSites.get(key).getOrElse( + FlowSymbol("type application")(using owner))) private[hkmc2] def instantiateTypeParameters(scheme: AnyDefinitionSymbol | TypeResolution, site: FlowSymbol, parameters: Ls[VarSymbol]): Map[VarSymbol, TypeParameterInstance] = require(parameters.distinct.length == parameters.length, "A scheme cannot bind a parameter twice") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 5c543f0ef9..5060c1a49e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -420,8 +420,8 @@ final case class ContextualShape(source: NonMarkedShape, instances: Map[VarSymbo override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = source.isInstanceOfClass(cls) protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = source.getMember(name).instantiate(instances) -/** A standalone type application is a recipe, not a call activation. Each later - * application uses its own binder group while retaining these caller references. +/** A type application has consumed the declaration's scheme. Subsequent term + * applications retain this binder group and the supplied caller references. */ final case class SpecializedShape(declaration: DefnShape, arguments: Ls[DeclaredType], instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: diff --git a/hkmc2/shared/src/test/mlscript/newres/InstantiationSites.mls b/hkmc2/shared/src/test/mlscript/newres/InstantiationSites.mls index 2042dad418..ef684fa4cc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InstantiationSites.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InstantiationSites.mls @@ -54,7 +54,6 @@ shared(Second(2)).second // An annotated result must not re-generalize the by-name definition's binder. // Both calls use one allocated array; the result of the second is still First. -:breakme :e class First(val first: Int) class Second(val second: Int) @@ -66,4 +65,65 @@ private fun make[A]: A -> A = private val shared = make shared(First(3)) shared(Second(4)).second +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.67: shared(Second(4)).second +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.66: shared(First(3)) +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ shared = fun + +// Explicit arguments instantiate the invocation, before the returned closure is used. +:expect [5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun make[A]: A -> A = (value: A) => value +private val left = make[First] +private val right = make[Second] +[left(First(5)).first, right(Second(6)).second] +//│ = [5, 6] +//│ left = fun +//│ right = fun + +// A selected by-name method consumes its own binders while retaining its receiver. +:expect [7, 8] +class First(val first: Int) +class Second(val second: Int) +class Factory with + fun make[A]: A -> A = (value: A) => value +private val factory = new Factory +private val left = factory.make[First] +private val right = factory.make[Second] +[left(First(7)).first, right(Second(8)).second] +//│ = [7, 8] +//│ factory = Factory +//│ left = fun +//│ right = fun + +// An explicit invocation inside a generic body retains its enclosing captures. +:expect [9, 10] +class First(val first: Int) +class Second(val second: Int) +private fun make[A]: A -> A = (value: A) => value +private fun wrap[T](value: T) = make[T](value) +[wrap[First](First(9)).first, wrap[Second](Second(10)).second] +//│ = [9, 10] + +// Separate signatures bind the by-name definition, not its returned closure. +:e +class First(val first: Int) +class Second(val second: Int) +private fun make: [A] -> A -> A +private fun make = value => value +private val shared = make +shared(First(11)) +shared(Second(12)).second +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.121: shared(Second(12)).second +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.120: shared(First(11)) +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ shared = fun diff --git a/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls b/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls index f9bdf2b9a3..13018d4d2b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls +++ b/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls @@ -44,18 +44,12 @@ private val right = shared[Second](Second(7)) //│ shared = fun pair // Callback checking must observe the specialization before an implementation call. -// TODO: Instantiate at the type application before checking the callback. -:fixme :expect 9 class Item(val value: Int) private fun identity[A](value: A) = value private fun use[B](f: Item -> B): B = f(Item(9)) use(identity[Item]).value -//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.53: use(identity[Item]).value -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '9', got: 'undefined' +//│ = 9 // Supplying the scheme's arguments consumes its type parameter list immediately. :ge @@ -66,16 +60,16 @@ private val second = declared[Int] first[Str] second[Str] //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.66: first[Str] +//│ ║ l.60: first[Str] //│ ║ ^^^^^^^^^^ //│ ╟── Function 'inferred' expected 0 type arguments, but got 1 -//│ ║ l.62: private fun inferred[A](value: A) = value +//│ ║ l.56: private fun inferred[A](value: A) = value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.67: second[Str] +//│ ║ l.61: second[Str] //│ ║ ^^^^^^^^^^^ //│ ╟── Function 'declared' expected 0 type arguments, but got 1 -//│ ║ l.63: private fun declared[A](value: A): A = value +//│ ║ l.57: private fun declared[A](value: A): A = value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = fun declared //│ first = fun inferred @@ -86,9 +80,9 @@ second[Str] private fun identity[A](value: A) = value private val tooMany = identity[Int, Str] //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.87: private val tooMany = identity[Int, Str] +//│ ║ l.81: private val tooMany = identity[Int, Str] //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'identity' expected 1 type argument, but got 2 -//│ ║ l.86: private fun identity[A](value: A) = value +//│ ║ l.80: private fun identity[A](value: A) = value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ tooMany = fun identity From 4c4074f5f908e885ffa77ce361f13425101b12b2 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 11:08:28 +0800 Subject: [PATCH 107/228] Keep type alias and structural field projections outside value scopes --- doc/new-resolution-design.md | 8 +++ doc/new-resolution-type-value-flow.md | 69 ++++++++++++------- .../src/test/scala/hkmc2/CompilerTest.scala | 24 +++++++ .../test/scala/hkmc2/TypeRelationTest.scala | 34 +++++++++ .../scala/hkmc2/semantics/NewResolver.scala | 17 +++-- .../src/main/scala/hkmc2/semantics/Term.scala | 12 ++-- .../mlscript/newres/TypeGraphTermination.mls | 51 ++++++++++++-- 7 files changed, 175 insertions(+), 40 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index ba9cd83432..40cadbcf48 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -44,6 +44,14 @@ use source-owned inference holes and retain live inference through partial signatures. Recursive hole contexts, alias-body omissions, and missing nominal member types still need the contextual reference work described in that document. +Type-alias qualification and structural type-field projection introduce no value +scope. Alias captures forward type references, and structural fields retain their +written types without the synthetic boundaries used for value-field lookup. +The tested direct recursive aliases and finite parameter permutations now pass. +A regular alias that resets its argument to `Int` still grows unused binding +environments; non-regular expansion and class-local alias receiver contexts also +remain incomplete. + Class bodies introduce lexical captures, and a class and its constructor share one resolution boundary. A method's reference to an outer constructor must include the enclosing instance boundary as well as the method boundary. Consuming a diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 04c41b993d..b64df374fc 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -45,8 +45,8 @@ implementation. This restriction and sharing regular recursive references are separate obligations; neither follows from the bound on call-site symbols. The finite call-site symbol bound alone is not a termination proof for the whole -resolver. Recursive alias regressions currently overflow, and the [fixed-point -conditions](#fixed-points-and-implementation-checks) remain completion gates. +resolver. Both non-regular expansion and a regular argument-reset alias still +overflow; class-local aliases expose a receiver-context failure. The [fixed-point conditions](#fixed-points-and-implementation-checks) remain completion gates. ## Implementation status @@ -808,14 +808,12 @@ private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () ``` -Compilation currently overflows the stack, even without a call to `walk`. -The variant whose `next` is simply `Chain[A]` fails too. Enabling the existing -`checkMarkPaths` assertions exposes repeated exits before the stack overflows. -Thus this failure is not evidence only of growing argument environments: -value-flow paths themselves violate the finite-mark premise. Even the finite selection `chain.next.next.value` in a function with a written -`Int` result currently overflows; that third regression needs no recursive call. -Both constraint expansion and mark paths need validation, independently of runtime -recursion depth. +This non-regular variant still overflows the stack without any call to `walk`. +With `checkMarkPaths` enabled it reports repeated exits first, so its failure is +not evidence only of growing argument environments. The regular variant with +`next: Chain[A]` and the finite selection `chain.next.next.value` now pass, +including with mark checks enabled. Both constraint expansion and mark paths need +validation independently of runtime recursion depth. The approved correction distinguishes these operations: @@ -833,13 +831,27 @@ The approved correction distinguishes these operations: This approval concerns which operations cross a value scope, not permission to truncate repeated marks, widen an alias, or allocate more inference variables. -The interpreter has not yet been changed. Validation must include -nested aliases inside generic functions and classes, fields containing callbacks, -separate uses of one recursive alias, and imported annotations. The two regular -regression blocks must eventually pass without `:fixme`, with mark checks enabled and -stable relation/listener counts. The expanding third block needs the regularity -diagnostic described below. Fixing the mark paths alone is insufficient if -`DeclaredType.bindings` can still construct unbounded nested environments. +The interpreter now forwards alias qualifications without a value capture, and +structural field declarations retain their written type without a synthetic +capture/exit. Worksheets cover regular recursion, finite parameter permutations, +mutually recursive aliases, function-local aliases, and structural callbacks. +An imported recursive alias is checked through two independent consumer modules. +These cases pass with mark checks enabled. A graph-level structural cycle test +also checks late bounds and verifies that repeated relations add no listeners or +parameter instances. + +The class-local alias case remains a `:fixme`: projecting the result of +`Box[A].get(): Slot`, where `Slot = {item: A}`, loses the correspondence between +the method's scope and its receiver's scope. This also fails without the scope +correction. Closed type interfaces need their deferred child references observed +in the appropriate context; adding an entry to every closed interface instead +breaks regular alias traversal. The whole-graph proof must address this context +transport, and existing repeated-mark failures in generic array-method paths, +not just the now-passing regular alias examples. + +The expanding alias needs the regularity diagnostic described below. The regular +argument-reset regression also overflows: fixing mark paths is insufficient while +`DeclaredType.bindings` retains nested, unused environments. The same graph audit must distinguish an open definition template from an already interpreted reference. In `Box[T].copy() = new Box[T](item)`, the source `T` in the @@ -892,12 +904,23 @@ E2 = {A -> (Array[A], E1)} ``` The source expression and binder identities stay fixed while the environments -grow. This is a separate risk identified from the representation, not a claim -that it is the sole cause of the existing stack overflows: mark assertions fail -on those regressions first. Removing synthetic value-scope crossings does not -bound these environments. Accepted regular types need canonical recursive -references and finite keys, including their caller contexts, rather than a fresh -nested environment on each visit. +grow. Removing synthetic value-scope crossings does not bound these environments. +There is now a concrete regular counterexample in `TypeGraphTermination.mls`: + +```mlscript +type Reset[A] = {value: A, next: Reset[Int]} +fun read(chain: Reset[Str]): Int = chain.next.next.value +``` + +The unfolding has only the `Reset[Str]` and `Reset[Int]` interfaces, but compilation +still overflows. The closed `Int` reference retains the previous binding map, +allowing semantically unused environments to nest. This case must be accepted; +a regularity restriction cannot legitimately reject it. Canonical references +must discard bindings that their source does not depend on, while retaining +lexical dependencies such as the enclosing `A` in a class-local alias. This must +also work for forward references: an unresolved dependency must not be mistaken +for an absent one. Accepted regular types need finite reference keys and shared +recursive edges, not a fresh nested environment on each visit. Keep structural alias unfolding distinct from inferred constraints at a recursive generic function call. The latter reuses a site's parameter symbol and can add an diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index f7d8ee0de7..ec41fe7ee3 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -345,6 +345,30 @@ class CompilerTest extends AnyFunSuite: assert(parameters.map(p => (p.shapes.toVector, p.shapeListeners.toVector)) == before, "A consumer must not change the cached definition's inference graph") + test("imported recursive structural aliases preserve their type arguments"): + val fs = new InMemoryFileSystem(loadStandardLibrary()) + given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace = false + given Raise = diagnostic => fail(diagnostic.toString) + fs.write("/Types.mls", """#lang(0.3.x, strictResolution: true) + |module Types with + | type Chain[A] = {value: A, next: Chain[A]} + | fun step[A](chain: Chain[A]): Chain[A] = chain.next + |""".stripMargin) + val compiler = new MLsCompiler(_ => summon[Raise]) + compiler.compileModule(Path("/Types.mls")) + List("first", "second").foreach: field => + val path = s"/$field.mls" + fs.write(path, s"""#lang(0.3.x, strictResolution: true) + |import "./Types.mls" + |class Item(val $field: Int) + |private fun read(chain: Types.Chain[Item]): Int = + | Types.step[Item](chain).next.value.$field + |""".stripMargin) + compiler.compileModule(Path(path)) + test("a supplied compound type stays inside one instance wrapper"): import semantics.{InstanceShape, Marked, Statement, Term, TermShape, TypeShape} val fs = new InMemoryFileSystem(loadStandardLibrary()) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 419a5b670f..ab90fd2d0f 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -56,6 +56,40 @@ class TypeRelationTest extends AnyFunSuite: assert((a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) assert(seen.toList == List(first, second)) + test("recursive structural projections preserve bounds and reuse their listeners"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val (b, bt) = h.parameter("B") + def recursiveRecord(element: DeclaredType): DeclaredType = + val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val value = Term.UnitVal() + value.typeInterpretation = S(element.resolution) + val next = Term.UnitVal() + next.typeInterpretation = S(resolution) + val valueField = RcdField.signature(Term.Lit(Tree.StrLit("value")), value) + val nextField = RcdField.signature(Term.Lit(Tree.StrLit("next")), next) + val source: Term.Rcd = Term.Rcd(false, List(valueField, nextField)) + resolution.publish(TypeShape.Record(source, List(valueField -> element.resolution, nextField -> resolution))) + DeclaredType(resolution, Map.empty, Map.empty, true) + val left = ContextualType(recursiveRecord(at), Nil) + val right = ContextualType(recursiveRecord(bt), Nil) + val first = IntroShape(Term.UnitVal(), N) + val later = DynShape() + h.resolver.publishParameter(a, first) + h.resolver.constrainTypes(left, right) + val seen = h.observe(ContextualType(bt, Nil)) + assert(seen.toList == List(first)) + def counts = List(left.tpe.resolution.inferenceHost.listeners.size, right.tpe.resolution.inferenceHost.listeners.size, + a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) + val before = counts + h.resolver.publishParameter(a, later) + assert(seen.toList == List(first, later)) + (1 to 1000).foreach: _ => + h.resolver.constrainTypes(left, right) + assert(counts == before) + assert(h.state.allocatedTypeInstanceCount == 0) + test("delayed argument selection fixes one endpoint for both constraint directions"): val h = new Harness import h.given diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 5cd254f8b4..9e632b34ee 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -104,6 +104,10 @@ class NewResolver: case _ => result.fail(msg"This reference does not denote a type." -> ref.toLoc :: Nil) result.publish(TypeShape.Abstract) + // An alias's lexical qualification substitutes type references; it + // does not cross an invocation boundary. Captured function and class + // parameters still retain their actual value-scope crossings. + case Capture(base, _: TypeAliasSymbol) => typeResolution(base).listen(result.publish) case Capture(base, thru) => result.publish(TypeShape.Captured(typeResolution(base), thru)) case TyApp(base, args) => result.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) case Forall(params, outer, body) => @@ -585,6 +589,7 @@ class NewResolver: case S(symbol: TermSymbol) if !symbol.isInstanceOf[ClassCtorSymbol] => selected(symbol) val td = symbol.defn.get + val crossesValueScope = !(td.k is syntax.RecordField) && !td.tsym.decl.exists(_.isInstanceOf[Param]) val callerInstances = rstate.instances val instances = if isByName(td) then instantiateByName(td, flow, ExitMark(ResolutionBoundary(td.tsym), S(flow), NoMarks) :: Nil, specialization) else callerInstances @@ -600,11 +605,11 @@ class NewResolver: case instance: InstanceShape if !isByName(td) => InstanceShape(quantifiedType(instance.tpe, td.tparams.toList.flatten.map(_.sym))) case _ => shape - // A synthesized field's signature is in the constructor parameter's - // scope. Written member signatures are in their own definition scope. - val exited = td.tsym.decl match - case S(_: Param) => generic - case _ => MarkedShape.exit(generic, ResolutionBoundary(td.tsym), S(flow)) + // Constructor fields keep their parameter scope; structural fields + // follow their written type directly. Other members leave their own + // definition scope when their declared interface is selected. + val exited = if crossesValueScope then MarkedShape.exit(generic, ResolutionBoundary(td.tsym), S(flow)) + else generic exited match case value: TermShape => listener(instantiateShape(value, instances))(using rstate.withInstances(callerInstances)) case NoShape => () @@ -616,7 +621,7 @@ class NewResolver: def visit(term: Term)(using NewResolverState): Unit = term match case Ref(symbol: VarSymbol) if capturedBindings.contains(symbol) => legacyParameters += symbol case _ => term.subTerms.foreach(visit) - if !td.tsym.decl.exists(_.isInstanceOf[Param]) then visit(sign) + if crossesValueScope then visit(sign) val scopedBindings = capturedBindings.map: (symbol, bound) => symbol -> (if legacyParameters(symbol) then captureType(bound, td.tsym) else bound) val tpe = declaredType(typeResolution(sign), scopedBindings, polarity) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index ce27adc2b2..77255e7087 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -1299,14 +1299,12 @@ object RcdField: val sym = new BlockMemberSymbol(name, Nil) val tsym = TermSymbol(RecordField, N, id, erasedType = N) sym.tsym = S(tsym) - // fromBMS removes a capture mark for tsym when it reads this definition. - // Wrap rhs in Capture to supply that mark, so the entry and exit cancel and - // leave the enclosing call-site marks intact. Lowering evaluates RcdField.rhs; - // it does not evaluate this synthetic definition separately. - val captured = Term.Capture(rhs, tsym) + // Value-field lookup removes the synthetic definition's capture mark, leaving + // enclosing call-site marks intact. A structural type field instead refers + // directly to its written type: projection does not invoke a value definition. tsym.defn = S(TermDefinition(RecordField, sym, tsym, Nil, N, - if signature then S(captured) else N, - if signature then N else S(captured), TermDefFlags.empty, Modulefulness.none, Nil, N)) + if signature then S(rhs) else N, + if signature then N else S(Term.Capture(rhs, tsym)), TermDefFlags.empty, Modulefulness.none, Nil, N)) sym.complete() RcdField(field, rhs, sym) final case class RcdSpread(rcd: Term) extends Statement diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index e257c2a3d3..d8bd40879a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -2,17 +2,13 @@ // A finite observation of a recursive alias has a finite declared interface. // It must also preserve the no-repeated-boundary invariant when mark checks are enabled. -:fixme type Chain[A] = {value: A, next: Chain[A]} fun read(chain: Chain[Int]): Int = chain.next.next.value -//│ /!!!\ Uncaught error: java.lang.StackOverflowError // Relating a recursive structural alias must reach a graph fixed point. -:fixme type Chain[A] = {value: A, next: Chain[A]} private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () -//│ /!!!\ Uncaught error: java.lang.StackOverflowError // This non-regular structural alias must eventually receive a regularity diagnostic: // its value types expand to Array[Array[...]]. It must not overflow during checking. @@ -21,3 +17,50 @@ type Chain[A] = {value: A, next: Chain[Array[A]]} private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () //│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// A regular parameter permutation has two interfaces, not an expanding environment. +class First(val first: Int) +class Second(val second: Int) +type Alternating[A, B] = {value: A, next: Alternating[B, A]} +fun read(chain: Alternating[First, Second]): Int = chain.next.value.second + +// Mutually recursive aliases preserve a finite set of declared interfaces. +type Left[A] = {value: A, next: Right[A]} +type Right[A] = {value: A, next: Left[A]} +fun read(chain: Left[Int]): Int = chain.next.next.value + +// An alias captures its enclosing function's parameter, without an alias scope. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +private fun choose[A](value: A) = + type Pair = {left: A, right: A} + let pair = ({left: value, right: value} as Pair) + pair.right +[choose[First](First(1)).first, choose[Second](Second(2)).second] +//│ = [1, 2] + +// A structural callback's inputs and outputs retain the supplied argument. +:expect 3 +class Item(val value: Int) +type Callback[A] = {run: A -> A} +private fun make[A](value: A): Callback[A] = {run: (x: A) => x} +make[Item](Item(0)).run(Item(3)).value +//│ = 3 + +// A member alias must retain the receiver context through its method result. +:fixme +:expect 4 +class Item(val value: Int) +class Box[A](val value: A) with + type Slot = {item: A} + fun get(): Slot = {item: value} +(new Box[Item](Item(4))).get().item.value +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: class:Box⁰ vs term:Box⁰/get⁰ for NominalInstanceView(class:Item⁰) + +// Resetting an argument to a closed type has a finite structural unfolding. +// The environment retained on Int must not nest the previous alias binding. +:fixme +type Reset[A] = {value: A, next: Reset[Int]} +fun read(chain: Reset[Str]): Int = chain.next.next.value +//│ /!!!\ Uncaught error: java.lang.StackOverflowError From f6483c189688453d78efb55247537abce63855e9 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 11:27:17 +0800 Subject: [PATCH 108/228] Project declared type bindings onto finite source dependencies --- doc/new-resolution-type-value-flow.md | 63 ++++++--- .../test/scala/hkmc2/TypeRelationTest.scala | 32 +++++ .../scala/hkmc2/semantics/Elaborator.scala | 8 ++ .../scala/hkmc2/semantics/NewResolver.scala | 129 +++++++++++++++++- .../hkmc2/semantics/NewResolverState.scala | 17 +++ .../mlscript/newres/TypeGraphTermination.mls | 34 ++++- 6 files changed, 261 insertions(+), 22 deletions(-) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index b64df374fc..a47cf07688 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -45,8 +45,10 @@ implementation. This restriction and sharing regular recursive references are separate obligations; neither follows from the bound on call-site symbols. The finite call-site symbol bound alone is not a termination proof for the whole -resolver. Both non-regular expansion and a regular argument-reset alias still -overflow; class-local aliases expose a receiver-context failure. The [fixed-point conditions](#fixed-points-and-implementation-checks) remain completion gates. +resolver. Non-regular expansion still overflows; class-local aliases expose a +receiver-context failure. Dependency-based binding projection now accepts regular +argument resets, including compound constants and recursively unused arguments. +The [fixed-point conditions](#fixed-points-and-implementation-checks) remain completion gates. ## Implementation status @@ -850,8 +852,10 @@ transport, and existing repeated-mark failures in generic array-method paths, not just the now-passing regular alias examples. The expanding alias needs the regularity diagnostic described below. The regular -argument-reset regression also overflows: fixing mark paths is insufficient while -`DeclaredType.bindings` retains nested, unused environments. +argument-reset regressions now pass after projecting `DeclaredType.bindings` onto +the source type's dependencies. A local nominal annotation whose constructor +field refers to an enclosing function parameter still exposes the receiver-context +failure; it has its own regression beside the class-local alias case. The same graph audit must distinguish an open definition template from an already interpreted reference. In `Box[T].copy() = new Box[T](item)`, the source `T` in the @@ -893,8 +897,8 @@ stop growing is not a decision procedure. Do not impose a depth limit. There is also a representation obligation for accepted types. Currently `DeclaredType.bindings` is a recursive map of `DeclaredType` values, and the whole reference is used as a view-cache key. `declaredType` substitutes a directly bound -parameter, but retains the environment for a compound expression. Schematically, -repeatedly binding `A` to the source expression `Array[A]` can build: +parameter and projects compound references onto their free binders. A genuinely +expanding dependency, such as repeatedly binding `A` to `Array[A]`, can still build: ```text E0 = {A -> Int} @@ -905,22 +909,49 @@ E2 = {A -> (Array[A], E1)} The source expression and binder identities stay fixed while the environments grow. Removing synthetic value-scope crossings does not bound these environments. -There is now a concrete regular counterexample in `TypeGraphTermination.mls`: +The regular argument-reset regression in `TypeGraphTermination.mls` is: ```mlscript type Reset[A] = {value: A, next: Reset[Int]} fun read(chain: Reset[Str]): Int = chain.next.next.value ``` -The unfolding has only the `Reset[Str]` and `Reset[Int]` interfaces, but compilation -still overflows. The closed `Int` reference retains the previous binding map, -allowing semantically unused environments to nest. This case must be accepted; -a regularity restriction cannot legitimately reject it. Canonical references -must discard bindings that their source does not depend on, while retaining -lexical dependencies such as the enclosing `A` in a class-local alias. This must -also work for forward references: an unresolved dependency must not be mistaken -for an absent one. Accepted regular types need finite reference keys and shared -recursive edges, not a fresh nested environment on each visit. +The unfolding has only the `Reset[Str]` and `Reset[Int]` interfaces. Compilation +now terminates: the closed `Int` reference has no free binders, so it retains no +previous binding map. The same holds for a fixed compound argument `Array[Int]`. +Accepted regular types need finite reference keys and shared recursive edges, +not a fresh nested environment on each visit. + +The implemented dependency analysis operates on source `TypeResolution` nodes. +An edge forwards the child's free binders, excluding those bound by an alias or +quantifier. An applied alias forwards an argument's dependencies only when its +body depends on the corresponding formal. All equations are monotone over the +finite set of source binders, so iteration reaches a least fixed point. This also +accepts `Loop[A] = {value: Int, next: Loop[Array[A]]}`: no observable component uses +`A`, and the cycle alone cannot introduce that dependency. Nominal arguments +remain relevant regardless of whether the class's members mention them. + +Nominal declarations conservatively retain all enclosing explicit type binders. +Elaboration records this immutable set before elaborating members, including for +legacy exporters. Consumers read it from the declaring graph. This preserves +parameters used through nested aliases, member annotations, or inferred member +types without making dependency analysis wait on member inference. The class's +own parameters are supplied by its type application, rather than treated as free. + +No summary is finalized while a reachable source type lacks a target. Interface +observation, constraints, and hole exposure wait on shared dependency hosts; +when the target arrives, each observer projects its own saved environment. There +is at most one waiting subscription per source/dependency pair. Completed summaries +are immutable; inference hosts and waiting callbacks use the usual consumer-private +graph copies. Valid written types have one target, while synthesized argument, +selection, and inferred-value nodes retain their own references and do not read +an ambient binding map. The forward-alias worksheet and a graph-level recursive +tuple test check delayed discovery, independent substitutions, and replay without +additional listeners or parameter instances. + +This bounds dependency analysis and eliminates irrelevant environment growth. +It does not yet establish the full representation bound for every regular type, +or diagnose non-regular structural expansion; those remain completion obligations. Keep structural alias unfolding distinct from inferred constraints at a recursive generic function call. The latter reuses a site's parameter symbol and can add an diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index ab90fd2d0f..cdba7aa891 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -90,6 +90,38 @@ class TypeRelationTest extends AnyFunSuite: assert(counts == before) assert(h.state.allocatedTypeInstanceCount == 0) + test("forward type dependencies retain each observer's substitution until the target is known"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val first = IntroShape(Term.UnitVal(), N) + val second = DynShape() + val early = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val late = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + early.publish(TypeShape.Tuple(List(at.resolution, late))) + val left = h.resolver.declaredType(early, Map(a -> h.tpe(TypeShape.Inferred(first)))) + val right = h.resolver.declaredType(early, Map(a -> h.tpe(TypeShape.Inferred(second)))) + val leftSeen = h.observe(ContextualType(left, Nil)) + val rightSeen = h.observe(ContextualType(right, Nil)) + assert(leftSeen.isEmpty && rightSeen.isEmpty) + // Closing a recursive dependency must wake both observers without merging + // their environments or allocating a parameter for the forward reference. + late.publish(TypeShape.Tuple(List(at.resolution, early))) + def firstField(values: ArrayBuffer[TermShape]): Set[TermShape] = values.toList match + case (tuple: TupleShape) :: Nil => tuple.elements.head match + case TupleShape.TypedField(tpe, marks) => h.observe(ContextualType(tpe, marks)).toSet + case _ => fail("Expected a declared tuple field") + case _ => fail("Expected one declared tuple") + assert(firstField(leftSeen) == Set(first)) + assert(firstField(rightSeen) == Set(second)) + (1 to 1000).foreach: _ => + h.resolver.constrainTypes(ContextualType(left, Nil), ContextualType(right, Nil)) + val saturated = (early.inferenceHost.listeners.size, late.inferenceHost.listeners.size) + (1 to 1000).foreach: _ => + h.resolver.constrainTypes(ContextualType(left, Nil), ContextualType(right, Nil)) + assert((early.inferenceHost.listeners.size, late.inferenceHost.listeners.size) == saturated) + assert(h.state.allocatedTypeInstanceCount == 0) + test("delayed argument selection fixes one endpoint for both constraint directions"): val h = new Harness import h.given diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index ada8abfbd7..de32081983 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -116,6 +116,13 @@ object Elaborator: override def toString: Str = s"${parent.fold("")(_.toString+"/")}${outer.showDbg}" lazy val scope: SrcScope = SrcScope(outer, parent.map(_.scope)) + + /** A nominal member can refer to enclosing binders even when its own + * signature does not mention them, for example through a local type alias. + */ + lazy val typeBinders: Set[VarSymbol] = parent.fold(Set.empty[VarSymbol])(_.typeBinders) ++ + env.valuesIterator.flatMap(_.symbol).collect: + case symbol: VarSymbol if symbol.decl.exists(_.isInstanceOf[TyParam]) => symbol def +(local: Str -> Symbol): Ctx = copy(env = env + local.mapSecond(Ctx.RefElem(_))) @@ -2279,6 +2286,7 @@ extends Importer: val sym = members.getOrElse(nme.name, lastWords(s"Symbol not found: ${nme.name}")) val outerCtx = ctx + rstate.lexicalTypeBinders(td.symbol) = ctx.typeBinders var newCtx = S(td.symbol).collectFirst: case s: InnerSymbol => s diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 9e632b34ee..99661187b0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -169,6 +169,123 @@ class NewResolver: registerSignature(rhs) case _ => () + /** Compute free binders on the finite source graph, not on substituted types. + * Each edge can hide binders introduced by an alias or quantifier. Iterating + * finite binder sets reaches a fixed point even for mutually recursive types. + * An unresolved target is a dependency still to discover, never a closed type. + * Valid written types have one target (validation rejects ambiguity). The + * synthetic nodes that can receive more candidates use no ambient bindings. + */ + private def typeDependencies(resolution: TypeResolution)(using NewResolverState) + : Either[Set[TypeResolution], Set[VarSymbol]] = rstate.typeDependencies.get(resolution) match + case S(binders) => R(binders) + case N => + val graph = mutable.LinkedHashMap.empty[TypeResolution, (Set[VarSymbol], Ls[(TypeResolution, Set[VarSymbol])])] + val argumentUses = mutable.Map.empty[TypeResolution, Ls[(TypeResolution, VarSymbol, TypeResolution)]] + val pending = mutable.Set.empty[TypeResolution] + def visit(res: TypeResolution): Unit = if !graph.contains(res) then + graph(res) = (Set.empty, Nil) + rstate.typeDependencies.get(res) match + case S(binders) => graph(res) = (binders, Nil) + case N => + val shapes = res.currentShapes.toList + if shapes.isEmpty then pending += res + var direct = Set.empty[VarSymbol] + var edges = List.empty[(TypeResolution, Set[VarSymbol])] + def edge(child: TypeResolution, hidden: Set[VarSymbol]): Unit = + edges ::= child -> hidden + visit(child) + def child(res: TypeResolution): Unit = edge(res, Set.empty) + shapes.foreach: + case TypeShape.Parameter(symbol, _) => direct += symbol + case TypeShape.Nominal(defn) => + // This immutable declaration metadata belongs to the original + // graph, even when the type was reached through an imported AST. + direct ++= defn.sym.getState.newResolverState.lexicalTypeBinders.get(defn.sym).getOrElse( + lastWords(s"Nominal declaration ${defn.sym.nme} must record its enclosing type binders")) + case TypeShape.Alias(symbol, rhs) => + val bound = symbol.defn.get.tparams.map(_.sym).toSet + rhs.foreach(edge(_, bound)) + case TypeShape.Captured(base, _) => child(base) + case TypeShape.Applied(base, args) => + child(base) + // An alias argument contributes dependencies only if the body + // uses its formal. Solve this with the other equations: a cycle + // such as Loop[A] = {next: Loop[Array[A]]} has no use of A. + def arguments(base: TypeResolution, seen: Set[TypeResolution]): Unit = + if seen(base) then args.foreach(child) else base.currentShapes.toList match + case TypeShape.Captured(inner, _) :: Nil => arguments(inner, seen + base) + case TypeShape.Alias(symbol, S(rhs)) :: Nil => + visit(rhs) + val formals = symbol.defn.get.tparams + args.zipWithIndex.foreach: (arg, index) => + formals.lift(index) match + case S(formal) => + visit(arg) + argumentUses(res) = (rhs, formal.sym, arg) :: argumentUses.getOrElse(res, Nil) + case N => child(arg) + case _ => args.foreach(child) + arguments(base, Set.empty) + case TypeShape.Tuple(fields) => fields.foreach(child) + case TypeShape.Record(_, fields) => fields.foreach((_, field) => child(field)) + case TypeShape.Function(params, result) => child(typeResolution(params)); child(result) + case TypeShape.Polymorphic(params, _, body) => + val bound = params.map(_.parameter.symbol).toSet + params.foreach: param => + param.lower.foreach(edge(_, bound)) + param.upper.foreach(edge(_, bound)) + edge(body, bound) + case TypeShape.Union(left, right) => child(left); child(right) + case TypeShape.Intersection(left, right) => child(left); child(right) + case TypeShape.Wildcard(input, output) => input.foreach(child); output.foreach(child) + // These synthesized nodes retain their own interpreted references + // or inference hosts; none reads the surrounding binding map. + case TypeShape.Argument(_) | TypeShape.SelectedArgument(_, _) | TypeShape.Inferred(_) | + TypeShape.Hole(_) | TypeShape.Unit | TypeShape.Dynamic | TypeShape.Abstract | + TypeShape.Top | TypeShape.Bottom => () + graph(res) = (direct, edges) + visit(resolution) + if pending.nonEmpty then L(pending.toSet) else + val dependencies = mutable.Map.from(graph.iterator.map((res, node) => res -> node._1)) + var changed = true + while changed do + changed = false + graph.foreach: (res, node) => + val direct = argumentUses.getOrElse(res, Nil).foldLeft(node._1): (acc, use) => + if dependencies(use._1)(use._2) then acc ++ dependencies(use._3) else acc + val next = node._2.foldLeft(direct): (acc, edge) => + acc ++ (dependencies(edge._1) -- edge._2) + if next != dependencies(res) then + dependencies(res) = next + changed = true + dependencies.foreach((res, binders) => rstate.typeDependencies(res) = binders) + R(dependencies(resolution)) + + /** Wait for the source graph before observing a substituted interface. The + * retained environment is projected only after every forward dependency has + * a target. Subscriptions and completed summaries are shared by source node; + * callbacks and their hosts remain private to each consuming inference graph. + */ + private def withCanonicalType(tpe: DeclaredType)(listener: ShapeListener[DeclaredType])(using NewResolverState): Unit = + def publish(binders: Set[VarSymbol])(using NewResolverState): Unit = + listener(declaredType(tpe.resolution, tpe.bindings.filter((symbol, _) => binders(symbol)), tpe.positive) + .instantiate(tpe.instances)) + typeDependencies(tpe.resolution) match + case R(binders) => publish(binders) + case L(_) => + rstate.pendingTypeDependencies.get(tpe.resolution) match + case S(host) => host.listen(publish) + case N => + val host = new TypeDependencyHost + rstate.pendingTypeDependencies(tpe.resolution) = host + host.listen(publish) + def retry()(using NewResolverState): Unit = typeDependencies(tpe.resolution) match + case R(binders) => host.publish(binders) + case L(pending) => pending.foreach: unresolved => + if rstate.dependencySubscriptions.add((tpe.resolution, unresolved)) then + unresolved.listen(_ => retry()) + retry() + /** Substitute a bound parameter before storing an argument. Otherwise recursive * interfaces such as Tree[A] = Node[A] | Empty would accumulate equivalent * A-to-A environments every time a recursive field is selected. @@ -179,7 +296,11 @@ class NewResolver: private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], positive: Bool) (using NewResolverState): DeclaredType = resolution.currentShapes.toList match case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) - case _ => DeclaredType(resolution, bindings, Map.empty, positive) + case _ => + val relevant = if bindings.isEmpty then bindings else typeDependencies(resolution) match + case R(binders) => bindings.filter((symbol, _) => binders(symbol)) + case L(_) => bindings + DeclaredType(resolution, relevant, Map.empty, positive) private def declaredType(resolution: TypeResolution, context: DeclaredType)(using NewResolverState): DeclaredType = declaredType(resolution, context.bindings, context.positive).instantiate(context.instances) @@ -351,7 +472,7 @@ class NewResolver: follow(declaredType(left, current), Nil, captures, next) follow(declaredType(right, current), Nil, captures, next) case _ => () - follow(tpe, Nil, Nil, Set.empty) + withCanonicalType(tpe)(follow(_, Nil, Nil, Set.empty)) private def abstractTypes(using rs: NewResolverState) = rs.abstractTypes // Narrowing a nominal receiver by projection does not infer the narrower @@ -547,7 +668,7 @@ class NewResolver: case TypeShape.Union(left, right) => next(left); next(right) case TypeShape.Intersection(left, right) => next(left); next(right) case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) - follow(tpe, Nil, Set.empty, host.publish) + withCanonicalType(tpe)(follow(_, Nil, Set.empty, host.publish)) // A separate full signature also annotates the implementation's parameters. // Register these before its body is elaborated, so observed calls can never @@ -843,7 +964,7 @@ class NewResolver: case _ => () // Alias expansion guards only unproductive cycles. Descending into a tuple, // nominal argument, or arrow installs another edge with its own cycle guard. - follow(tpe, Nil, Nil, Set.empty) + withCanonicalType(tpe)(follow(_, Nil, Nil, Set.empty)) /** Structural constraints follow declared fields only. This supplies generic * argument inference and callback checking without refining the annotation diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index e9ea7eb0c2..7e857ec0ff 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -206,6 +206,16 @@ final class NewResolverState private ( new Cache(inherited.map(_.defnShapes), identity) val typeInterpretations: Cache[Identity[Term], TypeResolution] = new Cache(inherited.map(_.typeInterpretations), identity) + // Recorded before elaborating a declaration's members, including in legacy + // exporters. Nominal interfaces may use any enclosing explicit type binder. + val lexicalTypeBinders: Cache[AnyDefinitionSymbol, Set[VarSymbol]] = + new Cache(inherited.map(_.lexicalTypeBinders), identity) + val typeDependencies: Cache[TypeResolution, Set[VarSymbol]] = + new Cache(inherited.map(_.typeDependencies), identity) + val pendingTypeDependencies: Cache[TypeResolution, TypeDependencyHost] = + new Cache(inherited.map(_.pendingTypeDependencies), identity) + val dependencySubscriptions: Seen[(TypeResolution, TypeResolution)] = + new Seen(inherited.map(_.dependencySubscriptions)) val quantifiedTypes: Cache[(TypeResolution, Ls[VarSymbol]), TypeResolution] = new Cache(inherited.map(_.quantifiedTypes), identity) val instantiatedCallables: Cache[(CallableTypeShape, FlowSymbol, Ls[Marks]), CallableTypeShape] = @@ -317,3 +327,10 @@ private[semantics] final class TermShapeHost extends Host[TermShape]: if currentShapes.add(shape) then notifyShapeListeners(shape) def listen(listener: NewResolverState.Listener)(using NewResolverState): Unit = subscribeToShapes(listener) + +private[semantics] final class TypeDependencyHost extends Host[Set[VarSymbol]]: + def showDbg(using DebugPrinter): Str = "type dependencies" + def publish(binders: Set[VarSymbol])(using NewResolverState): Unit = + if currentShapes.add(binders) then notifyShapeListeners(binders) + def listen(listener: ShapeListener[Set[VarSymbol]])(using NewResolverState): Unit = + subscribeToShapes(listener) diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index d8bd40879a..d36a5e19b2 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -60,7 +60,37 @@ class Box[A](val value: A) with // Resetting an argument to a closed type has a finite structural unfolding. // The environment retained on Int must not nest the previous alias binding. -:fixme type Reset[A] = {value: A, next: Reset[Int]} fun read(chain: Reset[Str]): Int = chain.next.next.value -//│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// A fixed compound argument is also independent of the previous alias binding. +type Reset[A] = {value: A, next: Reset[Array[Int]]} +fun read(chain: Reset[Str]): Array[Int] = chain.next.next.value + +// An unused argument cannot make a structurally constant recursive type grow. +type Loop[A] = {value: Int, next: Loop[Array[A]]} +fun read(chain: Loop[Str]): Int = chain.next.next.value + +// Forward aliases must retain an enclosing binder even before the target is known. +:expect [5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun delayed[A](value: A) = + type Early = Later + let wrapped = ({item: value} as Early) + type Later = {item: A} + wrapped.item +[delayed[First](First(5)).first, delayed[Second](Second(6)).second] +//│ = [5, 6] + +// A nominal annotation must retain outer binders used only by its members. +:fixme +:expect [7, 8] +class First(val first: Int) +class Second(val second: Int) +private fun local[A](value: A) = + class Local(val item: A) + let box = (new Local(value) as Local) + box.item +[local[First](First(7)).first, local[Second](Second(8)).second] +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: class:Local⁰ vs term:local⁰ for NominalInstanceView(class:First⁰) From 1f329f6d7fd13550fc704281dbef938985ed8877 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 11:32:59 +0800 Subject: [PATCH 109/228] Document receiver context failures and cover finite argument resets --- doc/new-resolution-type-value-flow.md | 72 +++++++++++++++++++ .../mlscript/newres/TypeGraphTermination.mls | 5 ++ 2 files changed, 77 insertions(+) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index a47cf07688..b3dd9cbd57 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -868,6 +868,66 @@ legacy constructions can have value shapes without a new-resolution producer, and completed reference targets must remain immutable. A saved graph operation must retain its selected definition and interpreted argument references. +### Receiver-context investigation + +Two current regressions isolate missing scope transfers. Both fail before any +concrete type mismatch is relevant: + +```mlscript +class Item(val value: Int) +private fun local[A](value: A) = + class Local(val item: A) + let box = (new Local(value) as Local) + box.item +local[Item](Item(7)).value +``` + +`Local.item`'s annotation captures the enclosing `A` into the class scope. The +nominal view's member projection does not leave that scope. When `local` returns, +the interpreter tries to cancel the function exit against the still-pending class +entry, triggering the scope assertion. The regression is the final block of +`newres/TypeGraphTermination.mls`. + +```mlscript +class Item(val value: Int) +class Box[A](val value: A) with + type Slot = {item: A} + fun get(): Slot = {item: value} +(new Box[Item](Item(4))).get().item.value +``` + +Here the reference to `Slot` captures the type into `get`'s scope. The interpreter +produces an unmarked `RecordTypeShape`, and its current capture rule only enters +already-marked shapes. The method entry is therefore lost before the deferred +field is inspected. The method exit then meets `A`'s class entry instead. This is +the existing class-local alias regression in the same worksheet. + +Simply entering every compound interface is not a valid general correction. +For `Chain[A]` inside a function, the type template `Chain` can come from an outer +scope while the supplied argument `A` is already in the function's scope. +Transporting the fully substituted interface moves both, introducing a second +entry for the supplied argument. Captured template references and supplied +argument references must retain their separate contexts. + +**Proposed representation extension, awaiting review:** use contextual references +for declared substitutions and deferred member types, generalizing the existing +`ContextualType` relation endpoints. Compose class/method transfers on these +references before expanding their inferred shapes. A nominal member projection +must explicitly leave its declaring class's scope; arguments supplied outside +that scope must retain their corresponding entry. A compound result must keep +the transfers needed by its deferred components. Apply the same reference +transport in input and output constraints, including callback arguments. + +The extension must reuse original source references, canonical binder instances, +and existing mark normalization. Each transported endpoint must contain a +normalized path rather than a list of transport operations or nested wrappers. +Repeated transport of a recursive interface must return the same endpoint key. +This preserves the existing finite-source/finite-instance argument only if mark +paths obey the no-repeated-boundary invariant and regular type bindings remain +bounded; neither condition may be assumed merely because the two examples pass. +Tests must also retain independent receivers/callers, imported-graph isolation, +recursive aliases, and the input obligations of supplied callback types. + ### Regular structural types The proposed restriction is that unfolding a structural type must admit a finite @@ -894,6 +954,18 @@ including how it handles aliases and inferred holes, remains a design review item. It must terminate on rejected inputs too; waiting for an unfolding cache to stop growing is not a decision procedure. Do not impose a depth limit. +Growth along one recursive edge is not sufficient to reject a type: + +```mlscript +type Left[A] = {value: A, next: Right[Array[A]]} +type Right[B] = {value: B, next: Left[Str]} +``` + +Starting from `Left[Int]` visits `Right[Array[Int]]`, then settles into the cycle +`Left[Str]` / `Right[Array[Str]]`. The argument reset breaks the expanding +dependency. `TypeGraphTermination.mls` includes this finite mutual unfolding as +an acceptance case alongside the parameter-permutation and unused-argument cases. + There is also a representation obligation for accepted types. Currently `DeclaredType.bindings` is a recursive map of `DeclaredType` values, and the whole reference is used as a view-cache key. `declaredType` substitutes a directly bound diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index d36a5e19b2..f775244d32 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -71,6 +71,11 @@ fun read(chain: Reset[Str]): Array[Int] = chain.next.next.value type Loop[A] = {value: Int, next: Loop[Array[A]]} fun read(chain: Loop[Str]): Int = chain.next.next.value +// A wrapping edge followed by a reset has a finite mutual unfolding. +type Left[A] = {value: A, next: Right[Array[A]]} +type Right[B] = {value: B, next: Left[Str]} +fun read(chain: Left[Int]): Str = chain.next.next.next.value.0 + // Forward aliases must retain an enclosing binder even before the target is known. :expect [5, 6] class First(val first: Int) From 46f790288ed0ce93c8543ad8d428e483fb886b37 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 11:37:03 +0800 Subject: [PATCH 110/228] Cover regular recursion through transparent type aliases --- doc/new-resolution-type-value-flow.md | 21 +++++++++++++++++++ .../mlscript/newres/TypeGraphTermination.mls | 7 +++++++ 2 files changed, 28 insertions(+) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index b3dd9cbd57..9b96c0899b 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -994,6 +994,27 @@ previous binding map. The same holds for a fixed compound argument `Array[Int]`. Accepted regular types need finite reference keys and shared recursive edges, not a fresh nested environment on each visit. +Dependency pruning alone does not identify transparent forwarding aliases. This +additional regular regression still overflows: + +```mlscript +type Identity[X] = X +type Chain[A] = {value: A, next: Chain[Identity[A]]} +fun read(chain: Chain[Int]): Int = chain.next.next.value +``` + +The argument continues to denote `Int`, but successive cache keys retain +`(Identity[A], previous-environment)`. Here `A` is genuinely a free binder of the +argument expression, so discarding unused bindings cannot help. A regularity +check must not classify the transparent alias application as a growing type +constructor. Canonical references must resolve forwarding through aliases and +their substitutions, respecting argument variance and occurrence polarity. +This needs to work through multiple aliases and parameter permutations, with a +finite source-graph guard for unproductive alias cycles. Special-casing a directly +written identity alias would not establish the required representation bound. +`TypeGraphTermination.mls` records this case separately from non-regular array +nesting and from the receiver-context failures. + The implemented dependency analysis operates on source `TypeResolution` nodes. An edge forwards the child's free binders, excluding those bound by an alias or quantifier. An applied alias forwards an argument's dependencies only when its diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index f775244d32..bdfddeaf2e 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -76,6 +76,13 @@ type Left[A] = {value: A, next: Right[Array[A]]} type Right[B] = {value: B, next: Left[Str]} fun read(chain: Left[Int]): Str = chain.next.next.next.value.0 +// Transparent aliases do not change the recursive argument's type. +:fixme +type Identity[X] = X +type Chain[A] = {value: A, next: Chain[Identity[A]]} +fun read(chain: Chain[Int]): Int = chain.next.next.value +//│ /!!!\ Uncaught error: java.lang.StackOverflowError + // Forward aliases must retain an enclosing binder even before the target is known. :expect [5, 6] class First(val first: Int) From 543aafebc06a9c57cbef6026421166b3326db3c5 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 12:05:08 +0800 Subject: [PATCH 111/228] Preserve contextual type references through captures and projections --- doc/new-resolution-type-value-flow.md | 146 ++++++------ .../test/scala/hkmc2/TypeRelationTest.scala | 40 ++++ .../scala/hkmc2/semantics/NewResolver.scala | 224 ++++++++++++------ .../hkmc2/semantics/NewResolverState.scala | 4 +- .../main/scala/hkmc2/semantics/Shape.scala | 14 +- .../scala/hkmc2/semantics/TypeShape.scala | 4 + .../test/mlscript/newres/GenericMethods.mls | 3 +- .../test/mlscript/newres/InferenceHoles.mls | 54 ++--- .../src/test/mlscript/newres/SpreadCalls.mls | 59 ++--- .../mlscript/newres/TypeArgumentVariance.mls | 4 +- .../mlscript/newres/TypeGraphTermination.mls | 39 ++- 11 files changed, 366 insertions(+), 225 deletions(-) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 9b96c0899b..50e80cb421 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -45,8 +45,9 @@ implementation. This restriction and sharing regular recursive references are separate obligations; neither follows from the bound on call-site symbols. The finite call-site symbol bound alone is not a termination proof for the whole -resolver. Non-regular expansion still overflows; class-local aliases expose a -receiver-context failure. Dependency-based binding projection now accepts regular +resolver. Non-regular expansion and regular recursion through transparent +argument aliases still overflow. Contextual references preserve class-local aliases +and enclosing binders through nominal member projections. Dependency-based binding projection accepts regular argument resets, including compound constants and recursively unused arguments. The [fixed-point conditions](#fixed-points-and-implementation-checks) remain completion gates. @@ -74,8 +75,9 @@ their declared output interface. Replacing the remaining explicit-argument flags and positive-only member conversion, reconstructed receiver contexts, inferred nominal member interfaces, and general recursive alias environments still need integration. Omitted arguments -use source-owned inference holes; recursive hole contexts and omissions inside -alias bodies still need the contextual reference work described below. The +use source-owned inference holes. The recursive array-hole regression now preserves +independent callers; distinguishing two omissions inside a shared alias body remains +unfinished. The implementation order below remains the full design, not a claim that all its parts are complete. See the [resolver notes](new-resolution-design.md) @@ -842,20 +844,12 @@ These cases pass with mark checks enabled. A graph-level structural cycle test also checks late bounds and verifies that repeated relations add no listeners or parameter instances. -The class-local alias case remains a `:fixme`: projecting the result of -`Box[A].get(): Slot`, where `Slot = {item: A}`, loses the correspondence between -the method's scope and its receiver's scope. This also fails without the scope -correction. Closed type interfaces need their deferred child references observed -in the appropriate context; adding an entry to every closed interface instead -breaks regular alias traversal. The whole-graph proof must address this context -transport, and existing repeated-mark failures in generic array-method paths, -not just the now-passing regular alias examples. - -The expanding alias needs the regularity diagnostic described below. The regular -argument-reset regressions now pass after projecting `DeclaredType.bindings` onto -the source type's dependencies. A local nominal annotation whose constructor -field refers to an enclosing function parameter still exposes the receiver-context -failure; it has its own regression beside the class-local alias case. +Class-local aliases and local nominal annotations retain their deferred member +contexts through the contextual-reference transport described below. These passing +regressions do not establish the whole-graph bound: repeated-mark failures in generic +array-method paths and expanding alias environments still require separate checks. +Dependency projection accepts regular argument resets, including compound constants. +The expanding alias needs the regularity diagnostic described below. The same graph audit must distinguish an open definition template from an already interpreted reference. In `Box[T].copy() = new Box[T](item)`, the source `T` in the @@ -868,65 +862,78 @@ legacy constructions can have value shapes without a new-resolution producer, and completed reference targets must remain immutable. A saved graph operation must retain its selected definition and interpreted argument references. -### Receiver-context investigation +### Contextual references and receiver projection -Two current regressions isolate missing scope transfers. Both fail before any -concrete type mismatch is relevant: +`TypeShape.Contextual(ContextualType(reference, marks))` retains an interpreted +reference together with its path into the observation scope. Substitution maps and +deferred fields keep these references as `DeclaredType` values. `transportType` +flattens an existing contextual node before interning the resulting endpoint; +contextual nodes contain one normalized mark path, never nested transport nodes. +Their dependencies are already in the saved reference, so they do not read an +ambient substitution map. + +For example: ```mlscript class Item(val value: Int) -private fun local[A](value: A) = - class Local(val item: A) - let box = (new Local(value) as Local) - box.item -local[Item](Item(7)).value +class Box[A](val value: A) with + type Slot = {item: A} + fun get(): Slot = {item: value} +(new Box[Item](Item(4))).get().item.value ``` -`Local.item`'s annotation captures the enclosing `A` into the class scope. The -nominal view's member projection does not leave that scope. When `local` returns, -the interpreter tries to cancel the function exit against the still-pending class -entry, triggering the scope assertion. The regression is the final block of +The `Slot` reference enters `get`'s scope even though its immediate interpretation +is an unmarked record interface. The method result leaves that scope, while the +record's deferred `item` type retains the receiver's interpretation of `A`. Scope +transport never depends on whether the current outer shape happens to be marked. +Both this example and the corresponding separate-signature case are covered in `newres/TypeGraphTermination.mls`. +Nominal member projection explicitly leaves the declaring class's scope. Its own +type arguments enter that scope before substituting member annotations. Modules +and objects introduce no such invocation boundary. Enclosing +binders keep their original contexts: their annotation's `Capture` nodes supply +the class entry. This also handles a local class capturing a function parameter: + ```mlscript class Item(val value: Int) -class Box[A](val value: A) with - type Slot = {item: A} - fun get(): Slot = {item: value} -(new Box[Item](Item(4))).get().item.value +private fun local[A](value: A) = + class Local(val item: A) + let box = (new Local(value) as Local) + box.item +local[Item](Item(7)).value ``` -Here the reference to `Slot` captures the type into `get`'s scope. The interpreter -produces an unmarked `RecordTypeShape`, and its current capture rule only enters -already-marked shapes. The method entry is therefore lost before the deferred -field is inspected. The method exit then meets `A`'s class entry instead. This is -the existing class-local alias regression in the same worksheet. - -Simply entering every compound interface is not a valid general correction. -For `Chain[A]` inside a function, the type template `Chain` can come from an outer -scope while the supplied argument `A` is already in the function's scope. -Transporting the fully substituted interface moves both, introducing a second -entry for the supplied argument. Captured template references and supplied -argument references must retain their separate contexts. - -**Proposed representation extension, awaiting review:** use contextual references -for declared substitutions and deferred member types, generalizing the existing -`ContextualType` relation endpoints. Compose class/method transfers on these -references before expanding their inferred shapes. A nominal member projection -must explicitly leave its declaring class's scope; arguments supplied outside -that scope must retain their corresponding entry. A compound result must keep -the transfers needed by its deferred components. Apply the same reference -transport in input and output constraints, including callback arguments. - -The extension must reuse original source references, canonical binder instances, -and existing mark normalization. Each transported endpoint must contain a -normalized path rather than a list of transport operations or nested wrappers. -Repeated transport of a recursive interface must return the same endpoint key. -This preserves the existing finite-source/finite-instance argument only if mark -paths obey the no-repeated-boundary invariant and regular type bindings remain -bounded; neither condition may be assumed merely because the two examples pass. -Tests must also retain independent receivers/callers, imported-graph isolation, -recursive aliases, and the input obligations of supplied callback types. +A captured template and its application arguments have different starting scopes. +For `Chain[A]` inside a function, `Chain` can denote an outer template while `A` +already belongs to the function. `TypeApplication` rebases supplied arguments into +the template's scope and records the same transfer for omitted positions. Expanding +the template transports its interface back. Views, constraints, and hole exposure +all use this operation; inferred holes receive the same context as supplied arguments. +Nominal constraints compare argument endpoints in the common observation scope, +including both their input and output parts. + +A reference's lexical round trip must be composed before observing its host. In +particular, leaving a scope with a wildcard mark and immediately re-entering that +same lexical scope returns the same reference. Applying those operations to each +host candidate instead would erase a concrete caller's identity at the wildcard +exit. `transportType` cancels that pair on the reference. It never cancels a reverse +pair containing an explicit invocation site. Ordinary value-flow mark operations +are unchanged. `TypeRelationTest` checks early and late bounds from distinct caller +sites, retained explicit invocation entries, and a thousand repeated transfers +without additional listeners or binder instances. + +`transportShape` composes paths on deferred instance references at projection and +result boundaries before delivering them to another caller. Leaving a wildcard +entry outside an instance wrapper could otherwise consume a subsequent concrete +call exit before the wrapper's own path was considered. Separate signatures retain +context when supplying parameter types and when following arrow results. By-name +members use the complete receiver path when consuming their binders. + +The finite-source/finite-instance argument still requires bounded mark paths and +regular type environments. Interning normalized endpoints prevents transport +wrapper histories from growing; it does not prove those two separate bounds or +justify truncating paths when an invariant fails. ### Regular structural types @@ -1119,15 +1126,16 @@ for it. `TypeRelationTest` checks reuse across repeated observations, empty host waiting for evidence, cyclic constraints with stable listener counts, and private bounds in separate importers sharing the same source identity. This bounds hole allocation and replay; it does not establish the missing context precision for -recursive holes or alias-body omissions noted above. +alias-body omissions noted above. The recursive omitted-array regression now +preserves independent caller contexts. During implementation, assert that a call instance's origin is an original binder, all of one scheme's binders are allocated before its constraints are activated, views are composed rather than nested, and generic checking witnesses never become ordinary instantiated lower bounds. Retain consumer-private host copies and the -existing prohibition on changing completed member targets. The design review does -not license changing mark normalization if these checks fail; any such change -requires a separate concrete example and proposal. +existing prohibition on changing completed member targets. The contextual-reference round-trip rule above does not license changing ordinary +value-flow mark normalization if these checks fail. Any further change requires a +separate concrete example and proposal. ## Acceptance checks diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index cdba7aa891..8af4eee721 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -56,6 +56,46 @@ class TypeRelationTest extends AnyFunSuite: assert((a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) assert(seen.toList == List(first, second)) + test("lexical reference round trips preserve early and late caller identities"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val owner = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("owner"))) + val firstSite = FlowSymbol.app() + val secondSite = FlowSymbol.app() + val exit = ExitMark(owner, N, NoMarks) :: Nil + val enter = EntryMark(owner, N, NoMarks) :: Nil + val outside = h.resolver.transportType(at, exit) + val back = h.resolver.transportType(outside, enter) + assert(back == at) + val first = IntroShape(Term.UnitVal(), N) + val second = DynShape() + h.resolver.publishParameter(a, MarkedShape.enter(first, owner, S(firstSite))) + val seen = h.observe(ContextualType(back, ExitMark(owner, S(firstSite), NoMarks) :: Nil)) + h.resolver.publishParameter(a, MarkedShape.enter(second, owner, S(secondSite))) + assert(seen.toList == List(first)) + val listeners = a.inferenceHost.listeners.size + (1 to 1000).foreach: _ => + assert(h.resolver.transportType(at, exit) eq outside) + assert(h.resolver.transportType(outside, enter) == at) + assert(a.inferenceHost.listeners.size == listeners) + assert(h.state.allocatedTypeInstanceCount == 0) + + test("reference rebasing never cancels explicit invocation entries"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val owner = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("owner"))) + val original = FlowSymbol.app() + val invoked = FlowSymbol.app() + val outside = h.resolver.transportType(at, ExitMark(owner, S(original), NoMarks) :: Nil) + val inside = h.resolver.transportType(outside, EntryMark(owner, S(invoked), NoMarks) :: Nil) + assert(inside != at) + val bound = IntroShape(Term.UnitVal(), N) + h.resolver.publishParameter(a, MarkedShape.enter(bound, owner, S(original))) + val seen = h.observe(ContextualType(inside, Nil)) + assert(seen.toList == List(MarkedShape.enter(bound, owner, S(invoked)))) + test("recursive structural projections preserve bounds and reuse their listeners"): val h = new Harness import h.given diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 99661187b0..7b8a3fd013 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -240,7 +240,7 @@ class NewResolver: case TypeShape.Wildcard(input, output) => input.foreach(child); output.foreach(child) // These synthesized nodes retain their own interpreted references // or inference hosts; none reads the surrounding binding map. - case TypeShape.Argument(_) | TypeShape.SelectedArgument(_, _) | TypeShape.Inferred(_) | + case TypeShape.Argument(_) | TypeShape.SelectedArgument(_, _) | TypeShape.Contextual(_) | TypeShape.Inferred(_) | TypeShape.Hole(_) | TypeShape.Unit | TypeShape.Dynamic | TypeShape.Abstract | TypeShape.Top | TypeShape.Bottom => () graph(res) = (direct, edges) @@ -296,6 +296,7 @@ class NewResolver: private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], positive: Bool) (using NewResolverState): DeclaredType = resolution.currentShapes.toList match case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) + case TypeShape.Captured(base, thru) :: Nil => captureType(declaredType(base, bindings, positive), thru) case _ => val relevant = if bindings.isEmpty then bindings else typeDependencies(resolution) match case R(binders) => bindings.filter((symbol, _) => binders(symbol)) @@ -327,6 +328,8 @@ class NewResolver: // shared extreme node itself has no source location. (if positive then output else input).fold(deferred): part => declaredType(part, argument.copy(positive = if positive then argument.positive else !argument.positive)) + case TypeShape.Contextual(reference) :: Nil => + transportType(selectArgument(reference.tpe.instantiate(argument.instances), positive), reference.marks) case Nil | TypeShape.Captured(_, _) :: Nil => deferred case _ => argument @@ -379,6 +382,9 @@ class NewResolver: case TypeShape.Captured(base, thru) => listenArgumentParts(declaredType(base, tpe)): parts => listener(TypeArgument(captureType(parts.input, thru), captureType(parts.output, thru))) + case TypeShape.Contextual(reference) => + listenArgumentParts(reference.tpe.instantiate(tpe.instances)): parts => + listener(TypeArgument(transportType(parts.input, reference.marks), transportType(parts.output, reference.marks))) case TypeShape.Parameter(symbol, _) if tpe.bindings.contains(symbol) => val selected = selectArgument(tpe.bindings(symbol).instantiate(tpe.instances), tpe.positive) listener(TypeArgument(selected, selected)) @@ -414,12 +420,21 @@ class NewResolver: declaredType(resolution, Map.empty) }) - private def typeArguments(source: DeclaredType, arguments: Ls[DeclaredType], parameters: Ls[TyParam]) + /** Supplied arguments and omitted positions belong to the application scope. + * Moving a template must rebase both, even before an omitted host is needed. + */ + private case class TypeApplication(arguments: Ls[DeclaredType], context: Ls[Marks]): + def move(marks: Ls[Marks])(using NewResolverState): TypeApplication = + TypeApplication(arguments.map(transportType(_, marks)), context ::: marks) + private val noTypeArguments = TypeApplication(Nil, Nil) + + private def typeArguments(source: DeclaredType, application: TypeApplication, parameters: Ls[TyParam]) (using NewResolverState): Ls[DeclaredType] = parameters.zipWithIndex.map: (parameter, index) => - arguments.lift(index).getOrElse(omittedType(source.resolution, parameter.sym).instantiate(source.instances)) + application.arguments.lift(index).getOrElse( + transportType(omittedType(source.resolution, parameter.sym).instantiate(source.instances), application.context)) - private def typeBindings(source: DeclaredType, arguments: Ls[DeclaredType], parameters: Ls[TyParam]) + private def typeBindings(source: DeclaredType, arguments: TypeApplication, parameters: Ls[TyParam]) (using NewResolverState): Map[VarSymbol, DeclaredType] = effectiveBindings(source) ++ parameters.zip(typeArguments(source, arguments, parameters)).map: (parameter, argument) => parameter.sym -> argumentType(argument, parameter.vce) @@ -431,7 +446,7 @@ class NewResolver: private[semantics] def exposeTypeHoles(tpe: DeclaredType, unknown: TermShape, marks: Ls[Marks]) (using NewResolverState): Unit = if !rstate.exposedTypeHoles.add((tpe, unknown, marks)) then return - def follow(current: DeclaredType, args: Ls[DeclaredType], captures: Ls[Marks], seen: Set[TypeResolution]) + def follow(current: DeclaredType, args: TypeApplication, captures: Ls[Marks], seen: Set[TypeResolution]) (using NewResolverState): Unit = if !seen(current.resolution) then val next = seen + current.resolution def descend(child: DeclaredType)(using NewResolverState): Unit = unknown.enter(captures) match @@ -442,17 +457,20 @@ class NewResolver: case value: TermShape => host.publish(value) case NoShape => () case TypeShape.Parameter(symbol, _) => - current.bindings.get(symbol).foreach(bound => follow(selectArgument(bound.instantiate(current.instances), current.positive), Nil, captures, next)) + current.bindings.get(symbol).foreach(bound => follow(selectArgument(bound.instantiate(current.instances), current.positive), noTypeArguments, captures, next)) case TypeShape.Captured(base, thru) => - follow(declaredType(base, current), args, EntryMark(ResolutionBoundary(thru), N, NoMarks) :: captures, next) + follow(captureType(declaredType(base, current), thru), args, captures, next) + case TypeShape.Contextual(reference) => + follow(reference.tpe.instantiate(current.instances), args.move(inverseMarks(reference.marks)), + reference.marks ::: captures, next) case TypeShape.Applied(base, arguments) => - follow(declaredType(base, current), arguments.map(declaredType(_, current)), captures, next) + follow(declaredType(base, current), TypeApplication(arguments.map(declaredType(_, current)), Nil), captures, next) case TypeShape.Alias(symbol, S(rhs)) => - follow(declaredType(rhs, typeBindings(current, args, symbol.defn.get.tparams), current.positive), Nil, captures, next) + follow(declaredType(rhs, typeBindings(current, args, symbol.defn.get.tparams), current.positive), noTypeArguments, captures, next) case TypeShape.Nominal(cls) => cls.tparams.zip(typeArguments(current, args, cls.tparams)).foreach: (param, argument) => listenArgumentParts(argumentType(argument, param.vce)): parts => - exposeTypeHoles(parts.output, unknown, marks) + exposeTypeHoles(transportType(parts.output, captures), unknown, marks) case TypeShape.Tuple(fields) => fields.foreach(field => descend(declaredType(field, current))) case TypeShape.Record(_, fields) => fields.foreach((_, field) => descend(declaredType(field, current))) case TypeShape.Function(_, result) => descend(declaredType(result, current)) @@ -466,13 +484,13 @@ class NewResolver: listenArgumentParts(argument.instantiate(current.instances)): parts => follow(if positive then parts.output else parts.input, args, captures, next) case TypeShape.Union(left, right) => - follow(declaredType(left, current), Nil, captures, next) - follow(declaredType(right, current), Nil, captures, next) + follow(declaredType(left, current), noTypeArguments, captures, next) + follow(declaredType(right, current), noTypeArguments, captures, next) case TypeShape.Intersection(left, right) => - follow(declaredType(left, current), Nil, captures, next) - follow(declaredType(right, current), Nil, captures, next) + follow(declaredType(left, current), noTypeArguments, captures, next) + follow(declaredType(right, current), noTypeArguments, captures, next) case _ => () - withCanonicalType(tpe)(follow(_, Nil, Nil, Set.empty)) + withCanonicalType(tpe)(follow(_, noTypeArguments, Nil, Set.empty)) private def abstractTypes(using rs: NewResolverState) = rs.abstractTypes // Narrowing a nominal receiver by projection does not infer the narrower @@ -499,10 +517,7 @@ class NewResolver: */ private[semantics] def listenInstanceViews(value: TermShape)(listener: Listener)(using NewResolverState): Unit = value match case Marked(instance: InstanceShape, marks) => - listenTypeViews(instance.tpe): shape => - shape.exit(marks) match - case value: TermShape => listener(value) - case NoShape => () + listenTypeViews(transportType(instance.tpe, marks :: Nil))(listener) case _ => listener(value) private def listenTermViews(term: Term)(listener: Listener)(using NewResolverState): Unit = @@ -571,18 +586,18 @@ class NewResolver: val host = new TermShapeHost typeViews(tpe) = host host.listen(listener) - def follow(current: DeclaredType, args: Ls[DeclaredType], aliases: Set[TypeResolution], publish: Listener)(using NewResolverState): Unit = + def follow(current: DeclaredType, args: TypeApplication, aliases: Set[TypeResolution], publish: Listener)(using NewResolverState): Unit = current.resolution.listen: shape => def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = typeBindings(current, args, params) - def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), Nil, aliases, publish) + def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), noTypeArguments, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) case TypeShape.Top => publish(OpaqueTypeShape(tpe.resolution.source)) case TypeShape.Bottom => () case TypeShape.Wildcard(_, output) => output.fold(publish(OpaqueTypeShape(tpe.resolution.source)))(next) - case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), Nil, aliases, publish) + case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), noTypeArguments, aliases, publish) case TypeShape.SelectedArgument(argument, positive) => // An argument cycle can forward this selection without exposing // an interface. Keep the subscription for later parts, but stop @@ -609,13 +624,13 @@ class NewResolver: // This also terminates aliases that recursively grow their arguments. if aliases(current.resolution) then publish(OpaqueTypeShape(tpe.resolution.source)) else rhs match - case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams), current.positive), Nil, + case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams), current.positive), noTypeArguments, aliases + current.resolution, publish) case N => publish(OpaqueTypeShape(tpe.resolution.source)) case TypeShape.Applied(base, params) => - follow(declaredType(base, current), params.map(declaredType(_, current)), aliases, publish) + follow(declaredType(base, current), TypeApplication(params.map(declaredType(_, current)), Nil), aliases, publish) case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match - case S(bound) => follow(selectArgument(bound.instantiate(current.instances), current.positive), Nil, aliases, publish) + case S(bound) => follow(selectArgument(bound.instantiate(current.instances), current.positive), noTypeArguments, aliases, publish) case N => val target = current.instances.get(symbol).fold(host)(_.inferenceHost) listenTypeArgument(target): value => @@ -634,12 +649,15 @@ class NewResolver: case value: TermShape => listenInstanceViews(instantiateShape(value, current.instances))(publish) case NoShape => () case TypeShape.Captured(base, thru) => - follow(declaredType(base, current), args, aliases, - shape => publish(shape match - // Nominal/function interfaces are closed descriptions. Only - // parameter flow carries a lexical activation to transport. - case _: MarkedShape => MarkedShape.enter(shape, ResolutionBoundary(thru), N) - case _ => shape)) + follow(captureType(declaredType(base, current), thru), args, aliases, publish) + case TypeShape.Contextual(reference) => + // Arguments belong to the application scope, whereas the + // template belongs to the reference's source scope. Move the + // arguments back before substitution, then transport the result. + follow(reference.tpe.instantiate(current.instances), args.move(inverseMarks(reference.marks)), aliases, + shape => shape.exit(reference.marks) match + case value: TermShape => publish(value) + case NoShape => ()) case TypeShape.Function(params, ret) => val ps = params match case Tup(fields) => DeclaredParams(fields.collect: @@ -668,7 +686,7 @@ class NewResolver: case TypeShape.Union(left, right) => next(left); next(right) case TypeShape.Intersection(left, right) => next(left); next(right) case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) - withCanonicalType(tpe)(follow(_, Nil, Set.empty, host.publish)) + withCanonicalType(tpe)(follow(_, noTypeArguments, Set.empty, host.publish)) // A separate full signature also annotates the implementation's parameters. // Register these before its body is elaborated, so observed calls can never @@ -678,14 +696,15 @@ class NewResolver: def bind(paramLists: Ls[ParamList], tpe: DeclaredType)(using NewResolverState): Unit = paramLists match case Nil => () case ps :: tail => listenTypeViews(tpe): - case callable: CallableTypeShape => + case Marked(callable: CallableTypeShape, marks) => val declared = callable.paramLists.head if !declared.hasRest then ps.params.zip(declared.params).foreach: (param, sign) => if param.sign.isEmpty then sign.foreach: tpe => - signatureParameters(param.sym) = tpe - listenTypeInstances(tpe): shape => + val contextual = transportType(tpe, marks :: Nil) + signatureParameters(param.sym) = contextual + listenTypeInstances(contextual): shape => if param.sym.currentShapes.add(shape) then param.sym.notifyShapeListeners(shape) - callable.result.foreach(bind(tail, _)) + callable.result.foreach(tpe => bind(tail, transportType(tpe, marks :: Nil))) case _ => () bind(paramLists, declaredType(typeResolution(sign), Map.empty)) @@ -694,17 +713,68 @@ class NewResolver: case S(p: Param) => p.sign case _ => td.resultSignature - private def capturedTypes(using rs: NewResolverState) = rs.capturedTypes - private def captureType(bound: DeclaredType, scope: AnyDefinitionSymbol)(using NewResolverState): DeclaredType = - capturedTypes.getOrElseUpdate((bound, scope), { - val captured = new TypeResolution(bound.resolution.source, bound.resolution.fail) - captured.publish(TypeShape.Captured(bound.resolution, scope)) - declaredType(captured, bound) - }) + /** Retain a scope transfer on the reference, including deferred fields and + * both argument parts. Flatten existing transfers before interning, so a + * recursive projection cannot build a tower of transport wrappers. + */ + private[semantics] def transportType(bound: DeclaredType, marks: Ls[Marks])(using NewResolverState): DeclaredType = + val (base, previous) = bound.resolution.currentShapes.toList match + case TypeShape.Contextual(reference) :: Nil => (reference.tpe.instantiate(bound.instances), reference.marks) + case _ => (bound, Nil) + softAssert(!base.resolution.currentShapes.exists: + case TypeShape.Contextual(_) => true + case _ => false + , "Contextual references must be flattened before they are interned") + def transfer(value: TermShape | NoShape, path: Marks): TermShape | NoShape = path match + case NoMarks => value + case EntryMark(boundary, N, rest) => transfer(value, rest) match + // These two wildcard operations rebase the same deferred reference + // out of a lexical scope and back. Cancel before observing its host: + // cancellation on the host's values would forget their caller sites. + // Explicit invocation marks are never erased by this rule. + case Marked(source, ExitMark(exited, N, tail)) if exited == boundary => source.exit(tail) + case value: TermShape => MarkedShape.enter(value, boundary, N) + case NoShape => NoShape + case EntryMark(boundary, site, rest) => transfer(value, rest) match + case value: TermShape => MarkedShape.enter(value, boundary, site) + case NoShape => NoShape + case ExitMark(boundary, site, rest) => transfer(value, rest) match + case value: TermShape => MarkedShape.exit(value, boundary, site) + case NoShape => NoShape + marks.foldLeft(InstanceShape(base).exit(previous))(transfer) match + case Marked(_, NoMarks) => base + case Marked(_, path: SomeMarks) => + val reference = ContextualType(base, path :: Nil) + rstate.contextualTypes.getOrElseUpdate(reference, { + val resolution = new TypeResolution(base.resolution.source, base.resolution.fail) + resolution.publish(TypeShape.Contextual(reference)) + declaredType(resolution, Map.empty) + }) + case NoShape => extremeType(false) + + private def inverseMarks(marks: Ls[Marks])(using NewResolverState): Ls[Marks] = + // Inversion is an operation, not a reordered list of scopes. Use the same + // mark algebra as value transport and retain the resulting reduced path. + InstanceShape(extremeType(false)).enter(marks) match + case Marked(_, NoMarks) => Nil + case Marked(_, path: SomeMarks) => path :: Nil + case NoShape => lastWords("Inverting a scope path cannot reject a candidate") + + private[semantics] def captureType(bound: DeclaredType, scope: AnyDefinitionSymbol)(using NewResolverState): DeclaredType = + transportType(bound, EntryMark(ResolutionBoundary(scope), N, NoMarks) :: Nil) + + /** Finish a projection on the deferred reference before delivering it to a + * caller. Leaving a wildcard entry outside InstanceShape would let a later + * call exit cancel it before the reference's own path had been composed. + */ + private def transportShape(value: TermShape, marks: Ls[Marks])(using NewResolverState): TermShape | NoShape = value match + case Marked(instance: InstanceShape, inner) => + InstanceShape(transportType(instance.tpe, inner :: marks)) + case _ => value.exit(marks) private def listenDeclaredMember(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], flow: FlowSymbol, source: Term, annotation: Opt[Term], selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool, positive: Bool, - specialization: Opt[(FlowSymbol, Ls[DeclaredType])])(listener: Listener)(using NewResolverState): Unit = + receiverMarks: Ls[Marks], specialization: Opt[(FlowSymbol, Ls[DeclaredType])])(listener: Listener)(using NewResolverState): Unit = member.onComplete: () => valueTarget(member, receiver) match case S(symbol: TermSymbol) if !symbol.isInstanceOf[ClassCtorSymbol] => @@ -713,24 +783,26 @@ class NewResolver: val crossesValueScope = !(td.k is syntax.RecordField) && !td.tsym.decl.exists(_.isInstanceOf[Param]) val callerInstances = rstate.instances val instances = if isByName(td) then instantiateByName(td, flow, - ExitMark(ResolutionBoundary(td.tsym), S(flow), NoMarks) :: Nil, specialization) else callerInstances + ExitMark(ResolutionBoundary(td.tsym), S(flow), NoMarks) :: receiverMarks, specialization) else callerInstances // A selected generic method binds its own parameters anew. Receiver // bindings can mention an earlier call of that same source method in // their values, but cannot bind the new method's local names. val capturedBindings = bindings -- definitionBinders(td) def publish(shape: TermShape)(using NewResolverState): Unit = - val generic = shape match + val Marked(interface, context) = shape + val generic = interface match case callable: CallableTypeShape if !isByName(td) => val parameters = td.tparams.toList.flatten.map(p => declaredParameter(p.sym)) ::: callable.tparams callable.copy(scheme = if parameters.isEmpty then N else S(TypeScheme(td.tsym, parameters)), declaration = S(td)) case instance: InstanceShape if !isByName(td) => InstanceShape(quantifiedType(instance.tpe, td.tparams.toList.flatten.map(_.sym))) - case _ => shape + case _ => interface // Constructor fields keep their parameter scope; structural fields // follow their written type directly. Other members leave their own // definition scope when their declared interface is selected. - val exited = if crossesValueScope then MarkedShape.exit(generic, ResolutionBoundary(td.tsym), S(flow)) - else generic + val path = context :: (if crossesValueScope then + ExitMark(ResolutionBoundary(td.tsym), S(flow), NoMarks) :: Nil else Nil) + val exited = transportShape(generic, path) exited match case value: TermShape => listener(instantiateShape(value, instances))(using rstate.withInstances(callerInstances)) case NoShape => () @@ -873,7 +945,7 @@ class NewResolver: private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = if !typeConstraints.add((tpe, value, marks)) then return - def follow(tpe: DeclaredType, args: Ls[DeclaredType], captures: Ls[Marks], + def follow(tpe: DeclaredType, args: TypeApplication, captures: Ls[Marks], seen: Set[TypeResolution])(using NewResolverState): Unit = if !seen(tpe.resolution) then val next = seen + tpe.resolution def observe(listener: Listener)(using NewResolverState): Unit = @@ -883,7 +955,7 @@ class NewResolver: case NoShape => () tpe.resolution.listen: case TypeShape.Parameter(symbol, host) => tpe.bindings.get(symbol) match - case S(bound) => follow(selectArgument(bound.instantiate(tpe.instances), tpe.positive), Nil, captures, next) + case S(bound) => follow(selectArgument(bound.instantiate(tpe.instances), tpe.positive), noTypeArguments, captures, next) case N => val target = tpe.instances.get(symbol) val destination = target.fold(host)(_.inferenceHost) @@ -901,12 +973,14 @@ class NewResolver: case _ => () else publishParameter(destination, lower) case TypeShape.Captured(base, thru) => - follow(declaredType(base, tpe), args, - EntryMark(ResolutionBoundary(thru), N, NoMarks) :: captures, next) + follow(captureType(declaredType(base, tpe), thru), args, captures, next) + case TypeShape.Contextual(reference) => + follow(reference.tpe.instantiate(tpe.instances), args.move(inverseMarks(reference.marks)), + reference.marks ::: captures, next) case TypeShape.Applied(base, params) => - follow(declaredType(base, tpe), params.map(declaredType(_, tpe)), captures, next) + follow(declaredType(base, tpe), TypeApplication(params.map(declaredType(_, tpe)), Nil), captures, next) case TypeShape.Alias(symbol, S(rhs)) => - follow(declaredType(rhs, typeBindings(tpe, args, symbol.defn.get.tparams), tpe.positive), Nil, captures, next) + follow(declaredType(rhs, typeBindings(tpe, args, symbol.defn.get.tparams), tpe.positive), noTypeArguments, captures, next) case TypeShape.Hole(host) => value.enter(captures) match case value: TermShape => host.publish(value) case NoShape => () @@ -933,7 +1007,7 @@ class NewResolver: listenArgumentParts(argument.instantiate(tpe.instances)): parts => follow(if positive then parts.output else parts.input, args, captures, next) case TypeShape.Nominal(cls) => listenInstanceViews(value): value => - val arguments = typeArguments(tpe, args, cls.tparams) + val arguments = typeArguments(tpe, args, cls.tparams).map(transportType(_, captures)) val (actual, instances) = contextualParts(value) val (head, context) = actual.applicationHead def constrain(param: TyParam, bound: DeclaredType, pattern: DeclaredType)(using NewResolverState): Unit = @@ -964,7 +1038,7 @@ class NewResolver: case _ => () // Alias expansion guards only unproductive cycles. Descending into a tuple, // nominal argument, or arrow installs another edge with its own cycle guard. - withCanonicalType(tpe)(follow(_, Nil, Nil, Set.empty)) + withCanonicalType(tpe)(follow(_, noTypeArguments, Nil, Set.empty)) /** Structural constraints follow declared fields only. This supplies generic * argument inference and callback checking without refining the annotation @@ -982,8 +1056,8 @@ class NewResolver: case MemberLookup.Found(member, inner) => fromBMS(member.memberSymbol, flow, inner, value => receive(instantiateShape(value, instances)), field.rhs, _ => (), false)(using rstate.withInstances(instances)) case MemberLookup.Declared(member, bound, inner, annotation, positive) => - listenDeclaredMember(member, bound, flow, field.rhs, annotation, _ => (), false, positive, N): value => - instantiateShape(value, instances).exit(inner) match + listenDeclaredMember(member, bound, flow, field.rhs, annotation, _ => (), false, positive, inner, N): value => + transportShape(instantiateShape(value, instances), inner) match case value: TermShape => receive(value) case NoShape => () case MemberLookup.Dynamic(inner) => DynShape().exit(inner) match @@ -1085,8 +1159,8 @@ class NewResolver: private def listenSignatureResult(tpe: DeclaredType, count: Int)(listener: Listener)(using NewResolverState): Unit = if count == 0 then listenTypeInstances(tpe)(listener) else listenTypeViews(tpe): - case Marked(callable: CallableTypeShape, _) => - callable.result.foreach(listenSignatureResult(_, count - 1)(listener)) + case Marked(callable: CallableTypeShape, marks) => + callable.result.foreach(tpe => listenSignatureResult(transportType(tpe, marks :: Nil), count - 1)(listener)) case _ => () private def tupleArrayParents(using rs: NewResolverState) = rs.tupleArrayParents @@ -1160,7 +1234,7 @@ class NewResolver: case _ => () private[semantics] def listenTupleField(field: TupleShape.Fixed)(listener: Listener)(using NewResolverState): Unit = - def receive(shape: TermShape)(using NewResolverState): Unit = shape.exit(field.marks) match + def receive(shape: TermShape)(using NewResolverState): Unit = transportShape(shape, field.marks) match case value: TermShape => listener(value) case NoShape => () field match @@ -1186,7 +1260,7 @@ class NewResolver: // parameter instance the allocation selected. val head = value.applicationHead._1 val context = source.applicationHead._2 - def receive(element: TermShape)(using NewResolverState): Unit = element.exit(context) match + def receive(element: TermShape)(using NewResolverState): Unit = transportShape(element, context) match case value: TermShape => listener(value) case NoShape => () head match @@ -1428,9 +1502,9 @@ class NewResolver: fromBMS(sym, resSym, marks, field => matchShapePat(instantiateShape(field, instances), pat)(_ => ()), ctor.target, _ => (), false) case MemberLookup.Declared(member, bindings, marks, annotation, positive) => - listenDeclaredMember(member, bindings, resSym, ctor.target, annotation, _ => (), false, positive, N): field => + listenDeclaredMember(member, bindings, resSym, ctor.target, annotation, _ => (), false, positive, marks, N): field => listenInstanceViews(instantiateShape(field, instances)): field => - field.exit(marks) match + transportShape(field, marks) match case value: TermShape => val field = (value.applicationHead._1, unknown) match case (_: UnknownValueShape, S(Marked(origin: UnknownValueShape, marks))) => @@ -1632,7 +1706,7 @@ class NewResolver: value match case value: TermShape => inferTypeArguments(tpe, value, context :: Nil) case NoShape => () - def publish(shape: TermShape)(using NewResolverState): Unit = shape.exit(context) match + def publish(shape: TermShape)(using NewResolverState): Unit = transportShape(shape, context :: Nil) match case value: TermShape => publishViewed(res, value, Map.empty)(using rstate.withInstances(callerInstances)) case NoShape => () @@ -1674,7 +1748,7 @@ class NewResolver: if sh.isSaturated then def go(body: Term, mss: Ls[Marks])(using NewResolverState) = listenTerm(body): sh => - sh.exit(mss) match + transportShape(sh, mss) match case NoShape => case sh: TermShape => publish(sh) @@ -1699,7 +1773,7 @@ class NewResolver: case S(sign) => val count = if td.flags.hasResultAnnotation then 0 else td.params.length listenSignatureResult(declaredType(typeResolution(sign), Map.empty).instantiate(instances), count): result => - result.exit(mss) match + transportShape(result, mss) match case value: TermShape => publish(value) case NoShape => () @@ -1879,7 +1953,7 @@ class NewResolver: softAssert(index.exists(_ >= 0), "Tuple lookup must identify a nonnegative index") rstate.recordResolution(sel, sel.tupleIndex == index)(sel.tupleIndex = index) listenTupleField(field): shape => - shape.exit(marks) match + transportShape(shape, marks) match case value: TermShape => publishViewed(sel, value, instances) case NoShape => () @@ -2066,8 +2140,8 @@ class NewResolver: fromSymbolAt(contextual.source, value => listener(instantiateShape(value, contextual.instances)), source, selected, receiver, specialization)(using rstate.withInstances(contextual.instances)) case declared: DeclaredSymShape => - listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, declared.annotation, selected, receiver, declared.positive, specialization): value => - value.exit(shape.markss) match + listenDeclaredMember(shape.sym, declared.bindings, shape.resSym, source, declared.annotation, selected, receiver, declared.positive, shape.markss, specialization): value => + transportShape(value, shape.markss) match case value: TermShape => listener(value) case NoShape => () case _ => fromBMSAt(shape.sym, shape.resSym, shape.markss, listener, source, selected, receiver, specialization) @@ -2117,7 +2191,9 @@ class NewResolver: val exited = sym match case _: ModuleOrObjectSymbol => sh case _ => MarkedShape.exit(sh, ResolutionBoundary(sym), S(resSym)) - exited.exit(markss) match + (exited match + case value: TermShape => transportShape(value, markss) + case NoShape => NoShape) match case NoShape => log(s"FILTER OUT ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") case sh: TermShape => @@ -2158,8 +2234,8 @@ class NewResolver: ((d.sign.nonEmpty && (!d.flags.hasResultAnnotation || d.params.forall(ps => (ps.params ::: ps.restParam.toList).forall(_.sign.nonEmpty)))) || (d.body.isEmpty && d.tsym.owner.exists(_.asDefnSym.defn.exists(_.tparams.nonEmpty)))) => - listenDeclaredMember(bms, instanceBindings(d, markss), resSym, trm, N, selected, receiver, true, specialization): value => - value.exit(markss) match + listenDeclaredMember(bms, instanceBindings(d, markss), resSym, trm, N, selected, receiver, true, markss, specialization): value => + transportShape(value, markss) match case value: TermShape => listener(value) case NoShape => () case S(d: TermDefinition) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 7e857ec0ff..ecaaadc61a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -283,8 +283,8 @@ final class NewResolverState private ( new Seen(inherited.map(_.exposedTypeHoles)) val signatureParameters: Cache[VarSymbol, DeclaredType] = new Cache(inherited.map(_.signatureParameters), identity) - val capturedTypes: Cache[(DeclaredType, AnyDefinitionSymbol), DeclaredType] = - new Cache(inherited.map(_.capturedTypes), identity) + val contextualTypes: Cache[ContextualType, DeclaredType] = + new Cache(inherited.map(_.contextualTypes), identity) val tupleArrayParents: Cache[Identity[TupleShape], NominalInstanceView] = new Cache(inherited.map(_.tupleArrayParents), identity) val instanceParameterTypes: Cache[(VarSymbol, Bool), DeclaredType] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 5060c1a49e..5eb59db8b3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -457,7 +457,19 @@ final case class NominalInstanceView(defn: ClassLikeDef, bindings: Map[VarSymbol case _ => N protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = defn.body.members.get(name) match - case S(member) => MemberLookup.Declared(member, bindings, Nil, annotation, true) + case S(member) => + // Supplied arguments are outside the class; its parameter annotations + // are inside. Enclosing binders keep their own lexical contexts and + // enter the class through the annotation's explicit Capture nodes. + val parameters = defn.tparams.map(_.sym).toSet + // Module/object references introduce no value invocation boundary. + val scope = defn.sym match + case _: ModuleOrObjectSymbol => N + case symbol => S(ResolutionBoundary(symbol)) + val local = bindings.map: (symbol, bound) => + symbol -> (if parameters(symbol) && scope.nonEmpty then resolver.captureType(bound, defn.sym) else bound) + MemberLookup.Declared(member, local, + scope.toList.map(ExitMark(_, N, NoMarks)), annotation, true) case N => (name.toIntOption, resolver.arrayElementType(this)) match case (S(index), S(element)) if index >= 0 => MemberLookup.Indexed(TupleShape.TypedField(element, Nil), Nil) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index dce62951ec..ed3abb473b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -23,6 +23,9 @@ enum TypeShape: // Substitution selects one part before the type is used in further constraints. // The argument stays in its own lexical environment, including delayed parts. case SelectedArgument(argument: DeclaredType, positive: Bool) + // A transported reference retains its context before any deferred component + // is observed. The resolver flattens these nodes and normalizes their marks. + case Contextual(reference: ContextualType) // The same third-party symbol can have different inference in two exporters. // Retain its originating host so importing a result needs no whole-state copy. case Parameter(symbol: VarSymbol, host: Publisher.Data[Shape]) @@ -91,6 +94,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case Wildcard(input, output) => input.foreach(_.validate(next)); output.foreach(_.validate(next)) case Argument(parts) => parts.input.resolution.validate(next); parts.output.resolution.validate(next) case SelectedArgument(argument, _) => argument.resolution.validate(next) + case Contextual(reference) => reference.tpe.resolution.validate(next) case Function(_, result) => result.validate(next) case Polymorphic(params, _, body) => params.foreach: param => diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index ae501b086d..3aab9f12c2 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -130,10 +130,9 @@ let methods = new GenericLibrary.Methods // FIXME: A callback reading a constructor field in a module initializer still // crosses incompatible lexical capture paths, before any consumer imports it. -:fixme :expect [5] module Local with class Known(val known: Int) val mapped = [Known(4)].map((item, ...) => Known(item.known + 1)) Local.mapped.map((item, ...) => item.known) -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: term:Local⁰/mapped⁰ vs term:Array⁰/map⁰ for InstanceShape(member:Int⁰^class:Known⁰) +//│ = [5] diff --git a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls index f47fcc05d7..e46378855f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls @@ -12,7 +12,6 @@ both(Pair([First(1), Second(2)]), Pair([Second(3), First(4)])) // TODO: preserve distinct callers through a recursive hole reference. // This hole must use ordinary inference, not fresh call-site parameter symbols. -:fixme :expect [5, 6] class First(val first: Int) class Second(val second: Int) @@ -23,20 +22,7 @@ let ys = mut [] append(xs, First(5), 2) append(ys, Second(6), 2) [xs.0.first, ys.0.second] -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.25: [xs.0.first, ys.0.second] -//│ ║ ^^^^^^^^^^^ -//│ ╟── Instance of class 'First' does not contain member 'second' -//│ ║ l.23: append(xs, First(5), 2) -//│ ╙── ^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.25: [xs.0.first, ys.0.second] -//│ ║ ^^^^^^^^^^ -//│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.24: append(ys, Second(6), 2) -//│ ╙── ^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '[5, 6]', got: 'undefined' +//│ = [5, 6] //│ xs = [First(5)] //│ ys = [Second(6)] @@ -48,7 +34,7 @@ class Pair[A, B](val value: [A, B]) private fun first(pair: Pair[Base]) = pair.value.0.extra first(Pair([Child(7), 0])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.48: private fun first(pair: Pair[Base]) = pair.value.0.extra +//│ ║ l.34: private fun first(pair: Pair[Base]) = pair.value.0.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -60,14 +46,14 @@ class Box[T](val item: T) fun read(box: Box) = box.item.value read(Box(Item(8))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.60: fun read(box: Box) = box.item.value +//│ ║ l.46: fun read(box: Box) = box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── Parameter 'box' admits values of unknown shape. -//│ ║ l.60: fun read(box: Box) = box.item.value +//│ ║ l.46: fun read(box: Box) = box.item.value //│ ║ ^^^^^^^^ //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.60: fun read(box: Box) = box.item.value +//│ ║ l.46: fun read(box: Box) = box.item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -98,16 +84,16 @@ type SomeBox = Box private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] both(Box(First(11)), Box(Second(12))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.98: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ l.84: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'First' does not contain member 'second' -//│ ║ l.99: both(Box(First(11)), Box(Second(12))) +//│ ║ l.85: both(Box(First(11)), Box(Second(12))) //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.98: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ l.84: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.99: both(Box(First(11)), Box(Second(12))) +//│ ║ l.85: both(Box(First(11)), Box(Second(12))) //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[11, 12]', got: 'undefined' @@ -117,13 +103,13 @@ both(Box(First(11)), Box(Second(12))) class Box[T](val item: T) private fun excess(value: Box[Int, Str]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.118: private fun excess(value: Box[Int, Str]) = () +//│ ║ l.104: private fun excess(value: Box[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^ //│ ╟── Type 'Box' accepts at most 1 type argument, but got 2. -//│ ║ l.118: private fun excess(value: Box[Int, Str]) = () +//│ ║ l.104: private fun excess(value: Box[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. -//│ ║ l.117: class Box[T](val item: T) +//│ ║ l.103: class Box[T](val item: T) //│ ╙── ^ // A type alias checks its own argument list before interpreting the body. @@ -132,13 +118,13 @@ class Box[T](val item: T) type Alias[A] = Box[A] private fun excess(value: Alias[Int, Str]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.133: private fun excess(value: Alias[Int, Str]) = () +//│ ║ l.119: private fun excess(value: Alias[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Type 'Alias' accepts at most 1 type argument, but got 2. -//│ ║ l.133: private fun excess(value: Alias[Int, Str]) = () +//│ ║ l.119: private fun excess(value: Alias[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. -//│ ║ l.132: type Alias[A] = Box[A] +//│ ║ l.118: type Alias[A] = Box[A] //│ ╙── ^^^^^^ // Annotated results do not hide missing input types from exposure checking. @@ -148,11 +134,11 @@ class Box[T](val item: T) fun read(box: Box): Int = box.item.value read(Box(Item(13))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.148: fun read(box: Box): Int = box.item.value +//│ ║ l.134: fun read(box: Box): Int = box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.148: fun read(box: Box): Int = box.item.value +//│ ║ l.134: fun read(box: Box): Int = box.item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -163,14 +149,14 @@ class Box[T](val item: T) fun read(callback: () -> Box) = callback().item.value read(() => Box(Item(14))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.163: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.149: fun read(callback: () -> Box) = callback().item.value //│ ║ ^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── Parameter 'callback' admits values of unknown shape. -//│ ║ l.163: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.149: fun read(callback: () -> Box) = callback().item.value //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.163: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.149: fun read(callback: () -> Box) = callback().item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index a1d12eaaa3..2d12bf0850 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -170,7 +170,7 @@ keep(9, ...growing(3), 8) // Hide both length and element types while retaining concrete runtime test values. -fun opaque(values): Array = values +fun opaque(values): Array[Any] = values let arr = opaque([1,2,3,4]) //│ arr = [1, 2, 3, 4] @@ -234,15 +234,9 @@ invokeUnknown(...opaque([() => 95])) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.232: fun invokeUnknown(f) = f() //│ ║ ^^^ -//│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.232: fun invokeUnknown(f) = f() -//│ ║ ^ -//│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.232: fun invokeUnknown(f) = f() -//│ ║ ^ -//│ ╟── 'invokeUnknown' is exposed by this compilation unit. -//│ ║ l.232: fun invokeUnknown(f) = f() -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Value of abstract type cannot be called like a function. +//│ ║ l.173: fun opaque(values): Array[Any] = values +//│ ╙── ^^^ //│ = 95 // Unknown elements must not silently resolve against another known candidate. @@ -251,22 +245,19 @@ fun inspectUnknown(x) = x.value inspectUnknown(Box(96)) inspectUnknown(...opaque([Box(96)])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.250: fun inspectUnknown(x) = x.value +//│ ║ l.244: fun inspectUnknown(x) = x.value //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.250: fun inspectUnknown(x) = x.value -//│ ║ ^ -//│ ╟── 'inspectUnknown' is exposed by this compilation unit. -//│ ║ l.250: fun inspectUnknown(x) = x.value -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── This abstract type does not specify a member interface. +//│ ║ l.173: fun opaque(values): Array[Any] = values +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Too many known fields remain a proven mismatch even with an unknown spread. :e four(...opaque([]), 1, 2, 3, 4, 5) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.267: four(...opaque([]), 1, 2, 3, 4, 5) +//│ ║ l.258: four(...opaque([]), 1, 2, 3, 4, 5) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got at least 5 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -278,17 +269,11 @@ four(...opaque([]), 1, 2, 3, 4, 5) fun invokeRest(f, x, ...tail) = f() invokeRest(...opaque([() => 97]), Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.278: fun invokeRest(f, x, ...tail) = f() +//│ ║ l.269: fun invokeRest(f, x, ...tail) = f() //│ ║ ^^^ -//│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.278: fun invokeRest(f, x, ...tail) = f() -//│ ║ ^ -//│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.278: fun invokeRest(f, x, ...tail) = f() -//│ ║ ^ -//│ ╟── 'invokeRest' is exposed by this compilation unit. -//│ ║ l.278: fun invokeRest(f, x, ...tail) = f() -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Value of abstract type cannot be called like a function. +//│ ║ l.173: fun opaque(values): Array[Any] = values +//│ ╙── ^^^ //│ = 97 // When repeatedTuples receives [Box(98)], both a and b must acquire its element @@ -322,16 +307,16 @@ invokeTypedRest(...callbacks, Box(0)) fun invokePublicRest(f, x, ...tail) = f() invokePublicRest(...callbacks, Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.322: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.307: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.322: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.307: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.322: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.307: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── 'invokePublicRest' is exposed by this compilation unit. -//│ ║ l.322: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.307: fun invokePublicRest(f, x, ...tail) = f() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 97 @@ -367,7 +352,7 @@ class Child extends Base with fun readBase(xs: Array[Base]) = first(...xs).extra readBase([new Child]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.367: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ║ l.352: fun readBase(xs: Array[Base]) = first(...xs).extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -406,16 +391,16 @@ invokeUnannotatedArray(...unknownCallbacks) private fun firstInArray(xs) = first(...xs) [firstInArray(lefts).left, firstInArray(rights).right] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.407: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.392: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Right' does not contain member 'left' -//│ ║ l.378: class Right(val right: Int) +//│ ║ l.363: class Right(val right: Int) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.407: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.392: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Left' does not contain member 'right' -//│ ║ l.377: class Left(val left: Int) +//│ ║ l.362: class Left(val left: Int) //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[43, 44]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls b/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls index 55a0c72ef9..bf252df8c0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls @@ -93,8 +93,8 @@ box.apply(x => x.value) //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This abstract type does not specify a member interface. -//│ ║ l.88: fun apply(f: T -> Int): Int = f(item) -//│ ╙── ^ +//│ ║ l.89: let box = (new Box(Item(8)) as Box[in Item]) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Box(Item(8)) diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index bdfddeaf2e..8d5e60d3c4 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -49,14 +49,13 @@ make[Item](Item(0)).run(Item(3)).value //│ = 3 // A member alias must retain the receiver context through its method result. -:fixme :expect 4 class Item(val value: Int) class Box[A](val value: A) with type Slot = {item: A} fun get(): Slot = {item: value} (new Box[Item](Item(4))).get().item.value -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: class:Box⁰ vs term:Box⁰/get⁰ for NominalInstanceView(class:Item⁰) +//│ = 4 // Resetting an argument to a closed type has a finite structural unfolding. // The environment retained on Int must not nest the previous alias binding. @@ -96,7 +95,6 @@ private fun delayed[A](value: A) = //│ = [5, 6] // A nominal annotation must retain outer binders used only by its members. -:fixme :expect [7, 8] class First(val first: Int) class Second(val second: Int) @@ -105,4 +103,37 @@ private fun local[A](value: A) = let box = (new Local(value) as Local) box.item [local[First](First(7)).first, local[Second](Second(8)).second] -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: class:Local⁰ vs term:local⁰ for NominalInstanceView(class:First⁰) +//│ = [7, 8] + +// A separate signature reached through a member alias keeps its result context. +:expect [9, 10] +class First(val first: Int) +class Second(val second: Int) +class Box[A](val value: A) with + type Slot = {item: A} + type Getter = () -> Slot + fun get: Getter + fun get() = {item: value} +[(new Box[First](First(9))).get().item.first, + (new Box[Second](Second(10))).get().item.second] +//│ = [9, 10] + +// An inherited field retains the supplied argument through both class scopes. +:expect [11, 12] +class First(val first: Int) +class Second(val second: Int) +class Parent[A](val item: A) +class Child[A](item: A) extends Parent[A](item) +private fun read[A](child: Child[A]) = child.item +[read[First](new Child[First](First(11))).first, + read[Second](new Child[Second](Second(12))).second] +//│ = [11, 12] + +// A nominal module annotation introduces no class invocation boundary. +:expect 13 +class Item(val value: Int) +module Items with + val item: Item = Item(13) +private fun read(module items: Items) = items.item +read(Items).value +//│ = 13 From 7a4702f14b85d4655c057c6cfc16698e6055653e Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 12:11:58 +0800 Subject: [PATCH 112/228] Retain input obligations for supplied member type arguments --- doc/new-resolution-type-value-flow.md | 51 ++++++++-------- .../test/scala/hkmc2/TypeRelationTest.scala | 15 +++-- .../scala/hkmc2/semantics/NewResolver.scala | 26 +++----- .../hkmc2/semantics/NewResolverState.scala | 27 ++++----- .../mlscript/newres/ConstructorInstances.mls | 59 +++++++++++++++---- 5 files changed, 98 insertions(+), 80 deletions(-) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 50e80cb421..e78fe6c224 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -72,8 +72,11 @@ nested function and nominal types. The explicit-binder recursive array acceptanc cases pass. Supplied function arguments receive input obligations while retaining their declared output interface. -Replacing the remaining explicit-argument flags and positive-only member -conversion, reconstructed receiver contexts, inferred nominal member interfaces, +Constructed receivers retain symbolic type-parameter endpoints for declared +members, so supplied callback types receive input obligations as well as providing +output interfaces. Suppliedness is recorded on the canonical parameter instance; +marks select its enclosing activation, rather than determining whether it is +supplied. Reconstructed receiver contexts, inferred nominal member interfaces, and general recursive alias environments still need integration. Omitted arguments use source-owned inference holes. The recursive array-hole regression now preserves independent callers; distinguishing two omissions inside a shared alias body remains @@ -196,10 +199,13 @@ alternative, while transporting its wrapper preserves the original union node. observes them for structural comparison. For an explicitly supplied argument, an input obligation follows its retained type reference instead of becoming an additional output candidate. Each distinct supplied type receives the obligation; -a supplied union stays whole. The implementation still identifies supplied -positions with explicit-argument flags. Integrating those positions into the -shared reference graph remains necessary for constructor views and general -recursive type environments. +a supplied union stays whole. The implementation records suppliedness by canonical +parameter instance. One authoritative definition/site always has the same supplied +positions, even when nested comparisons observe it from different scopes. Only +positions with an actual argument are marked supplied. Marks still select the +applicable bounds and filter other enclosing activations; they do not determine +whether a supplied slot is fixed. The metadata is consumer-owned and does not add +symbols or contextual cache keys. ## Variance rules @@ -504,8 +510,8 @@ upper target, preservation of a whole negative union, reverse endpoint contexts, and independent importers. The supplied-argument tests additionally check input obligations arriving before and after supplied references, chained parameters in different marked contexts, isolation between activations, and cycles without -listener growth. These checks do not yet replace the remaining explicit-argument -flags or establish the whole-graph termination bound. +listener growth. These checks do not establish the whole-graph termination bound; suppliedness +metadata and endpoint context have separate roles as described above. Existing marks still transport instance flow through lexical scopes and distinguish enclosing activations sharing a static inner call. Apply their current entry/exit @@ -731,17 +737,15 @@ let callbacks = new mut Array[Item -> Int](0) callbacks.push((x) => x.value) ``` -The expected input of `x` is `Item`, but its member target remains unresolved. -`instanceBindings` still expands explicitly supplied class arguments into -positive interfaces. Simply replacing this conversion with `TypeShape.Parameter` -is insufficient: nested nominal comparisons can reach that parameter with no -outer constraint marks, even though the incoming value retains class and member -entries. The explicit-argument flag then fails to identify the supplied slot and -incorrectly treats the input as another output candidate. The shared reference -graph must retain the supplied endpoint and its context through those comparisons; -it must replace this conversion and the flag-based routing together. Both failures -also occur before constructor call-site instantiation; they are not accepted -behavior or reasons to change marks or allocate additional variables. +The expected input of `x` is `Item`, including when the array is never invoked. +`instanceBindings` preserves a symbolic `TypeShape.Parameter` endpoint for supplied +and inferred class arguments alike. Nested nominal comparisons can observe that +endpoint without outer constraint marks. Suppliedness therefore follows the +canonical parameter instance, while the retained endpoint marks choose which +supplied bound receives each obligation. The callback-input regressions pass, +including rejection of a subclass-only input member and preservation of the +written callback result. No implementation candidate is added to a supplied +parameter by an input constraint. Reconstruction of `Box[T]` above remains open. Pre-application observation of an inferred result is still missing. `newres/StoredSpecializations.mls` retains this concrete regression: @@ -783,11 +787,10 @@ arguments and recursive environments, independently of this instantiation fix. retaining their hosts, substitutions, and marks. Ordinary arguments contribute bounds; supplied types receive all obligations at their applicable polarity. Apply the InvalML variance rules above. -4. Complete supplied-argument references and replace explicit-argument suppression - and the positive-only conversion in `instanceBindings` together. Functions - and constructors share instantiation-site binder allocation and retain consumed - groups; their remaining input/output obligations must use the same reference - mechanism as nominal arguments and declared array interfaces. +4. Supplied member arguments retain symbolic references, and suppliedness is keyed + by canonical instance rather than observation path. Functions and constructors + share instantiation-site binder allocation and retain consumed groups. Preserve + this behavior while finishing reconstructed receiver and deferred body views. 5. Route declared member lookup to partial member schemes, retaining receiver contexts for inferred fields and results. Check override compatibility before completing member targets. Convert the member-inference `:fixme`s together; diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 8af4eee721..04d5c20fa8 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -335,7 +335,7 @@ class TypeRelationTest extends AnyFunSuite: val two = h.tpe(TypeShape.Abstract) val first = IntroShape(Term.UnitVal(), N) val second = DynShape() - h.state.markExplicitTypeArgument(a, Nil) + h.state.markExplicitTypeArgument(a) h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(first)), Nil), ContextualType(at, Nil)) h.resolver.publishParameter(a, InstanceShape(one)) h.resolver.publishParameter(a, InstanceShape(two)) @@ -352,7 +352,7 @@ class TypeRelationTest extends AnyFunSuite: val two = h.tpe(TypeShape.Abstract) val union = h.tpe(TypeShape.Union(one.resolution, two.resolution)) val bound = IntroShape(Term.UnitVal(), N) - h.state.markExplicitTypeArgument(a, Nil) + h.state.markExplicitTypeArgument(a) h.resolver.publishParameter(a, InstanceShape(union)) h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil), ContextualType(at, Nil)) assert(h.state.typeConstraints((union, bound, Nil))) @@ -372,8 +372,8 @@ class TypeRelationTest extends AnyFunSuite: val targetMarks = ExitMark(target, S(site), NoMarks) :: Nil val concrete = h.tpe(TypeShape.Unit) val bound = IntroShape(Term.UnitVal(), N) - h.state.markExplicitTypeArgument(a, targetMarks) - h.state.markExplicitTypeArgument(b, sourceMarks) + h.state.markExplicitTypeArgument(a) + h.state.markExplicitTypeArgument(b) h.resolver.publishParameter(a, InstanceShape(bt).exit(sourceMarks).enter(targetMarks)) h.resolver.publishParameter(b, InstanceShape(concrete).enter(sourceMarks)) h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil), ContextualType(at, targetMarks)) @@ -390,8 +390,7 @@ class TypeRelationTest extends AnyFunSuite: val one = h.tpe(TypeShape.Unit) val two = h.tpe(TypeShape.Abstract) val bound = IntroShape(Term.UnitVal(), N) - h.state.markExplicitTypeArgument(a, firstMarks) - h.state.markExplicitTypeArgument(a, secondMarks) + h.state.markExplicitTypeArgument(a) h.resolver.publishParameter(a, InstanceShape(one).enter(firstMarks)) h.resolver.publishParameter(a, InstanceShape(two).enter(secondMarks)) h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil), ContextualType(at, firstMarks)) @@ -406,8 +405,8 @@ class TypeRelationTest extends AnyFunSuite: val concrete = h.tpe(TypeShape.Unit) val bound = IntroShape(Term.UnitVal(), N) val lower = ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil) - h.state.markExplicitTypeArgument(a, Nil) - h.state.markExplicitTypeArgument(b, Nil) + h.state.markExplicitTypeArgument(a) + h.state.markExplicitTypeArgument(b) h.resolver.publishParameter(a, InstanceShape(bt)) h.resolver.publishParameter(b, InstanceShape(at)) h.resolver.constrainTypes(lower, ContextualType(at, Nil)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 7b8a3fd013..3b99385e8e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -886,7 +886,7 @@ class NewResolver: (using NewResolverState): Unit = // Establish every supplied position before publication can replay body // constraints or trigger observations of a mutually dependent parameter. - parameters.foreach(p => rstate.markExplicitTypeArgument(p.symbol, marks)) + parameters.zip(arguments).foreach((p, _) => rstate.markExplicitTypeArgument(p.symbol)) parameters.zip(arguments).foreach: (parameter, argument) => publishParameter(parameter.host, InstanceShape(argument).enter(marks)) @@ -960,7 +960,7 @@ class NewResolver: val target = tpe.instances.get(symbol) val destination = target.fold(host)(_.inferenceHost) val lower = value.enter(captures) - if rstate.hasExplicitTypeArgument(target.getOrElse(symbol), marks) then + if rstate.hasExplicitTypeArgument(target.getOrElse(symbol)) then listenTypeArgument(destination): supplied => // Compare in the caller's scope, retaining the supplied // reference's endpoint for any further parameter constraint. @@ -2109,24 +2109,14 @@ class NewResolver: case N => listener(implicitParent(defn)) - /** Bodyless members reached through constructed instances need the same - * signature bindings as members reached through nominal annotations. Explicit - * constructor arguments are read-only interfaces; inferred arguments remain - * writable parameter flow. Keep the class context on each delivered value. + /** Constructed receivers and nominal annotations share the same symbolic + * parameter endpoints. Explicit arguments still receive input obligations; + * observing only their output candidates would discard callback constraints. */ - private def instanceBindings(td: TermDefinition, marks: Ls[Marks])(using NewResolverState): Map[VarSymbol, DeclaredType] = + private def instanceBindings(td: TermDefinition)(using NewResolverState): Map[VarSymbol, DeclaredType] = td.tsym.owner.toList.flatMap(_.asDefnSym.defn.toList).flatMap(_.tparams).map: param => val symbol = rstate.instances.getOrElse(param.sym, param.sym) - val explicit = rstate.hasExplicitTypeArgument(symbol, marks) - val bound = rstate.instanceParameterTypes.getOrElseUpdate((symbol, explicit), { - val source = SimpleRef(param.sym)(param.sym.id) - val resolution = new TypeResolution(source, messages => resolError(source, messages)) - if explicit then listenTypeArgument(symbol): value => - resolution.publish(TypeShape.Inferred(value)) - else resolution.publish(TypeShape.Parameter(symbol, symbol.inferenceHost)) - declaredType(resolution, Map.empty) - }) - param.sym -> bound + param.sym -> parameterType(TypeShape.Parameter(symbol, symbol.inferenceHost)) .toMap private def fromSymbol(shape: SymShape, listener: Listener, source: Term, @@ -2234,7 +2224,7 @@ class NewResolver: ((d.sign.nonEmpty && (!d.flags.hasResultAnnotation || d.params.forall(ps => (ps.params ::: ps.restParam.toList).forall(_.sign.nonEmpty)))) || (d.body.isEmpty && d.tsym.owner.exists(_.asDefnSym.defn.exists(_.tparams.nonEmpty)))) => - listenDeclaredMember(bms, instanceBindings(d, markss), resSym, trm, N, selected, receiver, true, markss, specialization): value => + listenDeclaredMember(bms, instanceBindings(d), resSym, trm, N, selected, receiver, true, markss, specialization): value => transportShape(value, markss) match case value: TermShape => listener(value) case NoShape => () diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index ecaaadc61a..89635e33eb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -287,8 +287,6 @@ final class NewResolverState private ( new Cache(inherited.map(_.contextualTypes), identity) val tupleArrayParents: Cache[Identity[TupleShape], NominalInstanceView] = new Cache(inherited.map(_.tupleArrayParents), identity) - val instanceParameterTypes: Cache[(VarSymbol, Bool), DeclaredType] = - new Cache(inherited.map(_.instanceParameterTypes), identity) val parameterTypes: Cache[TypeShape.Parameter, DeclaredType] = new Cache(inherited.map(_.parameterTypes), identity) val mutableArrays: Cache[Identity[Term.Mut], TermShape] = @@ -299,21 +297,16 @@ final class NewResolverState private ( new Cache(inherited.map(_.namedTupleRecords), identity) val aggregateProducers: Seen[Identity[Tup | Rcd]] = new Seen(inherited.map(_.aggregateProducers)) - // Explicit instantiations are visible to every graph view in this consumer; - // inherited flags are queried without enumerating an exporter's instantiations. - private val explicitTypeArguments = mutable.Set.empty[(VarSymbol, Marks)] - private def argumentContext(marks: Ls[Marks])(using TL): Opt[Marks] = - // A path can arrive as several fragments or one composed mark. Use the - // existing transport operation so these identify the same supplied slot. - DynShape().exit(marks) match - case Marked(_, context) => S(context) - case NoShape => N - def markExplicitTypeArgument(symbol: VarSymbol, marks: Ls[Marks])(using TL): Unit = - argumentContext(marks).foreach(context => root.explicitTypeArguments += ((symbol, context))) - def hasExplicitTypeArgument(symbol: VarSymbol, marks: Ls[Marks])(using TL): Bool = - argumentContext(marks).exists(context => hasExplicitTypeArgument((symbol, context))) - private def hasExplicitTypeArgument(key: (VarSymbol, Marks)): Bool = - root.explicitTypeArguments(key) || source.exists(_.hasExplicitTypeArgument(key)) + // Whether a binder is supplied is fixed by its authoritative syntactic site. + // Each definition/site has its own parameter instances; marks distinguish the + // enclosing activations of their bounds, not whether the slot is explicit. + // A method projection or nested constraint can change the observation scope + // without changing this property. Missing supplied positions remain inferred. + private val explicitTypeArguments = mutable.Set.empty[VarSymbol] + def markExplicitTypeArgument(symbol: VarSymbol): Unit = + root.explicitTypeArguments += symbol + def hasExplicitTypeArgument(symbol: VarSymbol): Bool = + root.explicitTypeArguments(symbol) || source.exists(_.hasExplicitTypeArgument(symbol)) val typeConstraints: Seen[(DeclaredType, TermShape, Ls[Marks])] = new Seen(inherited.map(_.typeConstraints)) val typeRelations: Seen[(ContextualType, ContextualType)] = diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls index d62bd1dcba..c71a9b03da 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls @@ -116,19 +116,14 @@ private val invalid = Box[Int, Str] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ invalid = fun Box { class: class Box } -// Supplied member types must eventually receive callback input obligations too. -:fixme +// Supplied member types receive callback input obligations too. :expect 19 class Item(val value: Int) let callbacks = new mut Array[Item -> Int](0) callbacks.push((x) => x.value) private fun first(callback, ...tail) = callback first(...callbacks)(Item(19)) -//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.124: callbacks.push((x) => x.value) -//│ ╙── ^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '19', got: 'undefined' +//│ = 19 //│ callbacks = [fun] // Type application cannot turn a constructed object back into its constructor. @@ -138,16 +133,16 @@ class Box[T](val item: T) let box = new Box[Int](1) box[Str]("bad") //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.139: box[Str]("bad") +//│ ║ l.134: box[Str]("bad") //│ ║ ^^^^^^^^ //│ ╟── Class 'Box' expected 0 type arguments, but got 1 -//│ ║ l.137: class Box[T](val item: T) +//│ ║ l.132: class Box[T](val item: T) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.139: box[Str]("bad") +//│ ║ l.134: box[Str]("bad") //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Box' cannot receive more argument lists. -//│ ║ l.138: let box = new Box[Int](1) +//│ ║ l.133: let box = new Box[Int](1) //│ ╙── ^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: box1 is not a function //│ box = Box(1) @@ -159,10 +154,48 @@ class Pair[T](val item: T)(val other: T) private val partial = Pair[Int](1) private val invalid = partial[Str] //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.160: private val invalid = partial[Str] +//│ ║ l.155: private val invalid = partial[Str] //│ ║ ^^^^^^^^^^^^ //│ ╟── Class 'Pair' expected 0 type arguments, but got 1 -//│ ║ l.158: class Pair[T](val item: T)(val other: T) +//│ ║ l.153: class Pair[T](val item: T)(val other: T) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ invalid = fun //│ partial = fun + +// The supplied callback input exposes only its written nominal interface. +:e +:ge +class Base +class Child(val extra: Int) extends Base +let callbacks = new mut Array[Base -> Int](0) +callbacks.push(x => x.extra) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.171: callbacks.push(x => x.extra) +//│ ║ ^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ = 1 +//│ callbacks = [fun] + +// The method input constrains the callback even if the array is never invoked. +:expect 1 +class Item(val value: Int) +let callbacks = new mut Array[Item -> Int](0) +callbacks.push(x => x.value) +//│ = 1 +//│ callbacks = [fun] + +// A more precise implementation must not refine the supplied callback result. +:e +:ge +class Base +class Child(val extra: Int) extends Base +let callbacks = new mut Array[Base -> Base](0) +callbacks.push(x => new Child(20)) +private fun first(callback, ...tail) = callback +first(...callbacks)(new Base).extra +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.195: first(...callbacks)(new Base).extra +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ callbacks = [fun] From 96ed81942fd3854a1881da4c9f5633620981e22a Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 12:25:25 +0800 Subject: [PATCH 113/228] Use ordinary marks for deferred reference transport --- doc/new-resolution-type-value-flow.md | 79 +++++++++++++++---- .../test/scala/hkmc2/TypeRelationTest.scala | 41 +++++++++- .../scala/hkmc2/semantics/NewResolver.scala | 26 ++---- .../test/mlscript/newres/InferenceHoles.mls | 59 ++++++++------ .../mlscript/newres/PartialSignatures.mls | 65 ++++++++++++--- 5 files changed, 198 insertions(+), 72 deletions(-) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index e78fe6c224..c8eddcb65c 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -78,9 +78,9 @@ output interfaces. Suppliedness is recorded on the canonical parameter instance; marks select its enclosing activation, rather than determining whether it is supplied. Reconstructed receiver contexts, inferred nominal member interfaces, and general recursive alias environments still need integration. Omitted arguments -use source-owned inference holes. The recursive array-hole regression now preserves -independent callers; distinguishing two omissions inside a shared alias body remains -unfinished. The +use source-owned inference holes. Omitted-argument rebasing currently loses caller +identity, including without recursion; distinguishing two omissions inside a shared +alias body also remains unfinished. See the context regression below. The implementation order below remains the full design, not a claim that all its parts are complete. See the [resolver notes](new-resolution-design.md) @@ -916,15 +916,21 @@ all use this operation; inferred holes receive the same context as supplied argu Nominal constraints compare argument endpoints in the common observation scope, including both their input and output parts. -A reference's lexical round trip must be composed before observing its host. In -particular, leaving a scope with a wildcard mark and immediately re-entering that -same lexical scope returns the same reference. Applying those operations to each -host candidate instead would erase a concrete caller's identity at the wildcard -exit. `transportType` cancels that pair on the reference. It never cancels a reverse -pair containing an explicit invocation site. Ordinary value-flow mark operations -are unchanged. `TypeRelationTest` checks early and late bounds from distinct caller -sites, retained explicit invocation entries, and a thousand repeated transfers -without additional listeners or binder instances. +Deferred references use the ordinary value-flow mark algebra. Leaving a scope +with a wildcard exit consumes a candidate's entry mark, including its call-site +identity. Re-entering with a wildcard entry does not restore that identity, even +when both operations name the same lexical scope. Those two operations must not +cancel each other on the reference: doing so would incorrectly preserve an earlier +caller's identity. `TypeRelationTest` compares deferred transport with +ordinary candidate transport for wildcard and explicit exits, entries, and consumer +sites, including bounds published before and after observation. Repeated transport +of the same reference reuses its cache entry without allocating binder instances. + +Rebasing application arguments into a captured template's scope is subject to these +same rules. It cannot assume that leaving and re-entering that scope is an identity. +If substitution requires a different relationship between the template and argument +contexts, that relationship needs an explicit design and supporting examples; it +must not be implemented by changing wildcard cancellation for deferred references. `transportShape` composes paths on deferred instance references at projection and result boundaries before delivering them to another caller. Leaving a wildcard @@ -938,6 +944,43 @@ regular type environments. Interning normalized endpoints prevents transport wrapper histories from growing; it does not prove those two separate bounds or justify truncating paths when an invariant fails. +### Omitted-argument context regression + +Removing the incorrect wildcard exit-entry cancellation exposes four failing +blocks, retained as `:fixme` regressions in `newres/PartialSignatures.mls` and +`newres/InferenceHoles.mls`. The failure does not require recursion: + +```mlscript +class First(val first: Int) +class Second(val second: Int) +class Pair[A, B](val value: [A, B]) +private fun second(pair: Pair[Int]) = pair.value.1 +[second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] +``` + +The intended result is `[1, 2]`. Currently both result selections receive both +`First` and `Second`, causing missing-member diagnostics. The other failing blocks +cover a bare `HalfPair` alias and mutable `Array` annotations with and without +recursive calls. + +The hole for `B` belongs to the annotation occurrence in `second`. Following the +captured `Pair` template moves that hole reference outward with a wildcard exit +from `second`. Projecting the argument back into the annotation scope then adds a +wildcard entry. The constraint interpreter applies the reversed reference path to +each incoming bound before publishing it into the hole. For a bound entering +`second` at call `c1`, the wildcard exit consumes the entry carrying `c1`; the +wildcard re-entry replaces it with an entry without an ID. The second call loses +its ID in the same way. The hole therefore already contains mixed caller bounds +before its output is observed. + +This is evidence of an incorrect context relationship in the interpreter, not a +proof that the ordinary marks algebra is insufficient. A replacement must explain +how an argument reference remains associated with its annotation scope while being +substituted into an outer template, including deferred fields and both constraint +directions. It must retain ordinary inference for holes and cannot invent binder +instances or assume that a wildcard exit-entry pair is reversible. Review that +representation before implementing a replacement. + ### Regular structural types The proposed restriction is that unfolding a structural type must admit a finite @@ -1129,16 +1172,18 @@ for it. `TypeRelationTest` checks reuse across repeated observations, empty host waiting for evidence, cyclic constraints with stable listener counts, and private bounds in separate importers sharing the same source identity. This bounds hole allocation and replay; it does not establish the missing context precision for -alias-body omissions noted above. The recursive omitted-array regression now -preserves independent caller contexts. +alias-body omissions noted above. The omitted-array regressions currently mix +caller contexts, both with and without recursion, as described in the context +regression above. During implementation, assert that a call instance's origin is an original binder, all of one scheme's binders are allocated before its constraints are activated, views are composed rather than nested, and generic checking witnesses never become ordinary instantiated lower bounds. Retain consumer-private host copies and the -existing prohibition on changing completed member targets. The contextual-reference round-trip rule above does not license changing ordinary -value-flow mark normalization if these checks fail. Any further change requires a -separate concrete example and proposal. +existing prohibition on changing completed member targets. Deferred references and +ordinary values must use the same mark algebra. If that algebra cannot express a +required context relationship, document a concrete example and propose an extension +before changing it. ## Acceptance checks diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 04d5c20fa8..259b7ea0f4 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -56,7 +56,7 @@ class TypeRelationTest extends AnyFunSuite: assert((a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) assert(seen.toList == List(first, second)) - test("lexical reference round trips preserve early and late caller identities"): + test("wildcard exit and reentry forget early and late caller identities"): val h = new Harness import h.given val (a, at) = h.parameter("A") @@ -67,20 +67,53 @@ class TypeRelationTest extends AnyFunSuite: val enter = EntryMark(owner, N, NoMarks) :: Nil val outside = h.resolver.transportType(at, exit) val back = h.resolver.transportType(outside, enter) - assert(back == at) + assert(back != at) val first = IntroShape(Term.UnitVal(), N) val second = DynShape() h.resolver.publishParameter(a, MarkedShape.enter(first, owner, S(firstSite))) + val inside = h.observe(ContextualType(back, Nil)) val seen = h.observe(ContextualType(back, ExitMark(owner, S(firstSite), NoMarks) :: Nil)) h.resolver.publishParameter(a, MarkedShape.enter(second, owner, S(secondSite))) - assert(seen.toList == List(first)) + assert(inside.toList == List(first, second).map(MarkedShape.enter(_, owner, N))) + assert(seen.toList == List(first, second)) val listeners = a.inferenceHost.listeners.size (1 to 1000).foreach: _ => assert(h.resolver.transportType(at, exit) eq outside) - assert(h.resolver.transportType(outside, enter) == at) + assert(h.resolver.transportType(outside, enter) eq back) assert(a.inferenceHost.listeners.size == listeners) assert(h.state.allocatedTypeInstanceCount == 0) + test("reference exit and reentry agree with value transport for wildcard and explicit sites"): + // Check both publication orders and every combination of wildcard/explicit + // exit, reentry, and consumer sites against the ordinary value operations. + for exitSite <- 0 to 2; entrySite <- 0 to 2; consumerSite <- 0 to 2 do + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val owner = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("owner"))) + val sites = Vector(N, S(FlowSymbol.app()), S(FlowSymbol.app())) + val exit = ExitMark(owner, sites(exitSite), NoMarks) + val entry = EntryMark(owner, sites(entrySite), NoMarks) + val consumer = ExitMark(owner, sites(consumerSite), NoMarks) + val first = MarkedShape.enter(IntroShape(Term.UnitVal(), N), owner, sites(1)) + val second = MarkedShape.enter(DynShape(), owner, sites(2)) + val outside = h.resolver.transportType(at, exit :: Nil) + val inside = h.resolver.transportType(outside, entry :: Nil) + h.resolver.publishParameter(a, first) + val observed = h.observe(ContextualType(inside, Nil)) + def expected(values: List[TermShape], path: List[Marks]): Set[TermShape] = + values.flatMap: value => + value.exit(path) match + case shape: TermShape => Some(shape) + case NoShape => None + .toSet + assert(observed.toSet == expected(List(first), List(exit, entry))) + h.resolver.publishParameter(a, second) + assert(observed.toSet == expected(List(first, second), List(exit, entry))) + val consumed = h.resolver.transportType(inside, consumer :: Nil) + assert(h.observe(ContextualType(consumed, Nil)).toSet == + expected(List(first, second), List(exit, entry, consumer))) + test("reference rebasing never cancels explicit invocation entries"): val h = new Harness import h.given diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 3b99385e8e..87b94dc0f9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -725,23 +725,10 @@ class NewResolver: case TypeShape.Contextual(_) => true case _ => false , "Contextual references must be flattened before they are interned") - def transfer(value: TermShape | NoShape, path: Marks): TermShape | NoShape = path match - case NoMarks => value - case EntryMark(boundary, N, rest) => transfer(value, rest) match - // These two wildcard operations rebase the same deferred reference - // out of a lexical scope and back. Cancel before observing its host: - // cancellation on the host's values would forget their caller sites. - // Explicit invocation marks are never erased by this rule. - case Marked(source, ExitMark(exited, N, tail)) if exited == boundary => source.exit(tail) - case value: TermShape => MarkedShape.enter(value, boundary, N) - case NoShape => NoShape - case EntryMark(boundary, site, rest) => transfer(value, rest) match - case value: TermShape => MarkedShape.enter(value, boundary, site) - case NoShape => NoShape - case ExitMark(boundary, site, rest) => transfer(value, rest) match - case value: TermShape => MarkedShape.exit(value, boundary, site) - case NoShape => NoShape - marks.foldLeft(InstanceShape(base).exit(previous))(transfer) match + // A wildcard exit followed by an entry is not an identity: the exit + // consumes a candidate's entry site and the new entry supplies its own. + // Deferred references use exactly the same transport as ordinary values. + InstanceShape(base).exit(previous).exit(marks) match case Marked(_, NoMarks) => base case Marked(_, path: SomeMarks) => val reference = ContextualType(base, path :: Nil) @@ -753,8 +740,9 @@ class NewResolver: case NoShape => extremeType(false) private def inverseMarks(marks: Ls[Marks])(using NewResolverState): Ls[Marks] = - // Inversion is an operation, not a reordered list of scopes. Use the same - // mark algebra as value transport and retain the resulting reduced path. + // Reverse directions using ordinary mark operations, not a reordered list + // of scopes. This is not an inverse on candidates: a wildcard exit can lose + // a call-site ID, which the reversed path cannot reconstruct. InstanceShape(extremeType(false)).enter(marks) match case Marked(_, NoMarks) => Nil case Marked(_, path: SomeMarks) => path :: Nil diff --git a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls index e46378855f..ae0075101b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls @@ -12,6 +12,8 @@ both(Pair([First(1), Second(2)]), Pair([Second(3), First(4)])) // TODO: preserve distinct callers through a recursive hole reference. // This hole must use ordinary inference, not fresh call-site parameter symbols. +// Template argument rebasing currently strips the caller IDs from its bounds. +:fixme :expect [5, 6] class First(val first: Int) class Second(val second: Int) @@ -22,7 +24,20 @@ let ys = mut [] append(xs, First(5), 2) append(ys, Second(6), 2) [xs.0.first, ys.0.second] -//│ = [5, 6] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.26: [xs.0.first, ys.0.second] +//│ ║ ^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.24: append(xs, First(5), 2) +//│ ╙── ^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.26: [xs.0.first, ys.0.second] +//│ ║ ^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.25: append(ys, Second(6), 2) +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[5, 6]', got: 'undefined' //│ xs = [First(5)] //│ ys = [Second(6)] @@ -34,7 +49,7 @@ class Pair[A, B](val value: [A, B]) private fun first(pair: Pair[Base]) = pair.value.0.extra first(Pair([Child(7), 0])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.34: private fun first(pair: Pair[Base]) = pair.value.0.extra +//│ ║ l.49: private fun first(pair: Pair[Base]) = pair.value.0.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -46,14 +61,14 @@ class Box[T](val item: T) fun read(box: Box) = box.item.value read(Box(Item(8))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.46: fun read(box: Box) = box.item.value +//│ ║ l.61: fun read(box: Box) = box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── Parameter 'box' admits values of unknown shape. -//│ ║ l.46: fun read(box: Box) = box.item.value +//│ ║ l.61: fun read(box: Box) = box.item.value //│ ║ ^^^^^^^^ //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.46: fun read(box: Box) = box.item.value +//│ ║ l.61: fun read(box: Box) = box.item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -84,17 +99,17 @@ type SomeBox = Box private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] both(Box(First(11)), Box(Second(12))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.84: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ l.99: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'First' does not contain member 'second' -//│ ║ l.85: both(Box(First(11)), Box(Second(12))) -//│ ╙── ^^^^^^^^^ +//│ ║ l.100: both(Box(First(11)), Box(Second(12))) +//│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.84: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ l.99: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.85: both(Box(First(11)), Box(Second(12))) -//│ ╙── ^^^^^^^^^^ +//│ ║ l.100: both(Box(First(11)), Box(Second(12))) +//│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[11, 12]', got: 'undefined' @@ -103,13 +118,13 @@ both(Box(First(11)), Box(Second(12))) class Box[T](val item: T) private fun excess(value: Box[Int, Str]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.104: private fun excess(value: Box[Int, Str]) = () +//│ ║ l.119: private fun excess(value: Box[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^ //│ ╟── Type 'Box' accepts at most 1 type argument, but got 2. -//│ ║ l.104: private fun excess(value: Box[Int, Str]) = () +//│ ║ l.119: private fun excess(value: Box[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. -//│ ║ l.103: class Box[T](val item: T) +//│ ║ l.118: class Box[T](val item: T) //│ ╙── ^ // A type alias checks its own argument list before interpreting the body. @@ -118,13 +133,13 @@ class Box[T](val item: T) type Alias[A] = Box[A] private fun excess(value: Alias[Int, Str]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.119: private fun excess(value: Alias[Int, Str]) = () +//│ ║ l.134: private fun excess(value: Alias[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Type 'Alias' accepts at most 1 type argument, but got 2. -//│ ║ l.119: private fun excess(value: Alias[Int, Str]) = () +//│ ║ l.134: private fun excess(value: Alias[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. -//│ ║ l.118: type Alias[A] = Box[A] +//│ ║ l.133: type Alias[A] = Box[A] //│ ╙── ^^^^^^ // Annotated results do not hide missing input types from exposure checking. @@ -134,11 +149,11 @@ class Box[T](val item: T) fun read(box: Box): Int = box.item.value read(Box(Item(13))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.134: fun read(box: Box): Int = box.item.value +//│ ║ l.149: fun read(box: Box): Int = box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.134: fun read(box: Box): Int = box.item.value +//│ ║ l.149: fun read(box: Box): Int = box.item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -149,14 +164,14 @@ class Box[T](val item: T) fun read(callback: () -> Box) = callback().item.value read(() => Box(Item(14))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.149: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.164: fun read(callback: () -> Box) = callback().item.value //│ ║ ^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── Parameter 'callback' admits values of unknown shape. -//│ ║ l.149: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.164: fun read(callback: () -> Box) = callback().item.value //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.149: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.164: fun read(callback: () -> Box) = callback().item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls index 6bd9f4fa21..dc782b2b83 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls @@ -23,15 +23,32 @@ private fun apply[A](x: A, f) = f(x) // An omitted trailing type argument is a hole inferred from each caller. // The written Int argument and the Pair interface remain in place. +// TODO: template argument rebasing must not introduce a lossy wildcard round trip. +:fixme :expect [1, 2] class First(val first: Int) class Second(val second: Int) class Pair[A, B](val value: [A, B]) private fun second(pair: Pair[Int]) = pair.value.1 [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] -//│ = [1, 2] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.33: [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.33: [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] +//│ ╙── ^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.33: [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.33: [second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[1, 2]', got: 'undefined' // Bare alias uses infer their omitted argument without losing the alias body. +// TODO: the same template argument rebasing loses caller IDs through the alias. +:fixme :expect [3, 4] class First(val first: Int) class Second(val second: Int) @@ -39,10 +56,25 @@ class Pair[A, B](val value: [A, B]) type HalfPair[T] = Pair[Int, T] private fun second(pair: HalfPair) = pair.value.1 [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] -//│ = [3, 4] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.58: [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.58: [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] +//│ ╙── ^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.58: [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.58: [second(Pair([0, First(3)])).first, second(Pair([0, Second(4)])).second] +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[3, 4]', got: 'undefined' // A bare Array annotation has an inferred element hole in both positions. // This case has no declared type binder to instantiate at either call. +// TODO: template argument rebasing currently mixes the two callers' hole bounds. +:fixme :expect [5, 6] class First(val first: Int) class Second(val second: Int) @@ -52,7 +84,20 @@ let ys = mut [] append(xs, First(5)) append(ys, Second(6)) [xs.0.first, ys.0.second] -//│ = [5, 6] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.86: [xs.0.first, ys.0.second] +//│ ║ ^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.84: append(xs, First(5)) +//│ ╙── ^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.86: [xs.0.first, ys.0.second] +//│ ║ ^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.85: append(ys, Second(6)) +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[5, 6]', got: 'undefined' //│ xs = [First(5)] //│ ys = [Second(6)] @@ -80,12 +125,12 @@ class Child extends Base with private fun read(x: Base) = x.item().value read(new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.80: private fun read(x: Base) = x.item().value -//│ ║ ^^^^^^^^^^^^^^ +//│ ║ l.125: private fun read(x: Base) = x.item().value +//│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.80: private fun read(x: Base) = x.item().value -//│ ╙── ^^^^^^ +//│ ║ l.125: private fun read(x: Base) = x.item().value +//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // A separate polymorphic signature retains binders for explicit type application. @@ -113,7 +158,7 @@ class Child extends Base with let identity = (x => x) as [A] -> A -> A identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.114: identity[Base](new Child).extra +//│ ║ l.159: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -124,10 +169,10 @@ identity[Base](new Child).extra let identity = (x => x) as [A] -> A -> A identity[Int, Int](1) //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.125: identity[Int, Int](1) +//│ ║ l.170: identity[Int, Int](1) //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Function with a declared signature expected 1 type argument, but got 2 -//│ ║ l.124: let identity = (x => x) as [A] -> A -> A +//│ ║ l.169: let identity = (x => x) as [A] -> A -> A //│ ╙── ^^^^^^ //│ = 1 //│ identity = fun identity From 9a9e938421de8a7374252732869b1d11f97238df Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 12:38:53 +0800 Subject: [PATCH 114/228] Document annotation substitution context requirements --- doc/new-resolution-type-value-flow.md | 44 ++++++++++++++ .../mlscript/newres/AnnotationContexts.mls | 57 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/newres/AnnotationContexts.mls diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index c8eddcb65c..a74048c4c2 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -981,6 +981,50 @@ directions. It must retain ordinary inference for holes and cannot invent binder instances or assume that a wildcard exit-entry pair is reversible. Review that representation before implementing a replacement. +### Template context versus argument context + +`newres/AnnotationContexts.mls` distinguishes a template's enclosing parameter from +an argument supplied where that template is used: + +```mlscript +private fun enclosing[A](x: A) = + type Both[B] = {left: A, right: B} + private fun wrap[B](y: B): Both[B] = {left: x, right: y} + wrap[Second](Second(2)) +``` + +`Both`'s free `A` belongs to `enclosing`, whereas the supplied `B` belongs to `wrap`. +The current implementation handles this explicit-argument example. The worksheet +checks two enclosing calls with different types, a passing unannotated control, +and a failing omitted-argument version marked `:fixme`. + +Simply removing `args.move(inverseMarks(reference.marks))` from the three type +interpreters is not a fix. That experiment leaves the omitted-argument failures +and makes the explicit-argument example fail a mark assertion: an entry into +`wrap` is matched against an exit from `enclosing`. The experimental change was +removed. Both directions of a constraint, as well as deferred member observation, +need the argument's original scope to be understood. Dropping the template's +transport instead is not a general solution either: the free `A` still needs its +path from `enclosing` into `wrap`. The template's free references and the supplied +arguments cannot uniformly receive the same treatment. + +The current interpreter substitutes arguments into the template and then applies +the template's transport to the resulting interface. That transport consequently +acts on substituted arguments as well as on references from the template itself. +Moving arguments backward first compensates for their different starting scopes, +but can lose caller identity through wildcard exits. + +A possible correction is to keep the template endpoint separate from the argument +environment, so that following a formal uses the supplied reference at its own +endpoint, while following a free reference uses the template's context. This is a +change to how substitution and transport compose, not permission to cancel a +wildcard exit-entry pair. It has not been implemented or established as a small +local change. Review the representation and its interaction with nominal member +projection, recursive aliases, occurrence polarity, and both constraint directions +before proceeding. Any new interpretation nodes must retain the finite source and +canonical-context bounds; allocating a new environment identity on each recursive +unfolding would not be acceptable. + ### Regular structural types The proposed restriction is that unfolding a structural type must admit a finite diff --git a/hkmc2/shared/src/test/mlscript/newres/AnnotationContexts.mls b/hkmc2/shared/src/test/mlscript/newres/AnnotationContexts.mls new file mode 100644 index 0000000000..0272584819 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/AnnotationContexts.mls @@ -0,0 +1,57 @@ +:. + +// A captured alias combines its enclosing binder with a supplied local binder. +:expect [1, 2, 3, 2] +class First(val first: Int) +class Second(val second: Int) +private fun enclosing[A](x: A) = + type Both[B] = {left: A, right: B} + private fun wrap[B](y: B): Both[B] = {left: x, right: y} + wrap[Second](Second(2)) +let one = enclosing[First](First(1)) +let two = enclosing[Second](Second(3)) +[one.left.first, one.right.second, two.left.second, two.right.second] +//│ = [1, 2, 3, 2] +//│ one = {left: First(1), right: Second(2)} +//│ two = {left: Second(3), right: Second(2)} + +// Ordinary value inference distinguishes the same two calls without an annotation. +:expect [1, 2, 1, 4] +class First(val first: Int) +class Second(val second: Int) +private fun enclosing[A](x: A) = + type Both[B] = {left: A, right: B} + private fun wrap(value) = value + [wrap({left: x, right: First(2)}), wrap({left: x, right: Second(4)})] +let result = enclosing[First](First(1)) +[result.0.left.first, result.0.right.first, result.1.left.first, result.1.right.second] +//│ = [1, 2, 1, 4] +//│ result = [{left: First(1), right: First(2)}, {left: First(1), right: Second(4)}] + +// A missing argument must retain its scope independently of the captured binder. +// TODO: rebasing the omitted B strips wrap's caller IDs before publishing its bounds. +:fixme +:expect [1, 2, 1, 4] +class First(val first: Int) +class Second(val second: Int) +private fun enclosing[A](x: A) = + type Both[B] = {left: A, right: B} + private fun wrap(value: Both) = value + [wrap({left: x, right: First(2)}), wrap({left: x, right: Second(4)})] +let result = enclosing[First](First(1)) +[result.0.left.first, result.0.right.first, result.1.left.first, result.1.right.second] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.42: [result.0.left.first, result.0.right.first, result.1.left.first, result.1.right.second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.40: [wrap({left: x, right: First(2)}), wrap({left: x, right: Second(4)})] +//│ ╙── ^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.42: [result.0.left.first, result.0.right.first, result.1.left.first, result.1.right.second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.40: [wrap({left: x, right: First(2)}), wrap({left: x, right: Second(4)})] +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[1, 2, 1, 4]', got: 'undefined' +//│ result = [{left: First(1), right: First(2)}, {left: First(1), right: Second(4)}] From 63cef5e26767dc22257b48257b19311731547a2c Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 12:55:39 +0800 Subject: [PATCH 115/228] Normalize applied aliases before retaining type environments --- doc/new-resolution-suite-migration.md | 2 + doc/new-resolution-type-value-flow.md | 64 ++++++++++++------- .../test/scala/hkmc2/TypeRelationTest.scala | 27 ++++++++ .../scala/hkmc2/semantics/NewResolver.scala | 36 ++++++++--- .../mlscript/newres/TypeGraphTermination.mls | 35 +++++++++- 5 files changed, 132 insertions(+), 32 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index fffb7b0a71..f0ac95ebc3 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -94,6 +94,8 @@ The inventory below separates these from compiler and prelude gaps. Concrete outstanding cases are in `newres/InstantiationSites.mls`, `StoredSpecializations.mls`, `ConstructorInstances.mls`, `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. + Loss of caller separation through omitted type arguments is an accepted current + limitation; defer that substitution redesign while completing the other cases. Decide how reassignment affects mutable storage's inferred interface, including across compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned array still checked against its initializer's tuple length; accumulating both diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index a74048c4c2..0d9b9b3887 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -20,7 +20,10 @@ The following semantic decisions are settled: enclosing activations of the same site. - Infer every missing signature part through ordinary marked flow, including holes within annotations and omitted generic arguments. These positions do not - introduce quantified binders or receive fresh symbols at calls. + introduce quantified binders or receive fresh symbols at calls. For now, accept + the [omitted-argument context limitation](#omitted-argument-context-regression): + these holes can lose caller separation. Fixing their substitution contexts is + deferred; it does not block the remaining explicit-argument implementation. - Apply the same rule to selected members' missing parameter, result, and field types through nominal annotations. Retain the receiver context and the selected declaration's member set; check overrides against that interface. @@ -45,8 +48,8 @@ implementation. This restriction and sharing regular recursive references are separate obligations; neither follows from the bound on call-site symbols. The finite call-site symbol bound alone is not a termination proof for the whole -resolver. Non-regular expansion and regular recursion through transparent -argument aliases still overflow. Contextual references preserve class-local aliases +resolver. Non-regular expansion still overflows. Fully supplied alias applications +are reduced before their arguments become part of recursive reference keys. Contextual references preserve class-local aliases and enclosing binders through nominal member projections. Dependency-based binding projection accepts regular argument resets, including compound constants and recursively unused arguments. The [fixed-point conditions](#fixed-points-and-implementation-checks) remain completion gates. @@ -588,10 +591,10 @@ argument. An omission written inside an alias body has its own source node and is transported with the alias use's context; expansion must not allocate more hole symbols. Excess arguments still indicate an arity error. -The positive `Pair[Int]`, `HalfPair`, and bare `Array` cases in -`newres/PartialSignatures.mls` now pass. Existing -`DeclaredTypes.mls` examples selecting members from omitted arguments without any -supporting flow remain negative tests: a hole is inferable, not evidence of an +The intended positive `Pair[Int]`, `HalfPair`, and bare `Array` cases in +`newres/PartialSignatures.mls` are tracked as `:fixme`s under the accepted +omitted-argument context limitation. Existing `DeclaredTypes.mls` examples selecting +members from omitted arguments without any supporting flow remain negative tests: a hole is inferable, not evidence of an arbitrary member. `TypeShape.Hole` retains a source inference host, cached by the type-use node and omitted formal parameter. Observation and constraint propagation share the argument-completion operation, so they reach the same hole. Bounds can @@ -615,8 +618,9 @@ symbols. Holes must retain ordinary marked inference; copying the hole at each call is not an acceptable repair. Also, with `type SomeBox = Box`, the hole in the alias body is currently shared by both parameters of `both(left: SomeBox, right: SomeBox)`, even though their alias-use references differ. -The contextual graph representation must preserve those uses without expanding -the alias or allocating more hole symbols. Neither case is covered by the passing +A future correction must preserve those uses without expanding the alias or +allocating more hole symbols; it is deferred under the accepted limitation. +Neither case is covered by the passing source-occurrence isolation test, which uses two directly written `Pair` annotations. The scheme therefore cannot be a closed type synthesized from whatever shapes @@ -931,6 +935,7 @@ same rules. It cannot assume that leaving and re-entering that scope is an ident If substitution requires a different relationship between the template and argument contexts, that relationship needs an explicit design and supporting examples; it must not be implemented by changing wildcard cancellation for deferred references. +The omitted-argument limitation is accepted for now; its correction is deferred. `transportShape` composes paths on deferred instance references at projection and result boundaries before delivering them to another caller. Leaving a wildcard @@ -1091,8 +1096,8 @@ previous binding map. The same holds for a fixed compound argument `Array[Int]`. Accepted regular types need finite reference keys and shared recursive edges, not a fresh nested environment on each visit. -Dependency pruning alone does not identify transparent forwarding aliases. This -additional regular regression still overflows: +Dependency pruning alone does not identify transparent forwarding aliases. Alias +application reduction now makes this regular regression reach a fixed point: ```mlscript type Identity[X] = X @@ -1100,17 +1105,32 @@ type Chain[A] = {value: A, next: Chain[Identity[A]]} fun read(chain: Chain[Int]): Int = chain.next.next.value ``` -The argument continues to denote `Int`, but successive cache keys retain -`(Identity[A], previous-environment)`. Here `A` is genuinely a free binder of the -argument expression, so discarding unused bindings cannot help. A regularity -check must not classify the transparent alias application as a growing type -constructor. Canonical references must resolve forwarding through aliases and -their substitutions, respecting argument variance and occurrence polarity. -This needs to work through multiple aliases and parameter permutations, with a -finite source-graph guard for unproductive alias cycles. Special-casing a directly -written identity alias would not establish the required representation bound. -`TypeGraphTermination.mls` records this case separately from non-regular array -nesting and from the receiver-context failures. +`declaredType` reduces a fully supplied application of a resolved alias before +storing it as another reference's argument. Arguments are interpreted in the +caller's environment first; then declaration variance is applied and the alias's +formals are bound for its body. Existing occurrence-polarity selection still +chooses each substituted argument's input or output part. Thus `Identity[A]` +reduces to the same interpreted reference as `A`, instead of retaining another +`(Identity[A], previous-environment)` layer on every recursive visit. + +Reduction follows alias applications until a non-alias type constructor, an +unresolved target, a partial application, or an alias already being expanded is +reached. The source-alias guard grows on body expansion, while processing argument +syntax descends the finite source expression. Nested applications such as +`Identity[Identity[A]]` reduce their operands before adding the outer alias to the +guard. The reducer allocates no binder instances and does not unfold the children +of nominal, record, or function types. Captures retain the ordinary mark operations. +Forward references retain their bindings until dependency analysis can complete. + +For the forwarding cases, reducing the arguments and pruning unused bindings +makes repeated projections return the same reference keys. `TypeRelationTest` +checks a thousand reductions for equality, unchanged caller marks, stable listener +counts, and zero binder allocation. `TypeGraphTermination.mls` covers nested +forwarding aliases, parameter selection/permutation, forward definitions, a captured +alias, and an unproductive cycle with growing arguments. This does not prove the +whole-graph bound: non-regular structural expansion and other growing binding +environments still need the regularity check. That check must not classify +transparent alias forwarding as a growing type constructor. The implemented dependency analysis operates on source `TypeResolution` nodes. An edge forwards the child's free binders, excluding those bound by an alias or diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 259b7ea0f4..17a17d9811 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -129,6 +129,33 @@ class TypeRelationTest extends AnyFunSuite: val seen = h.observe(ContextualType(inside, Nil)) assert(seen.toList == List(MarkedShape.enter(bound, owner, S(invoked)))) + test("alias argument normalization reaches a fixed point without changing its marks"): + val h = new Harness + import h.given + val (formal, ft) = h.parameter("X") + val symbol = TypeAliasSymbol(Tree.Ident("Identity")) + val member = BlockMemberSymbol("Identity", Nil) + symbol.defn = S(TypeDef(symbol, member, List(TyParam(FldFlags.empty, N, formal)), + S(ft.resolution.source), N, Nil)) + val alias = h.tpe(TypeShape.Alias(symbol, S(ft.resolution))) + val (argument, at) = h.parameter("A") + val application = h.tpe(TypeShape.Applied(alias.resolution, List(at.resolution))) + val (target, tt) = h.parameter("Target") + val owner = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("owner"))) + val path = EntryMark(owner, S(FlowSymbol.app()), NoMarks) + val original = h.resolver.transportType(tt, path :: Nil) + val seen = h.observe(ContextualType(original, Nil)) + val before = target.inferenceHost.listeners.size + var current = original + (1 to 1000).foreach: _ => + current = h.resolver.declaredType(application.resolution, Map(argument -> current)) + assert(current == original) + val value = DynShape() + h.resolver.publishParameter(target, value) + assert(seen.toList == List(MarkedShape.enter(value, owner, path.id))) + assert(target.inferenceHost.listeners.size == before) + assert(h.state.allocatedTypeInstanceCount == 0) + test("recursive structural projections preserve bounds and reuse their listeners"): val h = new Harness import h.given diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 87b94dc0f9..25d8b809a7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -294,14 +294,34 @@ class NewResolver: declaredType(resolution, bindings, true) private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], positive: Bool) - (using NewResolverState): DeclaredType = resolution.currentShapes.toList match - case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) - case TypeShape.Captured(base, thru) :: Nil => captureType(declaredType(base, bindings, positive), thru) - case _ => - val relevant = if bindings.isEmpty then bindings else typeDependencies(resolution) match - case R(binders) => bindings.filter((symbol, _) => binders(symbol)) - case L(_) => bindings - DeclaredType(resolution, relevant, Map.empty, positive) + (using NewResolverState): DeclaredType = + def loop(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], aliases: Set[TypeAliasSymbol]): DeclaredType = + def retain: DeclaredType = + val relevant = if bindings.isEmpty then bindings else typeDependencies(resolution) match + case R(binders) => bindings.filter((symbol, _) => binders(symbol)) + case L(_) => bindings + DeclaredType(resolution, relevant, Map.empty, positive) + resolution.currentShapes.toList match + case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) + case TypeShape.Captured(base, thru) :: Nil => captureType(loop(base, bindings, aliases), thru) + case TypeShape.Applied(base, arguments) :: Nil => base.currentShapes.toList match + case TypeShape.Alias(symbol, S(rhs)) :: Nil if !aliases(symbol) => + val parameters = symbol.defn.get.tparams + if parameters.length != arguments.length then retain + else + // Reduce alias applications before storing them in another type's + // environment. Otherwise Chain[Identity[A]] retains a new wrapper + // around A on every recursive projection. Arguments are reduced in + // their original environment before the alias's formals are bound. + val supplied = arguments.map(loop(_, bindings, aliases)) + val substitutions = parameters.zip(supplied).map: (parameter, argument) => + parameter.sym -> argumentType(argument, parameter.vce) + // Unproductive source-alias cycles stop at their original node. + // No expansion through a nominal, record, or function is needed. + loop(rhs, bindings ++ substitutions, aliases + symbol) + case _ => retain + case _ => retain + loop(resolution, bindings, Set.empty) private def declaredType(resolution: TypeResolution, context: DeclaredType)(using NewResolverState): DeclaredType = declaredType(resolution, context.bindings, context.positive).instantiate(context.instances) diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index 8d5e60d3c4..d12cf7b341 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -76,11 +76,9 @@ type Right[B] = {value: B, next: Left[Str]} fun read(chain: Left[Int]): Str = chain.next.next.next.value.0 // Transparent aliases do not change the recursive argument's type. -:fixme type Identity[X] = X type Chain[A] = {value: A, next: Chain[Identity[A]]} fun read(chain: Chain[Int]): Int = chain.next.next.value -//│ /!!!\ Uncaught error: java.lang.StackOverflowError // Forward aliases must retain an enclosing binder even before the target is known. :expect [5, 6] @@ -137,3 +135,36 @@ module Items with private fun read(module items: Items) = items.item read(Items).value //│ = 13 + +// Nested forwarding aliases normalize before the recursive environment is stored. +type Identity[X] = X +type Forward[Y] = Identity[Identity[Y]] +type Chain[A] = {value: A, next: Chain[Forward[A]]} +fun read(chain: Chain[Int]): Int = chain.next.next.next.value + +// A forwarding alias can select and permute arguments without nesting its environment. +type FirstOf[X, Y] = X +type SecondOf[X, Y] = Y +class First(val first: Int) +class Second(val second: Int) +type Alternating[A, B] = {value: A, next: Alternating[SecondOf[A, B], FirstOf[A, B]]} +fun read(chain: Alternating[First, Second]): Int = chain.next.value.second + +// Unproductive alias cycles must stop without eagerly unfolding growing arguments. +type Loop[A] = Loop[Array[A]] +private fun unused(value: Loop[Int]) = () + +// An unresolved alias body must keep its argument binding until the target is known. +type Forward[A] = Identity[A] +type Chain[A] = {value: A, next: Chain[Forward[A]]} +fun read(chain: Chain[Int]): Int = chain.next.next.value +type Identity[X] = X + +// The same forwarding normalization is needed for aliases captured by a function. +type Identity[X] = X +private fun local[A](value: A) = + type Chain[B] = {value: B, next: Chain[Identity[B]]} + fun read(chain: Chain[A]): A = chain.next.next.value + value +local[Int](1) +//│ = 1 From c81e33bf7e484913bda7345f691156aa706d46d5 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 13:18:22 +0800 Subject: [PATCH 116/228] Normalize recursive alias and Boolean type arguments --- doc/new-resolution-regular-types.md | 252 ++++++++++++++++++ doc/new-resolution-suite-migration.md | 5 +- doc/new-resolution-type-value-flow.md | 28 +- .../src/test/scala/hkmc2/CompilerTest.scala | 4 +- .../test/scala/hkmc2/TypeFormulaTest.scala | 40 +++ .../test/scala/hkmc2/TypeRelationTest.scala | 65 ++++- .../scala/hkmc2/semantics/NewResolver.scala | 84 ++++-- .../hkmc2/semantics/NewResolverState.scala | 2 + .../scala/hkmc2/semantics/TypeFormula.scala | 29 ++ .../scala/hkmc2/semantics/TypeShape.scala | 5 + .../mlscript/newres/TypeGraphTermination.mls | 101 +++++++ 11 files changed, 582 insertions(+), 33 deletions(-) create mode 100644 doc/new-resolution-regular-types.md create mode 100644 hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/TypeFormula.scala diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md new file mode 100644 index 0000000000..bfa4628446 --- /dev/null +++ b/doc/new-resolution-regular-types.md @@ -0,0 +1,252 @@ +# Regular structural types and canonical references + +This is an internal reference for structural recursion in new resolution. The +instance-wrapper and variance semantics are specified in +[instance types and parameter constraints](new-resolution-type-value-flow.md). + +## Requirement and current status + +A structural type must unfold into a finite graph of distinct type components. +Check regularity after reducing aliases and normalizing unions/intersections; +textually changing an argument is not itself evidence of unbounded growth. + +```mlscript +type Chain[A] = {value: A, next: Chain[A]} +type Alternating[A, B] = {value: A, next: Alternating[B, A]} +type Reset[A] = {value: A, next: Reset[Array[Int]]} +type Saturating[A] = {value: A, next: Saturating[A | Int]} +type Growing[A] = {value: A, next: Growing[Array[A]]} +``` + +The first four have finite unfoldings. `Growing[Int]` exposes `Int`, `Array[Int]`, +`Array[Array[Int]]`, and so on. It must eventually receive a regularity diagnostic, +without widening the type or imposing an expansion-depth limit. + +Canonical references now reduce fully supplied aliases and normalize Boolean +combinations. The regularity diagnostic is **not implemented**. A check using one +dependency node per formal parameter is insufficient: it conflates the parameter's +input and output parts and loses correlations between arguments. The refinement +below requires review before implementation. +The existing stack-overflow regression remains a known failure, not an acceptance +case for non-regular recursion. + +## Implemented normalization + +`TypeFormula` represents a union of intersections as a set of sets of interpreted +references. It applies associativity, commutativity, idempotence, distribution, +and absorption. It does not inspect nominal subtyping, class disjointness, or the +current candidates of an inference host. For example: + +```text +(A | Int) | Int = A | Int +(A | Int) & Str = (A & Str) | (Int & Str) +A | (A & Array[A]) = A +((A | Int) & Str | Int) & Str = (A | Int) & Str +``` + +`TypeShape.Combined` retains the formula as one type. In particular, supplying a +union still creates one instance wrapper, and a negative obligation against that +union stays whole. Member-interface observation can inspect its components, just +as it did for source unions and intersections. Normalization does not turn one +supplied union into several independently supplied types. + +Formula atoms retain their lexical bindings, canonical binder instances, and +ordinary marks. Different written references to the same unbound parameter and +host share an atom within a formula. Ordinary annotation references retain their +own source locations for diagnostics. A live parameter remains a reference; +normalization does not freeze its current bounds. Combined nodes are interned in +the consuming resolution state, using the same inherited-cache discipline as other +type references. + +Fully supplied aliases reduce before they become another type's argument. Arguments +are interpreted in their caller environment first, then declaration variance is +applied and the alias's formals are substituted. A guard on source alias symbols +stops unproductive recursive expansion. Nested arguments are reduced before adding +the outer alias to that guard, so `Identity[Identity[A]]` reduces to `A`. +Reduction stops at nominal, tuple, record, and function constructors. + +A captured alias uses the same application rule as deferred interpretation: + +1. Rebase supplied arguments to the template endpoint using the existing mark + operations. +2. Substitute the formals at that endpoint. +3. Transport the resulting reference back using the existing mark operations. + +This applies to `Chain[B | Identity[B]]` when `Identity` is captured by an enclosing +function. Retaining the captured application as a fresh argument layer on each +projection would prevent a fixed point. Conversely, cancelling its scope crossings +would change the meaning: a wildcard exit followed by wildcard entry can lose the +caller's ID and is **not** an identity. Eager reduction performs those operations; +it does not add any special mark simplification. + +Dependency discovery also follows the source graphs retained by synthetic formulas +and contextual references, without treating their saved bindings as ambient free +variables. Unresolved forward references therefore still delay interpretation. + +For a fixed finite atom set, the antichains of subsets used by `TypeFormula` are +finite. This bounds repeated union/intersection substitution, including alternating +operators. Formula normalization can be exponential in the number of atoms; this +is a representation bound, not a claim of linear complexity. No fresh inference +variables or binder instances are allocated by normalization. + +This does **not** by itself bound the atoms. Constructor growth, retained argument +parts that never become observable, and the existing requirements on finite marked +contexts remain separate obligations. + +## Why a binder-only growth graph is insufficient + +A candidate check connected original formals through recursive applications. An +edge recorded whether the argument wrapped its source formal in a non-Boolean +constructor. A growing edge on a dependency cycle caused rejection. Alias and +Boolean normalization preceded edge construction; unused formals were removed by +existing source dependency analysis. + +That handles ordinary permutations and resets, including: + +```mlscript +type Left[A] = {value: A, next: Right[Array[A]]} +type Right[B] = {value: B, next: Left[Str]} +``` + +The wrapping edge is followed by a reset, so no growing dependency cycle remains. +It also distinguishes an absorbed `A | (A & Array[A])` from the growing +`A | Array[A]`. + +However, this finite case would be incorrectly rejected: + +```mlscript +type Chain[A] = {value: A, next: Chain[in Array[A]]} +private fun read[A](chain: Chain[A]) = chain.next.value +``` + +At the positive occurrence `value: A`, substituting `in Array[A]` selects its +missing output part, `Any`. All later `value` components have that output type. +The constructor inside the input part is not an observable growing component of +this structural unfolding. A binder-only edge from `A` to `A` loses that fact. +The worksheet contains this finite projection as an acceptance test against an +incorrect regularity diagnostic. + +The representation needs the same distinction. Current free-binder projection +retains the whole bound argument whenever a formal is relevant, including parts +not selected by the structural body. Recursive observation can therefore accumulate +input-part environments even when the resulting structural type is regular. +Merely permitting this case in a rejection check would not establish termination. +The candidate rejection rule has not been enabled. + +## Proposed refinement for review + +Track dependencies on argument parts, rather than only on binder symbols. The +analysis must distinguish lexical occurrence polarity and argument-part selection +from the direction of an instance-value constraint. A selected argument denotes +one fixed type for both subsequent input and output constraints. + +Let `I_p(T)` mean interpreting a type expression in its saved lexical polarity +`p`. A reference to alias formal `A` demands the `p` part of its bound argument. +For the parts produced by the current argument interpretation rules: + +| Argument | Input part | Output part | +| --- | --- | --- | +| Invariant `S` | `I_p(S)` | `I_p(S)` | +| Written `in T` | `I_not-p(T)` | `Any` | +| Written `out U` | `Nothing` | `I_p(U)` | +| Plain `S` with declaration `in` | `I_p(S)` | `Any` | +| Plain `S` with declaration `out` | `Nothing` | `I_p(S)` | + +Written wildcards override declaration variance, as in the existing interpreter. +The difference between the written `in T` row and the declaration-variance row is +intentional: a written input bound is an opposite-polarity syntax occurrence; +declaration variance selects which parts of an already interpreted argument are +available. These are the rules to preserve, not a proposal to change substitution. + +For an invariant actual argument `B`, selecting either part of the callee's formal +still selects the same saved `I_p(B)`. It must not reinterpret `B` with the callee's +current polarity. Likewise, after selecting a written argument part, both subsequent +constraint directions use that fixed type. Function inputs reverse lexical polarity; +record fields, tuple fields, and function results preserve it. Scope transport does +not change which argument part is selected and continues to use ordinary marks. + +The analysis can attach finite demand sets to source states consisting of a source +node, lexical polarity, and whether the operation requests the whole interpreted +type or a particular argument part. The demands name original binders and their +input/output parts. Alias application composes these demands with the argument table, +hiding the callee's formals. Forward references delay finalization. Equations range +over a finite set of source states and part demands, so monotone iteration terminates +without unfolding substituted recursive types or allocating inference variables. + +For `Chain[in Array[A]]` at positive polarity, the recursive output demand reaches +`Any`, ending that dependency. At negative polarity, selecting the written input +reverses the argument's saved polarity: its nested `A` demands the previous output +part, which is `Any`. Thus constructor depth does not grow indefinitely in either +polarity. A single binder node cannot express that dependency reset. + +The same demand summaries must govern the saved environments. A projected argument +view should retain references only for demanded parts, together with an explicit +mask recording which parts are available. Selecting an unretained part must assert +an invalid dependency summary; it must not silently invent `Any`, `Nothing`, or an +inference hole. Extreme types in the table come from actual variance semantics, +not from environment pruning. Keep both parts when either may be selected. Preserve +source-owned holes and live inference references without inspecting their current +candidates. Nominal member dependencies remain conservative until their required +parts are known. + +### A further limit of a simple growth graph + +Even part-sensitive dependency edges lose relationships between arguments. This +regular type requires absorption across successive recursive substitutions: + +```mlscript +type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} +``` + +Writing `X = A | (B & Array[A])`, its argument states are: + +```text +(A, B) -> (X, A) -> (X, X) -> (X, X) +``` + +The second step computes `X | (A & Array[X])`, which is `X` because `X` already +contains `A`. The next step absorbs `X & Array[X]`. A per-parameter growth graph +still contains a constructor cycle and would falsely reject the definition. +`TypeGraphTermination.mls` includes a recursive observation of this example. + +Consequently, refining binder dependencies into part dependencies is necessary for +environment projection, but it is **not sufficient** to make constructor cycles +an exact regularity test after Boolean normalization. Such a graph can provide a +sufficient acceptance criterion; a positive cycle cannot by itself justify a +non-regularity diagnostic. The rejection procedure must also account for normalized +substitution relationships, and must terminate on genuinely growing cases. +This procedure is an unresolved design obligation. Repeatedly unfolding until a +cache stops growing, imposing a depth limit, or assuming a constructor cycle proves +growth would not resolve it. + +Proposed implementation boundary for review: + +1. Specify and test the finite, part-sensitive demand equations above, including + both polarities and alias substitution. This analysis itself has a finite + source-graph bound. +2. Use those summaries for checked environment projection, retaining marks and + live references. Verify the variance example and ordinary holes without claiming + that this supplies a complete regularity decision procedure. +3. Separately design a terminating rejection check that preserves correlations + needed by examples such as `Stable`. Only then enable regularity diagnostics, + including for unused written annotations. + +The whole resolver still needs a bound on accepted reference keys: finite source +and binder identities alone do not bound nested environments. Boolean normalization +bounds formulas only once their atom set is bounded. Existing finite-context +requirements on the marks algebra remain in force throughout. + +## Regression coverage + +`newres/TypeGraphTermination.mls` covers regular recursion, permutations, mutual +resets, unused arguments, transparent and captured forwarding aliases, forward +references, union/intersection saturation, Boolean-only recursion, and the variance +counterexample. Expected future regularity diagnostics are marked `:breakme`/`:e`; +they are completion obligations, not implemented rejection behavior. + +`TypeFormulaTest` checks normalization against Boolean truth tables and repeated +alternating substitution. `TypeRelationTest` checks a thousand repeated reductions, +unchanged binder allocation and source-listener counts, late bounds, whole negative +union targets, and the loss of caller identity under captured alias rebasing. These +checks support normalization's local invariants; they do not establish the whole +resolver's termination argument. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index f0ac95ebc3..479ed4d819 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -90,7 +90,10 @@ The inventory below separates these from compiler and prelude gaps. [type-argument constraint design](new-resolution-type-value-flow.md), including supplied member-input constraints, reconstructed receiver contexts, inferred missing member types, - regular structural interfaces, and the whole graph's termination bound. + [regular structural interfaces](new-resolution-regular-types.md), and the whole + graph's termination bound. Alias and Boolean argument normalization is implemented; + regularity checking still needs dependencies on input and output argument parts + and relationships between recursive arguments. Concrete outstanding cases are in `newres/InstantiationSites.mls`, `StoredSpecializations.mls`, `ConstructorInstances.mls`, `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 0d9b9b3887..06166eb6b1 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -41,15 +41,16 @@ value-scope crossings](#type-reference-scope-audit) is approved. Recursive refer graphs must preserve caller and receiver contexts without growing binding environments. The variance substitution rules below are implemented for declared type views. -Require structural types to have a finite recursive representation: the proposed -[regularity restriction](#regular-structural-types) rules out structural unfolding -that keeps producing distinct types. The exact check remains to be reviewed before -implementation. This restriction and sharing regular recursive references are -separate obligations; neither follows from the bound on call-site symbols. +Require structural types to have a finite recursive representation, after alias +and union/intersection normalization. The [regularity design](new-resolution-regular-types.md) +describes the implemented normalization and the input/output dependency refinement +still requiring review. Diagnosing non-regular types and sharing regular recursive +references are separate obligations; neither follows from the bound on call-site symbols. The finite call-site symbol bound alone is not a termination proof for the whole resolver. Non-regular expansion still overflows. Fully supplied alias applications -are reduced before their arguments become part of recursive reference keys. Contextual references preserve class-local aliases +are reduced and Boolean combinations normalized before their arguments become part +of recursive reference keys. Contextual references preserve class-local aliases and enclosing binders through nominal member projections. Dependency-based binding projection accepts regular argument resets, including compound constants and recursively unused arguments. The [fixed-point conditions](#fixed-points-and-implementation-checks) remain completion gates. @@ -1051,10 +1052,12 @@ overflow; it is no longer an acceptance case for unrestricted structural recursi Regularity is not a requirement that recursive arguments be textually unchanged. For example, `{value: A, next: Alternating[B, A]}` as the body of `Alternating[A, B]` has a finite two-state unfolding. Mutually recursive aliases -and finite changes of arguments must also be considered. The precise check, -including how it handles aliases and inferred holes, remains a design review -item. It must terminate on rejected inputs too; waiting for an unfolding cache to -stop growing is not a decision procedure. Do not impose a depth limit. +and finite changes of arguments must also be considered. Alias and Boolean +normalization is implemented. The remaining check must distinguish dependencies +on input and output argument parts, including when pruning saved environments; +its [refined design](new-resolution-regular-types.md#proposed-refinement-for-review) +remains under review. It must terminate on rejected inputs too; waiting for an +unfolding cache to stop growing is not a decision procedure. Do not impose a depth limit. Growth along one recursive edge is not sufficient to reject a type: @@ -1130,7 +1133,10 @@ forwarding aliases, parameter selection/permutation, forward definitions, a capt alias, and an unproductive cycle with growing arguments. This does not prove the whole-graph bound: non-regular structural expansion and other growing binding environments still need the regularity check. That check must not classify -transparent alias forwarding as a growing type constructor. +transparent alias forwarding as a growing type constructor. See the +[normalization implementation and variance counterexample](new-resolution-regular-types.md) +for the Boolean normal form, captured alias reduction, and why a binder-only +growth graph is insufficient. The implemented dependency analysis operates on source `TypeResolution` nodes. An edge forwards the child's free binders, excluding those bound by an alias or diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index ec41fe7ee3..12dc9e4eca 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -414,7 +414,9 @@ class CompilerTest extends AnyFunSuite: case _ => Nil assert(supplied.length == 1, "A union argument must not become two supplied type arguments") assert(supplied.head.shapes.toList match - case (_: TypeShape.Union) :: Nil => true + case TypeShape.Combined(formula) :: Nil => + formula.clauses.size == 2 && formula.clauses.forall(_.size == 1) && + formula.atoms.flatMap(_.resolution.shapes.collect { case TypeShape.Nominal(cls) => cls.sym.nme }) == Set("Int", "Str") case _ => false) test("mutable array parameter flow stays private to each importer"): diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala new file mode 100644 index 0000000000..cba224a675 --- /dev/null +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala @@ -0,0 +1,40 @@ +package hkmc2 +package semantics + +import org.scalatest.funsuite.AnyFunSuite + + +class TypeFormulaTest extends AnyFunSuite: + test("lattice normalization preserves Boolean meanings and identifies equal formulas"): + val a = TypeFormula.atom(0) + val b = TypeFormula.atom(1) + val c = TypeFormula.atom(2) + val atoms = List(a, b, c, TypeFormula.top[Int], TypeFormula.bottom[Int]) + val formulas = (atoms ++ (for left <- atoms; right <- atoms; union <- List(false, true) + yield if union then left.union(right) else left.intersection(right))).distinct + def truthTable(formula: TypeFormula[Int]): List[Boolean] = (0 until 8).toList.map: assignment => + formula.clauses.exists(_.forall(atom => (assignment & (1 << atom)) != 0)) + val results = for left <- formulas; right <- formulas yield + val union = left.union(right) + val intersection = left.intersection(right) + assert(truthTable(union) == truthTable(left).zip(truthTable(right)).map(_ || _)) + assert(truthTable(intersection) == truthTable(left).zip(truthTable(right)).map(_ && _)) + List(union, intersection) + results.flatten.groupBy(truthTable).values.foreach(equal => assert(equal.distinct.size == 1)) + assert(a.union(a.intersection(b)) == a) + assert(a.intersection(b.union(c)) == a.intersection(b).union(a.intersection(c))) + assert(a.union(b) == b.union(a)) + assert(a.union(b).hashCode == b.union(a).hashCode) + assert(a.union(b).orderedAtoms == Vector(0, 1)) + assert(b.union(a).orderedAtoms == Vector(1, 0)) + assert(a.union(a.intersection(b)).orderedAtoms == Vector(0)) + + test("alternating recursive substitutions saturate without accumulating syntax"): + val a = TypeFormula.atom("A") + val b = TypeFormula.atom("B") + val c = TypeFormula.atom("C") + var value = a + val expected = a.union(b).intersection(c) + (1 to 1000).foreach: _ => + value = value.union(b).intersection(c) + assert(value == expected) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 17a17d9811..b2d307d1d6 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -156,6 +156,65 @@ class TypeRelationTest extends AnyFunSuite: assert(target.inferenceHost.listeners.size == before) assert(h.state.allocatedTypeInstanceCount == 0) + test("captured alias reduction uses ordinary exit and entry including caller identity loss"): + val h = new Harness + import h.given + val (formal, ft) = h.parameter("X") + val symbol = TypeAliasSymbol(Tree.Ident("Identity")) + val member = BlockMemberSymbol("Identity", Nil) + symbol.defn = S(TypeDef(symbol, member, List(TyParam(FldFlags.empty, N, formal)), + S(ft.resolution.source), N, Nil)) + val alias = h.tpe(TypeShape.Alias(symbol, S(ft.resolution))) + val scope = TermSymbol(Fun, N, Tree.Ident("owner")) + val owner = ResolutionBoundary(scope) + val captured = h.tpe(TypeShape.Captured(alias.resolution, scope)) + val (argument, at) = h.parameter("A") + val application = h.tpe(TypeShape.Applied(captured.resolution, List(at.resolution))) + val (target, tt) = h.parameter("Target") + val original = h.resolver.transportType(tt, EntryMark(owner, S(FlowSymbol.app()), NoMarks) :: Nil) + val outside = h.resolver.transportType(original, ExitMark(owner, N, NoMarks) :: Nil) + val expected = h.resolver.transportType(outside, EntryMark(owner, N, NoMarks) :: Nil) + assert(expected != original) + val actual = h.resolver.declaredType(application.resolution, Map(argument -> original)) + assert(actual == expected) + val early = IntroShape(Term.UnitVal(), N) + val late = DynShape() + h.resolver.publishParameter(target, early) + val seen = h.observe(ContextualType(actual, Nil)) + h.resolver.publishParameter(target, late) + assert(seen.toList == List(early, late).map(MarkedShape.enter(_, owner, N))) + val count = target.inferenceHost.listeners.size + (1 to 1000).foreach: _ => + assert(h.resolver.declaredType(application.resolution, Map(argument -> actual)) == expected) + assert(target.inferenceHost.listeners.size == count) + assert(h.state.allocatedTypeInstanceCount == 0) + + test("recursive Boolean arguments share a fixed point and preserve each atom's marks and late bounds"): + val h = new Harness + import h.given + val (a, at) = h.parameter("A") + val (b, bt) = h.parameter("B") + val (target, tt) = h.parameter("Target") + val owner = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("owner"))) + val path = EntryMark(owner, S(FlowSymbol.app()), NoMarks) + val original = h.resolver.transportType(tt, path :: Nil) + val source = h.tpe(TypeShape.Union(at.resolution, bt.resolution)) + val expected = h.resolver.declaredType(source.resolution, Map(a -> original)) + val early = IntroShape(Term.UnitVal(), N) + val late = DynShape() + h.resolver.publishParameter(target, early) + val seen = h.observe(ContextualType(expected, Nil)) + val counts = (target.inferenceHost.listeners.size, b.inferenceHost.listeners.size) + var current = expected + (1 to 1000).foreach: _ => + current = h.resolver.declaredType(source.resolution, Map(a -> current)) + assert(current eq expected) + h.resolver.listenInstanceViews(InstanceShape(current))(_ => ()) + h.resolver.publishParameter(b, late) + assert(seen.toSet == Set(MarkedShape.enter(early, owner, path.id), late)) + assert((target.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) + assert(h.state.allocatedTypeInstanceCount == 0) + test("recursive structural projections preserve bounds and reuse their listeners"): val h = new Harness import h.given @@ -370,7 +429,8 @@ class TypeRelationTest extends AnyFunSuite: val (a, at) = h.parameter("A") val one = h.tpe(TypeShape.Unit) val two = h.tpe(TypeShape.Abstract) - val union = h.tpe(TypeShape.Union(one.resolution, two.resolution)) + val writtenUnion = h.tpe(TypeShape.Union(one.resolution, two.resolution)) + val union = h.resolver.declaredType(writtenUnion.resolution, Map.empty) val first = IntroShape(Term.UnitVal(), N) val second = DynShape() h.resolver.publishParameter(a, first) @@ -410,7 +470,8 @@ class TypeRelationTest extends AnyFunSuite: val (a, at) = h.parameter("A") val one = h.tpe(TypeShape.Unit) val two = h.tpe(TypeShape.Abstract) - val union = h.tpe(TypeShape.Union(one.resolution, two.resolution)) + val writtenUnion = h.tpe(TypeShape.Union(one.resolution, two.resolution)) + val union = h.resolver.declaredType(writtenUnion.resolution, Map.empty) val bound = IntroShape(Term.UnitVal(), N) h.state.markExplicitTypeArgument(a) h.resolver.publishParameter(a, InstanceShape(union)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 25d8b809a7..fe0d07261b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -196,6 +196,9 @@ class NewResolver: edges ::= child -> hidden visit(child) def child(res: TypeResolution): Unit = edge(res, Set.empty) + def reference(tpe: DeclaredType): Unit = + visit(tpe.resolution) + tpe.bindings.values.foreach(reference) shapes.foreach: case TypeShape.Parameter(symbol, _) => direct += symbol case TypeShape.Nominal(defn) => @@ -240,8 +243,11 @@ class NewResolver: case TypeShape.Wildcard(input, output) => input.foreach(child); output.foreach(child) // These synthesized nodes retain their own interpreted references // or inference hosts; none reads the surrounding binding map. - case TypeShape.Argument(_) | TypeShape.SelectedArgument(_, _) | TypeShape.Contextual(_) | TypeShape.Inferred(_) | - TypeShape.Hole(_) | TypeShape.Unit | TypeShape.Dynamic | TypeShape.Abstract | + case TypeShape.Combined(formula) => formula.orderedAtoms.foreach(reference) + case TypeShape.Argument(parts) => reference(parts.input); reference(parts.output) + case TypeShape.SelectedArgument(argument, _) => reference(argument) + case TypeShape.Contextual(context) => reference(context.tpe) + case TypeShape.Inferred(_) | TypeShape.Hole(_) | TypeShape.Unit | TypeShape.Dynamic | TypeShape.Abstract | TypeShape.Top | TypeShape.Bottom => () graph(res) = (direct, edges) visit(resolution) @@ -304,25 +310,63 @@ class NewResolver: resolution.currentShapes.toList match case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) case TypeShape.Captured(base, thru) :: Nil => captureType(loop(base, bindings, aliases), thru) - case TypeShape.Applied(base, arguments) :: Nil => base.currentShapes.toList match - case TypeShape.Alias(symbol, S(rhs)) :: Nil if !aliases(symbol) => - val parameters = symbol.defn.get.tparams - if parameters.length != arguments.length then retain - else - // Reduce alias applications before storing them in another type's - // environment. Otherwise Chain[Identity[A]] retains a new wrapper - // around A on every recursive projection. Arguments are reduced in - // their original environment before the alias's formals are bound. - val supplied = arguments.map(loop(_, bindings, aliases)) - val substitutions = parameters.zip(supplied).map: (parameter, argument) => - parameter.sym -> argumentType(argument, parameter.vce) - // Unproductive source-alias cycles stop at their original node. - // No expansion through a nominal, record, or function is needed. - loop(rhs, bindings ++ substitutions, aliases + symbol) - case _ => retain + case TypeShape.Union(left, right) :: Nil => + combinedType(resolution, typeFormula(loop(left, bindings, aliases)).union(typeFormula(loop(right, bindings, aliases)))) + case TypeShape.Intersection(left, right) :: Nil => + combinedType(resolution, typeFormula(loop(left, bindings, aliases)).intersection(typeFormula(loop(right, bindings, aliases)))) + case TypeShape.Applied(base, arguments) :: Nil => + def applyAlias(base: DeclaredType, supplied: Ls[DeclaredType], seen: Set[TypeResolution]): Opt[DeclaredType] = + if seen(base.resolution) then N else base.resolution.currentShapes.toList match + case TypeShape.Alias(symbol, S(rhs)) :: Nil if !aliases(symbol) => + val parameters = symbol.defn.get.tparams + if parameters.length != supplied.length then N else + val substitutions = parameters.zip(supplied).map: (parameter, argument) => + parameter.sym -> argumentType(argument, parameter.vce) + S(loop(rhs, effectiveBindings(base) ++ substitutions, aliases + symbol)) + case TypeShape.Contextual(reference) :: Nil => + // Use exactly the interpreter's application rule: move caller + // arguments to the template endpoint, substitute there, then + // transport the result back. A wildcard exit followed by entry + // is not cancelled; transportType retains its ordinary marks. + applyAlias(reference.tpe.instantiate(base.instances), + supplied.map(transportType(_, inverseMarks(reference.marks))), seen + base.resolution) + .map(transportType(_, reference.marks)) + case _ => N + // Reduce arguments in their caller environment before binding formals. + // The alias guard grows only on body expansion, so nested Identity + // applications reduce while unproductive recursive aliases stop. + applyAlias(loop(base, bindings, aliases), arguments.map(loop(_, bindings, aliases)), Set.empty).getOrElse(retain) case _ => retain loop(resolution, bindings, Set.empty) + /** Normalize Boolean combinations without observing an atom's candidates. + * Transport distributes over components using the ordinary transportType + * operation; no exit/entry cancellation is introduced by normalization. + */ + private def typeFormula(tpe: DeclaredType)(using NewResolverState): TypeFormula[DeclaredType] = + tpe.resolution.currentShapes.toList match + case (parameter: TypeShape.Parameter) :: Nil if !tpe.bindings.contains(parameter.symbol) => + // Different written occurrences of A are the same lattice atom. Limit + // this canonicalization to combinations, so ordinary annotation and + // storage references retain their precise diagnostic source location. + TypeFormula.atom(parameterType(parameter).copy(positive = tpe.positive).instantiate(tpe.instances)) + case TypeShape.Combined(formula) :: Nil => formula.map(_.instantiate(tpe.instances)) + case TypeShape.Contextual(reference) :: Nil => + typeFormula(reference.tpe.instantiate(tpe.instances)).map(transportType(_, reference.marks)) + case TypeShape.Top :: Nil => TypeFormula.top + case TypeShape.Bottom :: Nil => TypeFormula.bottom + case _ => TypeFormula.atom(tpe) + + private def combinedType(source: TypeResolution, formula: TypeFormula[DeclaredType])(using NewResolverState): DeclaredType = + if formula.clauses.isEmpty then extremeType(false) + else if formula.clauses.contains(Set.empty) then extremeType(true) + else if formula.clauses.size == 1 && formula.atoms.size == 1 then formula.atoms.head + else rstate.combinedTypes.getOrElseUpdate(formula, { + val resolution = new TypeResolution(source.source, source.fail) + resolution.publish(TypeShape.Combined(formula)) + DeclaredType(resolution, Map.empty, Map.empty, true) + }) + private def declaredType(resolution: TypeResolution, context: DeclaredType)(using NewResolverState): DeclaredType = declaredType(resolution, context.bindings, context.positive).instantiate(context.instances) @@ -509,6 +553,8 @@ class NewResolver: case TypeShape.Intersection(left, right) => follow(declaredType(left, current), noTypeArguments, captures, next) follow(declaredType(right, current), noTypeArguments, captures, next) + case TypeShape.Combined(formula) => formula.orderedAtoms.foreach: atom => + follow(atom.instantiate(current.instances), noTypeArguments, captures, next) case _ => () withCanonicalType(tpe)(follow(_, noTypeArguments, Nil, Set.empty)) @@ -705,6 +751,8 @@ class NewResolver: case TypeShape.Record(source, fields) => publish(RecordTypeShape(source, fields, effectiveBindings(current), current.positive)) case TypeShape.Union(left, right) => next(left); next(right) case TypeShape.Intersection(left, right) => next(left); next(right) + case TypeShape.Combined(formula) => formula.orderedAtoms.foreach: atom => + follow(atom.instantiate(current.instances), noTypeArguments, aliases, publish) case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) withCanonicalType(tpe)(follow(_, noTypeArguments, Set.empty, host.publish)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 89635e33eb..dabf2eaf01 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -212,6 +212,8 @@ final class NewResolverState private ( new Cache(inherited.map(_.lexicalTypeBinders), identity) val typeDependencies: Cache[TypeResolution, Set[VarSymbol]] = new Cache(inherited.map(_.typeDependencies), identity) + val combinedTypes: Cache[TypeFormula[DeclaredType], DeclaredType] = + new Cache(inherited.map(_.combinedTypes), identity) val pendingTypeDependencies: Cache[TypeResolution, TypeDependencyHost] = new Cache(inherited.map(_.pendingTypeDependencies), identity) val dependencySubscriptions: Seen[(TypeResolution, TypeResolution)] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeFormula.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeFormula.scala new file mode 100644 index 0000000000..6827c806ce --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeFormula.scala @@ -0,0 +1,29 @@ +package hkmc2 +package semantics + +/** A union of intersections, with absorption. This is the free distributive + * lattice over A: no nominal subtyping, disjointness, or inference bounds are + * consulted. In particular, an atom can be a still-live inference reference. + * For a finite set of atoms there are finitely many formulas, even when recursive + * substitutions alternate union and intersection arbitrarily often. + */ +final case class TypeFormula[A] private (clauses: Set[Set[A]])(val orderedAtoms: Vector[A]): + // The second parameter list is not part of case-class equality. Preserve the + // first source order for observation and diagnostics without distinguishing + // equal formulas merely because their operands were written in another order. + def union(that: TypeFormula[A]): TypeFormula[A] = TypeFormula.normalized(clauses ++ that.clauses, orderedAtoms ++ that.orderedAtoms) + def intersection(that: TypeFormula[A]): TypeFormula[A] = + TypeFormula.normalized(for left <- clauses; right <- that.clauses yield left ++ right, orderedAtoms ++ that.orderedAtoms) + def map[B](f: A => B): TypeFormula[B] = + val mapped = orderedAtoms.map(atom => atom -> f(atom)).toMap + TypeFormula.normalized(clauses.map(_.map(mapped)), orderedAtoms.map(mapped)) + def atoms: Set[A] = clauses.flatten + +object TypeFormula: + def atom[A](value: A): TypeFormula[A] = TypeFormula(Set(Set(value)))(Vector(value)) + def top[A]: TypeFormula[A] = TypeFormula(Set(Set.empty))(Vector.empty) + def bottom[A]: TypeFormula[A] = TypeFormula(Set.empty)(Vector.empty) + private def normalized[A](clauses: Set[Set[A]], order: Vector[A]): TypeFormula[A] = + val result = clauses.filterNot(clause => clauses.exists(other => other != clause && other.subsetOf(clause))) + val atoms = result.flatten + TypeFormula(result)(order.distinct.filter(atoms)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index ed3abb473b..9c55dd7202 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -12,6 +12,10 @@ enum TypeShape: case Alias(symbol: TypeAliasSymbol, rhs: Opt[TypeResolution]) case Union(left: TypeResolution, right: TypeResolution) case Intersection(left: TypeResolution, right: TypeResolution) + // Substituted unions/intersections retain whole interpreted endpoints. Keeping + // their lattice normal form prevents repeated Boolean substitutions from + // nesting environments, without expanding bounds or changing endpoint marks. + case Combined(formula: TypeFormula[DeclaredType]) case Tuple(fields: Ls[TypeResolution]) case Record(source: Term.Rcd, fields: Ls[(RcdField, TypeResolution)]) case Function(params: Term, result: TypeResolution) @@ -105,6 +109,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case Record(_, fields) => fields.foreach(_._2.validate(next)) case Union(left, right) => left.validate(next); right.validate(next) case Intersection(left, right) => left.validate(next); right.validate(next) + case Combined(formula) => formula.orderedAtoms.foreach(_.resolution.validate(next)) case _ => () /** A type with its lexical type-parameter bindings. These bindings describe declared diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index d12cf7b341..415e7c9f2a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -168,3 +168,104 @@ private fun local[A](value: A) = value local[Int](1) //│ = 1 + +// Repeated unions reach a finite set of components. +type Chain[A] = {value: A, next: Chain[A | Int]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + +// Repeated intersections also reach a fixed point. +type Chain[A] = {value: A, next: Chain[A & Int]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + +// Alternating Boolean operations need distribution and absorption to saturate. +type Chain[A] = {value: A, next: Chain[(A | Int) & Str]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + +// An absorbed constructor does not cause recursive argument growth. +type Chain[A] = {value: A, next: Chain[A | (A & Array[A])]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + +// A union does not hide a constructor that survives normalization. +:breakme +:e +type Chain[A] = {value: A, next: Chain[A | Array[A]]} +private fun unused[A](chain: Chain[A]) = () + +// Growth through a forwarding alias and a mutual cycle is still non-regular. +:breakme +:e +type Wrap[A] = Array[A] +type Left[A] = {value: A, next: Right[Wrap[A]]} +type Right[B] = {value: B, next: Left[B]} +private fun unused[A](chain: Left[A]) = () + +// Tuple and function constructors also count as growth. +:breakme +:e +type Chain[A] = {value: A, next: Chain[[A, A]]} +private fun unused[A](chain: Chain[A]) = () + +:breakme +:e +type Chain[A] = {value: A, next: Chain[Int -> A]} +private fun unused[A](chain: Chain[A]) = () + +// A forward alias must be resolved before deciding whether an argument grows. +:breakme +:e +type Chain[A] = {value: A, next: Chain[Later[A]]} +private fun unused[A](chain: Chain[A]) = () +type Later[A] = Array[A] + +// Boolean combinations can include an omitted argument, which remains a source hole. +type Chain[A] = {value: A, next: Chain[A | Array]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + +// A record constructor is growth even when its field is itself a union. +:breakme +:e +type Chain[A] = {value: A, next: Chain[{item: A | Int}]} +private fun unused[A](chain: Chain[A]) = () + +// Captured forwarding aliases inside a Boolean argument must also saturate. +type Identity[X] = X +private fun local[A](value: A) = + type Chain[B] = {value: B, next: Chain[B | Identity[B]]} + private fun walk[B](chain: Chain[B], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + value +local[Int](1) +//│ = 1 + +// Reusing one alias body twice must not hide the second constructor layer. +:breakme +:e +type Wrap[A] = Array[A] +type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} +private fun unused[A](chain: Chain[A]) = () + +// Boolean-only recursion also needs stable references, without a record boundary. +type Choice[A] = A | Choice[A | Int] +fun choose(value: Choice[Str]): Choice[Str] = value + +// Constructor growth is observable through a Boolean-only recursive alias too. +:breakme +:e +type Choice[A] = A | Choice[Array[A]] +private fun unused(value: Choice[Int]) = () + +// Selecting an absent output resets the observable argument to Any. +// This finite observation must not be rejected as non-regular constructor growth. +type Chain[A] = {value: A, next: Chain[in Array[A]]} +private fun read[A](chain: Chain[A]) = chain.next.value + +// Absorption across recursive steps can depend on the relationship between arguments. +// After two steps both arguments stabilize, despite a syntactic constructor cycle. +type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} +private fun walk[A, B](chain: Stable[A, B], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () From 200d7d3f8be06fe48f96c61462fca995133cc4c4 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 13:26:57 +0800 Subject: [PATCH 117/228] Reject unsupported recursive type growth conservatively --- doc/new-resolution-regular-types.md | 74 +++++----- doc/new-resolution-suite-migration.md | 5 +- doc/new-resolution-type-value-flow.md | 58 ++++---- .../scala/hkmc2/semantics/NewResolver.scala | 103 +++++++++++++- .../hkmc2/semantics/NewResolverState.scala | 2 + .../mlscript/newres/TypeGraphTermination.mls | 127 ++++++++++++++++-- 6 files changed, 294 insertions(+), 75 deletions(-) diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index bfa4628446..5b0faf9ef2 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -19,16 +19,17 @@ type Growing[A] = {value: A, next: Growing[Array[A]]} ``` The first four have finite unfoldings. `Growing[Int]` exposes `Int`, `Array[Int]`, -`Array[Array[Int]]`, and so on. It must eventually receive a regularity diagnostic, +`Array[Array[Int]]`, and so on. It receives a regularity diagnostic, without widening the type or imposing an expansion-depth limit. -Canonical references now reduce fully supplied aliases and normalize Boolean -combinations. The regularity diagnostic is **not implemented**. A check using one -dependency node per formal parameter is insufficient: it conflates the parameter's -input and output parts and loses correlations between arguments. The refinement -below requires review before implementation. -The existing stack-overflow regression remains a known failure, not an acceptance -case for non-regular recursion. +Canonical references reduce fully supplied aliases and normalize Boolean +combinations. The implemented check uses one dependency node per original formal +parameter and rejects constructor-bearing dependency cycles before observing the +structural interface. This is an intentionally conservative restriction: it can +reject regular types whose finite unfolding requires distinguishing argument parts +or correlations between successive substitutions. The diagnostic says that the +current check does not support the recursive type; it does not claim to prove +non-regularity. The more precise design below is deferred for later review. ## Implemented normalization @@ -93,13 +94,22 @@ This does **not** by itself bound the atoms. Constructor growth, retained argume parts that never become observable, and the existing requirements on finite marked contexts remain separate obligations. -## Why a binder-only growth graph is insufficient +## Implemented conservative check -A candidate check connected original formals through recursive applications. An -edge recorded whether the argument wrapped its source formal in a non-Boolean -constructor. A growing edge on a dependency cycle caused rejection. Alias and -Boolean normalization preceded edge construction; unused formals were removed by -existing source dependency analysis. +The check connects original formals through alias applications in the reachable +source graph. An edge records whether the argument wraps its source formal in a +non-Boolean constructor. A growing edge on a dependency cycle causes rejection. +Alias and Boolean normalization precede edge construction; unused formals are +removed by existing source dependency analysis. + +Validation waits until the source dependency graph is complete, including forward +references. It runs both when an alias occurs in a written annotation and before +an interface is observed, so an unused annotation cannot avoid the check. Source +visitation and alias expansion use finite source-node and alias-symbol guards; +reachability uses the finite original-binder graph. Accepted and rejected results +are cached by source root in the consuming resolver state. A rejected root does +not reject an unrelated regular child. No inference variables are allocated and +no ordinary mark operation is changed. That handles ordinary permutations and resets, including: @@ -112,7 +122,7 @@ The wrapping edge is followed by a reset, so no growing dependency cycle remains It also distinguishes an absorbed `A | (A & Array[A])` from the growing `A | Array[A]`. -However, this finite case would be incorrectly rejected: +The conservative restriction also rejects this finite case: ```mlscript type Chain[A] = {value: A, next: Chain[in Array[A]]} @@ -123,17 +133,16 @@ At the positive occurrence `value: A`, substituting `in Array[A]` selects its missing output part, `Any`. All later `value` components have that output type. The constructor inside the input part is not an observable growing component of this structural unfolding. A binder-only edge from `A` to `A` loses that fact. -The worksheet contains this finite projection as an acceptance test against an -incorrect regularity diagnostic. +The worksheet records the rejection explicitly as a current precision limitation. The representation needs the same distinction. Current free-binder projection retains the whole bound argument whenever a formal is relevant, including parts not selected by the structural body. Recursive observation can therefore accumulate input-part environments even when the resulting structural type is regular. Merely permitting this case in a rejection check would not establish termination. -The candidate rejection rule has not been enabled. +The conservative check rejects this case before that growth can occur. -## Proposed refinement for review +## Deferred refinement for later review Track dependencies on argument parts, rather than only on binder symbols. The analysis must distinguish lexical occurrence polarity and argument-part selection @@ -206,20 +215,20 @@ Writing `X = A | (B & Array[A])`, its argument states are: The second step computes `X | (A & Array[X])`, which is `X` because `X` already contains `A`. The next step absorbs `X & Array[X]`. A per-parameter growth graph -still contains a constructor cycle and would falsely reject the definition. +still contains a constructor cycle and rejects the definition under the current restriction. `TypeGraphTermination.mls` includes a recursive observation of this example. Consequently, refining binder dependencies into part dependencies is necessary for environment projection, but it is **not sufficient** to make constructor cycles -an exact regularity test after Boolean normalization. Such a graph can provide a -sufficient acceptance criterion; a positive cycle cannot by itself justify a -non-regularity diagnostic. The rejection procedure must also account for normalized -substitution relationships, and must terminate on genuinely growing cases. -This procedure is an unresolved design obligation. Repeatedly unfolding until a -cache stops growing, imposing a depth limit, or assuming a constructor cycle proves -growth would not resolve it. +an exact regularity test after Boolean normalization. Such a graph provides a +sufficient acceptance criterion; a positive cycle justifies the conservative +restriction, but not a claim of non-regularity. A more precise procedure would need +to account for normalized substitution relationships while terminating on genuinely +growing cases. That design is deferred. Repeatedly unfolding until a cache stops +growing, imposing a depth limit, or assuming a constructor cycle proves growth +would not resolve it. -Proposed implementation boundary for review: +Possible refinement steps, requiring review before implementation: 1. Specify and test the finite, part-sensitive demand equations above, including both polarities and alias substitution. This analysis itself has a finite @@ -228,8 +237,8 @@ Proposed implementation boundary for review: live references. Verify the variance example and ordinary holes without claiming that this supplies a complete regularity decision procedure. 3. Separately design a terminating rejection check that preserves correlations - needed by examples such as `Stable`. Only then enable regularity diagnostics, - including for unused written annotations. + needed by examples such as `Stable`. Use it to relax the current conservative + restriction without admitting unbounded reference environments. The whole resolver still needs a bound on accepted reference keys: finite source and binder identities alone do not bound nested environments. Boolean normalization @@ -241,8 +250,9 @@ requirements on the marks algebra remain in force throughout. `newres/TypeGraphTermination.mls` covers regular recursion, permutations, mutual resets, unused arguments, transparent and captured forwarding aliases, forward references, union/intersection saturation, Boolean-only recursion, and the variance -counterexample. Expected future regularity diagnostics are marked `:breakme`/`:e`; -they are completion obligations, not implemented rejection behavior. +counterexample. Rejected constructor cycles and the two accepted precision +limitations use ordinary `:e` expectations; they no longer rely on stack overflows +or future-diagnostic expectations. `TypeFormulaTest` checks normalization against Boolean truth tables and repeated alternating substitution. `TypeRelationTest` checks a thousand repeated reductions, diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 479ed4d819..6ce5f82a5f 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -92,8 +92,9 @@ The inventory below separates these from compiler and prelude gaps. constraints, reconstructed receiver contexts, inferred missing member types, [regular structural interfaces](new-resolution-regular-types.md), and the whole graph's termination bound. Alias and Boolean argument normalization is implemented; - regularity checking still needs dependencies on input and output argument parts - and relationships between recursive arguments. + a conservative regularity check rejects constructor-bearing dependency cycles. + Refinements using input/output argument parts and relationships between recursive + arguments are deferred; the current diagnostic also rejects some regular types. Concrete outstanding cases are in `newres/InstantiationSites.mls`, `StoredSpecializations.mls`, `ConstructorInstances.mls`, `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 06166eb6b1..fefa369aef 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -43,12 +43,14 @@ substitution rules below are implemented for declared type views. Require structural types to have a finite recursive representation, after alias and union/intersection normalization. The [regularity design](new-resolution-regular-types.md) -describes the implemented normalization and the input/output dependency refinement -still requiring review. Diagnosing non-regular types and sharing regular recursive -references are separate obligations; neither follows from the bound on call-site symbols. +describes the implemented normalization and conservative regularity check. A more +precise check using input/output dependencies and correlated substitutions is deferred. +Rejecting unsupported recursion and sharing accepted recursive references are separate +obligations; neither follows from the bound on call-site symbols. The finite call-site symbol bound alone is not a termination proof for the whole -resolver. Non-regular expansion still overflows. Fully supplied alias applications +resolver. The regularity check rejects constructor-bearing dependency cycles before +structural observation, including some finite cases. Fully supplied alias applications are reduced and Boolean combinations normalized before their arguments become part of recursive reference keys. Contextual references preserve class-local aliases and enclosing binders through nominal member projections. Dependency-based binding projection accepts regular @@ -730,11 +732,11 @@ for that scheme. By-name function/method cases, separate signatures, independent quantified results, shared mutation, and specialization inside generic bodies are also covered. Broader graph convergence remains a separate completion gate. -`newres/ConstructorInstances.mls` still contains two failing regressions. +`newres/ConstructorInstances.mls` still contains the failing reconstruction regression. Reconstruction with `class Box[T](val item: T) with { fun copy() = new Box[T](item) }` must retain the receiver's view of `T` separately from the new constructor's view; the first of two distinct receiver calls currently loses its resulting element -interface. Separately, this callback must receive the supplied input type: +interface. The separate supplied-callback regression now passes: ```mlscript class Item(val value: Int) @@ -820,9 +822,10 @@ private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () ``` -This non-regular variant still overflows the stack without any call to `walk`. -With `checkMarkPaths` enabled it reports repeated exits first, so its failure is -not evidence only of growing argument environments. The regular variant with +The conservative check now rejects this non-regular variant even without a call +to `walk`. Earlier unrestricted expansion also encountered repeated exits with +`checkMarkPaths` enabled; rejecting this type does not establish the marks algebra's +bound for other recursive constraints. The regular variant with `next: Chain[A]` and the finite selection `chain.next.next.value` now pass, including with mark checks enabled. Both constraint expansion and mark paths need validation independently of runtime recursion depth. @@ -855,9 +858,9 @@ parameter instances. Class-local aliases and local nominal annotations retain their deferred member contexts through the contextual-reference transport described below. These passing regressions do not establish the whole-graph bound: repeated-mark failures in generic -array-method paths and expanding alias environments still require separate checks. -Dependency projection accepts regular argument resets, including compound constants. -The expanding alias needs the regularity diagnostic described below. +array-method paths still require separate checks. Dependency projection accepts +regular argument resets, including compound constants. The conservative regularity +check rejects the expanding alias before observation. The same graph audit must distinguish an open definition template from an already interpreted reference. In `Box[T].copy() = new Box[T](item)`, the source `T` in the @@ -1033,7 +1036,7 @@ unfolding would not be acceptable. ### Regular structural types -The proposed restriction is that unfolding a structural type must admit a finite +The restriction is that unfolding an accepted structural type must admit a finite graph of distinct type components, with recursive occurrences represented by back-edges. A finite alias definition alone does not establish this property: @@ -1046,17 +1049,20 @@ type Growing[A] = {value: A, next: Growing[Array[A]]} `Growing[Int]` exposes successive `value` types `Int`, `Array[Int]`, `Array[Array[Int]]`, and so on. It is non-regular and should be diagnosed rather than expanded indefinitely or silently approximated. The third block of -`newres/TypeGraphTermination.mls` currently records this latter failure as a stack -overflow; it is no longer an acceptance case for unrestricted structural recursion. +`newres/TypeGraphTermination.mls` checks its rejection by the conservative +regularity diagnostic before structural observation. Regularity is not a requirement that recursive arguments be textually unchanged. For example, `{value: A, next: Alternating[B, A]}` as the body of `Alternating[A, B]` has a finite two-state unfolding. Mutually recursive aliases and finite changes of arguments must also be considered. Alias and Boolean -normalization is implemented. The remaining check must distinguish dependencies -on input and output argument parts, including when pruning saved environments; -its [refined design](new-resolution-regular-types.md#proposed-refinement-for-review) -remains under review. It must terminate on rejected inputs too; waiting for an +normalization is implemented. The current finite source-graph check rejects cycles +that put an original binder underneath a non-Boolean constructor. It deliberately +rejects some regular types: argument-part selection and absorption across recursive +steps can make such a cycle finite. The diagnostic states this implementation +restriction rather than claiming to prove non-regularity. The +[refined design](new-resolution-regular-types.md#deferred-refinement-for-later-review) +is deferred. Any replacement must terminate on rejected inputs too; waiting for an unfolding cache to stop growing is not a decision procedure. Do not impose a depth limit. Growth along one recursive edge is not sufficient to reject a type: @@ -1131,12 +1137,11 @@ checks a thousand reductions for equality, unchanged caller marks, stable listen counts, and zero binder allocation. `TypeGraphTermination.mls` covers nested forwarding aliases, parameter selection/permutation, forward definitions, a captured alias, and an unproductive cycle with growing arguments. This does not prove the -whole-graph bound: non-regular structural expansion and other growing binding -environments still need the regularity check. That check must not classify -transparent alias forwarding as a growing type constructor. See the +whole-graph bound. The conservative regularity check rejects constructor-bearing +cycles while treating transparent alias forwarding as substitution. See the [normalization implementation and variance counterexample](new-resolution-regular-types.md) -for the Boolean normal form, captured alias reduction, and why a binder-only -growth graph is insufficient. +for the Boolean normal form, captured alias reduction, and the precision limits +of the current binder-only growth graph. The implemented dependency analysis operates on source `TypeResolution` nodes. An edge forwards the child's free binders, excluding those bound by an alias or @@ -1166,8 +1171,9 @@ tuple test check delayed discovery, independent substitutions, and replay withou additional listeners or parameter instances. This bounds dependency analysis and eliminates irrelevant environment growth. -It does not yet establish the full representation bound for every regular type, -or diagnose non-regular structural expansion; those remain completion obligations. +Together with the conservative source-graph check, it prevents the recorded +constructor-growth cases. It does not establish the whole resolver's representation +bound; accepted reference environments and inferred constraints still need that audit. Keep structural alias unfolding distinct from inferred constraints at a recursive generic function call. The latter reuses a site's parameter symbol and can add an diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index fe0d07261b..9bd9b76df2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -76,7 +76,11 @@ class NewResolver: case _ => () def definition(defn: Definition)(using NewResolverState): Unit = defn match case cls: ClassLikeDef => result.publish(TypeShape.Nominal(cls)) - case alias: TypeDef => result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) + case alias: TypeDef => + result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) + // Validate unused annotations too, once their forward source graph + // is complete. Interface observation uses the same gate below. + withCanonicalType(DeclaredType(result, Map.empty, Map.empty, true))(_ => ()) case _ => result.publish(TypeShape.Abstract) symbol.defn match case S(defn) => definition(defn) @@ -267,6 +271,98 @@ class NewResolver: dependencies.foreach((res, binders) => rstate.typeDependencies(res) = binders) R(dependencies(resolution)) + /** Check recursive substitutions on the finite source graph, before observing + * structural children can generate an unbounded sequence of environments. + * Edges connect original formal parameters, and record whether their argument + * puts the source formal underneath a non-Boolean constructor. A growing edge + * is forbidden only when its destination can reach its source: constants and + * resets break dependency cycles, while permutations do not add constructors. + * Alias and Boolean reduction is shared with ordinary argument interpretation. + * This is a conservative sufficient condition, not an exact regularity test: + * argument-part selection and absorption across recursive steps can make a + * rejected cycle finite. Do not describe rejection as proof of non-regularity. + */ + private def regularType(root: TypeResolution)(using NewResolverState): Bool = rstate.regularTypes.get(root) match + case S(result) => result + case N => + val visited = mutable.Set.empty[TypeResolution] + val applications = mutable.ArrayBuffer.empty[(TypeResolution, TypeAliasSymbol, TypeResolution, Ls[TypeResolution])] + def alias(base: TypeResolution, seen: Set[TypeResolution]): Opt[(TypeAliasSymbol, TypeResolution)] = + if seen(base) then N else base.currentShapes.toList match + case TypeShape.Alias(symbol, S(rhs)) :: Nil => S(symbol -> rhs) + case TypeShape.Captured(inner, _) :: Nil => alias(inner, seen + base) + case _ => N + def visit(res: TypeResolution): Unit = if visited.add(res) then + res.currentShapes.foreach: + case TypeShape.Alias(_, rhs) => rhs.foreach(visit) + case TypeShape.Captured(base, _) => visit(base) + case TypeShape.Applied(base, args) => + alias(base, Set.empty).foreach: (symbol, rhs) => + applications += ((res, symbol, rhs, args)) + visit(base) + args.foreach(visit) + case TypeShape.Tuple(fields) => fields.foreach(visit) + case TypeShape.Record(_, fields) => fields.foreach((_, field) => visit(field)) + case TypeShape.Function(params, result) => visit(typeResolution(params)); visit(result) + case TypeShape.Polymorphic(params, _, body) => + params.foreach: param => + param.lower.foreach(visit) + param.upper.foreach(visit) + visit(body) + case TypeShape.Union(left, right) => visit(left); visit(right) + case TypeShape.Intersection(left, right) => visit(left); visit(right) + case TypeShape.Wildcard(input, output) => input.foreach(visit); output.foreach(visit) + case TypeShape.Combined(formula) => formula.orderedAtoms.foreach(atom => visit(atom.resolution)) + case TypeShape.Argument(parts) => visit(parts.input.resolution); visit(parts.output.resolution) + case TypeShape.SelectedArgument(argument, _) => visit(argument.resolution) + case TypeShape.Contextual(reference) => visit(reference.tpe.resolution) + case _ => () + visit(root) + def dependencies(res: TypeResolution): Set[VarSymbol] = typeDependencies(res) match + case R(binders) => binders + case L(_) => lastWords("Regularity requires a completed source dependency graph") + def uses(tpe: DeclaredType, wrapped: Bool, seen: Set[DeclaredType], aliases: Set[TypeAliasSymbol]): Set[(VarSymbol, Bool)] = + if seen(tpe) then Set.empty else + val next = seen + tpe + def bound(symbol: VarSymbol, inside: Bool): Set[(VarSymbol, Bool)] = tpe.bindings.get(symbol) match + case S(argument) => uses(selectArgument(argument, tpe.positive), inside, next, aliases) + case N => Set(symbol -> inside) + tpe.resolution.currentShapes.toList match + case TypeShape.Parameter(symbol, _) :: Nil => bound(symbol, wrapped) + case TypeShape.Combined(formula) :: Nil => formula.atoms.flatMap(uses(_, wrapped, next, aliases)) + case TypeShape.Contextual(reference) :: Nil => uses(reference.tpe, wrapped, next, aliases) + case TypeShape.Argument(parts) :: Nil => uses(parts.input, wrapped, next, aliases) ++ uses(parts.output, wrapped, next, aliases) + case TypeShape.SelectedArgument(argument, _) :: Nil => uses(argument, wrapped, next, aliases) + case TypeShape.Applied(base, args) :: Nil => alias(base, Set.empty) match + case S((symbol, rhs)) if !aliases(symbol) => + // Constructor analysis ignores scope crossings, but still follows + // the alias's substitution. The guard bounds body expansion; + // substitution values are finite immutable reference DAGs. + val substitutions = symbol.defn.get.tparams.zip(args).map: (formal, argument) => + formal.sym -> argumentType(declaredType(argument, tpe), formal.vce) + uses(declaredType(rhs, tpe.bindings ++ substitutions, tpe.positive), wrapped, next, aliases + symbol) + case S(_) => dependencies(tpe.resolution).flatMap(bound(_, wrapped)) + case N => dependencies(tpe.resolution).flatMap(bound(_, true)) + case _ => dependencies(tpe.resolution).flatMap(bound(_, true)) + val edges = applications.toList.flatMap: (source, symbol, rhs, args) => + symbol.defn.get.tparams.zip(args).flatMap: (formal, argument) => + if !dependencies(rhs)(formal.sym) then Nil + else uses(declaredType(argument, Map.empty), false, Set.empty, Set.empty).toList.map: + case (origin, growing) => (origin, formal.sym, growing, source, symbol) + val successors = edges.groupMap(_._1)(_._2) + def reaches(current: VarSymbol, target: VarSymbol, seen: Set[VarSymbol]): Bool = + current == target || (!seen(current) && successors.getOrElse(current, Nil).exists(reaches(_, target, seen + current))) + val invalid = edges.filter: (origin, target, growing, _, _) => + growing && reaches(target, origin, Set.empty) + invalid.foreach: (_, formal, _, source, symbol) => + source.fail(msg"Recursive type '${symbol.nme}' is not supported by the current regularity check." -> source.source.toLoc :: + (msg"Type parameter '${formal.nme}' occurs under a type constructor on a recursive dependency cycle." -> formal.toLoc) :: Nil) + val result = invalid.isEmpty + // Failure belongs to this reachable graph, not to unrelated children in + // it. A later root may observe a regular subgraph of a rejected alias. + rstate.regularTypes(root) = result + result + /** Wait for the source graph before observing a substituted interface. The * retained environment is projected only after every forward dependency has * a target. Subscriptions and completed summaries are shared by source node; @@ -274,8 +370,9 @@ class NewResolver: */ private def withCanonicalType(tpe: DeclaredType)(listener: ShapeListener[DeclaredType])(using NewResolverState): Unit = def publish(binders: Set[VarSymbol])(using NewResolverState): Unit = - listener(declaredType(tpe.resolution, tpe.bindings.filter((symbol, _) => binders(symbol)), tpe.positive) - .instantiate(tpe.instances)) + if regularType(tpe.resolution) then + listener(declaredType(tpe.resolution, tpe.bindings.filter((symbol, _) => binders(symbol)), tpe.positive) + .instantiate(tpe.instances)) typeDependencies(tpe.resolution) match case R(binders) => publish(binders) case L(_) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index dabf2eaf01..93d95231c8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -212,6 +212,8 @@ final class NewResolverState private ( new Cache(inherited.map(_.lexicalTypeBinders), identity) val typeDependencies: Cache[TypeResolution, Set[VarSymbol]] = new Cache(inherited.map(_.typeDependencies), identity) + val regularTypes: Cache[TypeResolution, Bool] = + new Cache(inherited.map(_.regularTypes), identity) val combinedTypes: Cache[TypeFormula[DeclaredType], DeclaredType] = new Cache(inherited.map(_.combinedTypes), identity) val pendingTypeDependencies: Cache[TypeResolution, TypeDependencyHost] = diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index 415e7c9f2a..b5f59a5a55 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -10,13 +10,24 @@ type Chain[A] = {value: A, next: Chain[A]} private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () -// This non-regular structural alias must eventually receive a regularity diagnostic: +// This non-regular structural alias receives a regularity diagnostic: // its value types expand to Array[Array[...]]. It must not overflow during checking. -:fixme +:e type Chain[A] = {value: A, next: Chain[Array[A]]} private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () -//│ /!!!\ Uncaught error: java.lang.StackOverflowError +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.16: type Chain[A] = {value: A, next: Chain[Array[A]]} +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. +//│ ║ l.16: type Chain[A] = {value: A, next: Chain[Array[A]]} +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.16: type Chain[A] = {value: A, next: Chain[Array[A]]} +//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] This selection of member 'next' has no resolved target +//│ ║ l.18: if n > 0 then walk(chain.next, n - 1) else () +//│ ╙── ^^^^^^^^^^ // A regular parameter permutation has two interfaces, not an expanding environment. class First(val first: Int) @@ -190,36 +201,76 @@ private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () // A union does not hide a constructor that survives normalization. -:breakme :e type Chain[A] = {value: A, next: Chain[A | Array[A]]} private fun unused[A](chain: Chain[A]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.205: type Chain[A] = {value: A, next: Chain[A | Array[A]]} +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. +//│ ║ l.205: type Chain[A] = {value: A, next: Chain[A | Array[A]]} +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.205: type Chain[A] = {value: A, next: Chain[A | Array[A]]} +//│ ╙── ^ // Growth through a forwarding alias and a mutual cycle is still non-regular. -:breakme :e type Wrap[A] = Array[A] type Left[A] = {value: A, next: Right[Wrap[A]]} type Right[B] = {value: B, next: Left[B]} private fun unused[A](chain: Left[A]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.220: type Left[A] = {value: A, next: Right[Wrap[A]]} +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Right' is not supported by the current regularity check. +//│ ║ l.220: type Left[A] = {value: A, next: Right[Wrap[A]]} +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'B' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.221: type Right[B] = {value: B, next: Left[B]} +//│ ╙── ^ // Tuple and function constructors also count as growth. -:breakme :e type Chain[A] = {value: A, next: Chain[[A, A]]} private fun unused[A](chain: Chain[A]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.235: type Chain[A] = {value: A, next: Chain[[A, A]]} +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. +//│ ║ l.235: type Chain[A] = {value: A, next: Chain[[A, A]]} +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.235: type Chain[A] = {value: A, next: Chain[[A, A]]} +//│ ╙── ^ -:breakme :e type Chain[A] = {value: A, next: Chain[Int -> A]} private fun unused[A](chain: Chain[A]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.248: type Chain[A] = {value: A, next: Chain[Int -> A]} +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. +//│ ║ l.248: type Chain[A] = {value: A, next: Chain[Int -> A]} +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.248: type Chain[A] = {value: A, next: Chain[Int -> A]} +//│ ╙── ^ // A forward alias must be resolved before deciding whether an argument grows. -:breakme :e type Chain[A] = {value: A, next: Chain[Later[A]]} private fun unused[A](chain: Chain[A]) = () type Later[A] = Array[A] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.262: type Chain[A] = {value: A, next: Chain[Later[A]]} +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. +//│ ║ l.262: type Chain[A] = {value: A, next: Chain[Later[A]]} +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.262: type Chain[A] = {value: A, next: Chain[Later[A]]} +//│ ╙── ^ // Boolean combinations can include an omitted argument, which remains a source hole. type Chain[A] = {value: A, next: Chain[A | Array]} @@ -227,10 +278,18 @@ private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () // A record constructor is growth even when its field is itself a union. -:breakme :e type Chain[A] = {value: A, next: Chain[{item: A | Int}]} private fun unused[A](chain: Chain[A]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.282: type Chain[A] = {value: A, next: Chain[{item: A | Int}]} +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. +//│ ║ l.282: type Chain[A] = {value: A, next: Chain[{item: A | Int}]} +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.282: type Chain[A] = {value: A, next: Chain[{item: A | Int}]} +//│ ╙── ^ // Captured forwarding aliases inside a Boolean argument must also saturate. type Identity[X] = X @@ -243,29 +302,73 @@ local[Int](1) //│ = 1 // Reusing one alias body twice must not hide the second constructor layer. -:breakme :e type Wrap[A] = Array[A] type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} private fun unused[A](chain: Chain[A]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.307: type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. +//│ ║ l.307: type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.307: type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} +//│ ╙── ^ // Boolean-only recursion also needs stable references, without a record boundary. type Choice[A] = A | Choice[A | Int] fun choose(value: Choice[Str]): Choice[Str] = value // Constructor growth is observable through a Boolean-only recursive alias too. -:breakme :e type Choice[A] = A | Choice[Array[A]] private fun unused(value: Choice[Int]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.325: type Choice[A] = A | Choice[Array[A]] +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Choice' is not supported by the current regularity check. +//│ ║ l.325: type Choice[A] = A | Choice[Array[A]] +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.325: type Choice[A] = A | Choice[Array[A]] +//│ ╙── ^ // Selecting an absent output resets the observable argument to Any. -// This finite observation must not be rejected as non-regular constructor growth. +// The current conservative check rejects this regular case; retain the example +// for the deferred input/output dependency refinement. +:e type Chain[A] = {value: A, next: Chain[in Array[A]]} private fun read[A](chain: Chain[A]) = chain.next.value +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.341: type Chain[A] = {value: A, next: Chain[in Array[A]]} +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. +//│ ║ l.341: type Chain[A] = {value: A, next: Chain[in Array[A]]} +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.341: type Chain[A] = {value: A, next: Chain[in Array[A]]} +//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.342: private fun read[A](chain: Chain[A]) = chain.next.value +//│ ╙── ^^^^^^^^^^^^^^^^ // Absorption across recursive steps can depend on the relationship between arguments. // After two steps both arguments stabilize, despite a syntactic constructor cycle. +// The current conservative check rejects this regular case too. +:e type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} private fun walk[A, B](chain: Stable[A, B], n: Int) = if n > 0 then walk(chain.next, n - 1) else () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.360: type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Recursive type 'Stable' is not supported by the current regularity check. +//│ ║ l.360: type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.360: type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} +//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] This selection of member 'next' has no resolved target +//│ ║ l.362: if n > 0 then walk(chain.next, n - 1) else () +//│ ╙── ^^^^^^^^^^ From b318f6cd70bec01c994b240a13d25cad14edf144 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 13:35:38 +0800 Subject: [PATCH 118/228] Bind captured specializations in their application scope --- doc/new-resolution-suite-migration.md | 4 ++ doc/new-resolution-type-value-flow.md | 26 +++++++- .../scala/hkmc2/semantics/NewResolver.scala | 29 ++++++--- .../mlscript/newres/ConstructorInstances.mls | 28 ++++++++ .../newres/SpecializationCaptures.mls | 64 +++++++++++++++++++ 5 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/SpecializationCaptures.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 6ce5f82a5f..7f3d59f239 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -98,6 +98,10 @@ The inventory below separates these from compiler and prelude gaps. Concrete outstanding cases are in `newres/InstantiationSites.mls`, `StoredSpecializations.mls`, `ConstructorInstances.mls`, `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. + Captured explicit specializations now bind arguments in the application scope; + `SpecializationCaptures.mls` covers nested function and by-name captures. + Reconstruction still mixes unannotated field flows as well as losing annotated + receiver contexts, so its remaining fix must cover ordinary value inference too. Loss of caller separation through omitted type arguments is an accepted current limitation; defer that substitution redesign while completing the other cases. Decide how reassignment affects mutable storage's inferred interface, including diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index fefa369aef..083659a98b 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -717,6 +717,22 @@ retained through callback checking, tuple/record results, closures, and construc aliases. Elaboration defers observing the base reference of a type application until its arguments are available, preventing a second by-name instantiation. +The supplied types belong to the type application's scope. For an ordinary +function or constructor, first transport the referenced callable through all +lexical captures, then bind its supplied arguments using that complete path. +Binding arguments before entering those captures makes specialization and the +later term application disagree about their common scope. In particular, +`wrap[B](value: B) = identity[B](value)` could accumulate exit marks even though +its source has no recursion. For a by-name definition, the reference itself +executes the body: rebase supplied arguments to that reference's scope before +invocation, then transport the result outward. Do not apply the supplied arguments +a second time to a separately quantified result. These transfers use ordinary +mark operations; no exit/reentry pair is treated as an identity. +`newres/SpecializationCaptures.mls` checks both forms through nested captures, +caller type parameters, and constructor specialization. These regressions also +pass with `checkMarkPaths` enabled; the default remains unchanged because other +existing paths still violate its invariants. + Constructors use the same site policy. `C[T]` and `new C[T]` instantiate at their explicit type application, including when term arguments remain to be supplied. Without explicit arguments, an unapplied `new C` waits for its first term @@ -732,11 +748,17 @@ for that scheme. By-name function/method cases, separate signatures, independent quantified results, shared mutation, and specialization inside generic bodies are also covered. Broader graph convergence remains a separate completion gate. -`newres/ConstructorInstances.mls` still contains the failing reconstruction regression. +`newres/ConstructorInstances.mls` retains the failing reconstruction regressions. Reconstruction with `class Box[T](val item: T) with { fun copy() = new Box[T](item) }` must retain the receiver's view of `T` separately from the new constructor's view; the first of two distinct receiver calls currently loses its resulting element -interface. The separate supplied-callback regression now passes: +interface. The unannotated control, `class Box(val item)` with +`copy() = new Box(item)`, mixes field candidates from distinct receivers. Thus the +remaining receiver-context problem also affects ordinary inferred value flow; +correcting only the type-binder map does not establish receiver separation. +The captured constructor-value form `Box[T](item)` now terminates, but its returned +field still needs the same receiver-context work as explicit `new`. +The separate supplied-callback regression now passes: ```mlscript class Item(val value: Int) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 9bd9b76df2..c944a5f4c0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2561,24 +2561,33 @@ class NewResolver: case specialized: TermShape => receive(specialized) case NoShape => () case _ => receive(value) - // Preserve lexical captures while supplying the invocation's arguments - // before a by-name reference publishes its result. Observing the result - // first would either consume the scheme twice or specialize a result scheme. - def observe(base: Term)(receive: Listener)(using NewResolverState): Unit = base match + // A value specialization binds its arguments in the type application's + // scope, after the reference has crossed all intervening captures. By-name + // definitions execute at the reference itself: supply their arguments there, + // rebasing from the application scope with the ordinary mark operations. + // Their result may have an independent scheme and must not be specialized + // again with the arguments belonging to the by-name definition. + def observe(base: Term, captures: Ls[Marks]) + (receive: (TermShape, Bool) => NewResolverState ?=> Unit)(using NewResolverState): Unit = base match case Capture(inner, thru) => - observe(inner)(shape => receive(if discardMarks then shape else MarkedShape.enter(shape, ResolutionBoundary(thru), N))) + val crossing = EntryMark(ResolutionBoundary(thru), N, NoMarks) + observe(inner, crossing :: captures): (shape, invoked) => + receive(if discardMarks then shape else MarkedShape.enter(shape, ResolutionBoundary(thru), N), invoked) case _ => listen(base, discardMarks): case sym: SymShape => sym.sym.onComplete: () => valueTarget(sym.sym, false).flatMap(_.defn) match case S(td: TermDefinition) if isByName(td) => val definition = defnShapes.getOrElseUpdate(td.tsym, DefnShape(td, N)) checkArity(definition, definitionBinders(td).length) - val arguments = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) - fromSymbolAt(sym, receive, base, recordValueTarget(base, _), false, + val arguments = args.map: arg => + val caller = declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances) + transportType(caller, inverseMarks(captures)) + fromSymbolAt(sym, value => receive(value, true), base, recordValueTarget(base, _), false, S((rstate.typeApplicationSite(application), arguments))) - case _ => fromSymbol(sym, value => instantiate(value)(receive), base, recordValueTarget(base, _), false) - case value: TermShape => instantiate(value)(receive) - observe(underlying)(listener) + case _ => fromSymbol(sym, value => receive(value, false), base, recordValueTarget(base, _), false) + case value: TermShape => receive(value, false) + observe(underlying, Nil): (value, invoked) => + if invoked then listener(value) else instantiate(value)(listener) case mut @ Mut(underlying: Tup) => listener(mutableArray(mut, underlying)) case Mut(underlying) => listenTerm(underlying)(listener) case tuple: Tup => listenAggregate(tuple, listener): publish => diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls index c71a9b03da..c0d448e549 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls @@ -199,3 +199,31 @@ first(...callbacks)(new Base).extra //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ callbacks = [fun] + +// Ordinary value flow through reconstruction also needs receiver separation. +// This control contains no type binders or annotations on the Box field. +:fixme +:expect [21, 22] +class First(val first: Int) +class Second(val second: Int) +class Box(val item) with + fun copy() = new Box(item) +let first = new Box(First(21)) +let second = new Box(Second(22)) +[first.copy().item.first, second.copy().item.second] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.213: [first.copy().item.first, second.copy().item.second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.211: let first = new Box(First(21)) +//│ ╙── ^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.213: [first.copy().item.first, second.copy().item.second] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.212: let second = new Box(Second(22)) +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '[21, 22]', got: 'undefined' +//│ first = Box(First(21)) +//│ second = Box(Second(22)) diff --git a/hkmc2/shared/src/test/mlscript/newres/SpecializationCaptures.mls b/hkmc2/shared/src/test/mlscript/newres/SpecializationCaptures.mls new file mode 100644 index 0000000000..5659c5b521 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/SpecializationCaptures.mls @@ -0,0 +1,64 @@ +:. + +// Explicit arguments are supplied after the reference enters the caller's scope. +:expect 1 +class Item(val value: Int) +private fun identity[A](value: A) = value +private fun use() = identity[Item -> Int](x => x.value)(Item(1)) +use() +//│ = 1 + +// More than one enclosing capture must retain the order of scope crossings. +:expect 2 +class Item(val value: Int) +private fun identity[A](value: A) = value +private fun wrap() = + fun inner() = identity[Item -> Int](x => x.value)(Item(2)) + inner() +wrap() +//│ = 2 + +// A by-name definition consumes the supplied arguments before producing a value. +:expect 3 +class Item(val value: Int) +private fun identity[A] = (value: A) => value +private fun use() = identity[Item -> Int](x => x.value)(Item(3)) +use() +//│ = 3 + +// The supplied type itself can refer to the enclosing function's type parameter. +:expect [4, 5] +class First(val first: Int) +class Second(val second: Int) +private fun identity[A](value: A) = value +private fun wrap[B](value: B) = identity[B](value) +[wrap(First(4)).first, wrap(Second(5)).second] +//│ = [4, 5] + +// By-name specialization also interprets enclosing parameters at the use site. +:expect [6, 7] +class First(val first: Int) +class Second(val second: Int) +private fun identity[A] = (value: A) => value +private fun wrap[B](value: B) = identity[B](value) +[wrap(First(6)).first, wrap(Second(7)).second] +//│ = [6, 7] + +// By-name rebasing preserves multiple nested captures too. +:expect [8, 9] +class First(val first: Int) +class Second(val second: Int) +private fun identity[A] = (value: A) => value +private fun wrap[B](value: B) = + fun inner() = identity[B](value) + inner() +[wrap(First(8)).first, wrap(Second(9)).second] +//│ = [8, 9] + +// Captured constructor specialization must terminate before any result use. +:expect Box(Item(10)) +class Item(val value: Int) +class Box[T](val item: T) with + fun copy() = Box[T](item) +(new Box[Item](Item(10))).copy() +//│ = Box(Item(10)) From 2bf2955bf27c3d5a9cab634470c4b70b673f9283 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 13:52:09 +0800 Subject: [PATCH 119/228] Fix lexical scope transfers in declared array and primitive interfaces --- .../scala/hkmc2/semantics/Elaborator.scala | 29 +++++-- .../scala/hkmc2/semantics/NewResolver.scala | 81 +++++++++++-------- .../hkmc2/semantics/NewResolverState.scala | 4 + .../main/scala/hkmc2/semantics/Shape.scala | 7 +- .../src/test/mlscript/newres/Functions.mls | 14 ++++ 5 files changed, 89 insertions(+), 46 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index de32081983..cba0f3d5d9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -174,6 +174,14 @@ object Elaborator: if config.language.useNewResolution then inherited.map(capture) else inherited + /** Legacy syntax omits Capture nodes, but its type references can later be + * interpreted by a new-resolution consumer. Save the same lexical path + * while lookup still knows where the binding was found. + */ + def referenceCaptures(name: Str): Ls[AnyDefinitionSymbol] = + if env.contains(name) then Nil + else parent.toList.flatMap(_.referenceCaptures(name)) ::: outer.resolutionBoundary.toList + private def capture(elem: Ctx.Elem): Ctx.Elem = outer.resolutionBoundary.fold(elem)(Ctx.CaptElem(elem, _)) @@ -2750,14 +2758,19 @@ extends Importer: msg"Expected a parameter list (a tuple of parameters), but found ${t.describe}" -> t.toLoc :: Nil (ParamList(ParamListFlags.empty, Nil, N).withLocOf(t), ctx) - def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = ctx.get(id.name) match - case candidate @ (S(_: Ctx.WildcardElem) | N) => - // Primitive operators are implicit bindings outside the Ctx environments; - // like explicit bindings, they take precedence over wildcard sources. - state.builtinOpsMap.get(id.name) match - case S(bi) => S(bi.ref(id)) - case N => candidate.map(_.ref(id)) - case S(elem) => S(elem.ref(id)) + def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = + val result = ctx.get(id.name) match + case candidate @ (S(_: Ctx.WildcardElem) | N) => + // Primitive operators are implicit bindings outside the Ctx environments; + // like explicit bindings, they take precedence over wildcard sources. + state.builtinOpsMap.get(id.name) match + case S(bi) => S(bi.ref(id)) + case N => candidate.map(_.ref(id)) + case S(elem) => S(elem.ref(id)) + if !newResolution then result.foreach: reference => + rstate.data(reference) + rstate.legacyReferenceCaptures(new Identity(reference)) = ctx.referenceCaptures(id.name) + result def pattern(t: Tree): Ctxl[Pattern] = import ucs.{Ctor, unapply, error}, ucs.extractors.*, Keyword.*, Pattern.*, InvalidReason.* diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index c944a5f4c0..3b704aa5a2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -64,7 +64,24 @@ class NewResolver: rs.typeInterpretations.get(new Identity(term)).orElse(term.typeInterpretation) match case S(result) => result case N => - val result = new TypeResolution(term, messages => resolError(term, messages)) + val interpreted = new TypeResolution(term, messages => resolError(term, messages)) + def legacyCaptures(source: Term): Ls[AnyDefinitionSymbol] = + val graph = source.originalData.owner + val recorded = if graph == null then N else graph.nn.legacyReferenceCaptures.get(new Identity(source)) + recorded.getOrElse(source match + case SynthSel(prefix, _) => legacyCaptures(prefix) + case Sel(prefix, _) => legacyCaptures(prefix) + case _ => Nil) + // The captured interpretation is consumer-owned. Source metadata is + // finite lexical syntax; it neither allocates call instances nor depends + // on the number of times an imported annotation is observed. + val result = legacyCaptures(term).foldLeft(interpreted): (base, scope) => + scope match + case _: TypeAliasSymbol => base + case _ => + val captured = new TypeResolution(term, messages => resolError(term, messages)) + captured.publish(TypeShape.Captured(base, scope)) + captured // Cache before following a recursive alias. Imported legacy annotations // have no stored interpretation: their new inference graph must remain // private to this consumer, including listeners on synthesized hosts. @@ -75,25 +92,25 @@ class NewResolver: rstate.recordResolution(ref, ref.resolvedTargets.contains(symbol))(ref.resolvedTargets ::= symbol) case _ => () def definition(defn: Definition)(using NewResolverState): Unit = defn match - case cls: ClassLikeDef => result.publish(TypeShape.Nominal(cls)) + case cls: ClassLikeDef => interpreted.publish(TypeShape.Nominal(cls)) case alias: TypeDef => - result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) + interpreted.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) // Validate unused annotations too, once their forward source graph // is complete. Interface observation uses the same gate below. withCanonicalType(DeclaredType(result, Map.empty, Map.empty, true))(_ => ()) - case _ => result.publish(TypeShape.Abstract) + case _ => interpreted.publish(TypeShape.Abstract) symbol.defn match case S(defn) => definition(defn) case N => symbol.defnListeners += definition def reject(shape: Shape)(using NewResolverState): Unit = result.fail(msg"${shape.describe.capitalize} cannot be used as a type" -> shape.toLoc :: Nil) - result.publish(TypeShape.Abstract) + interpreted.publish(TypeShape.Abstract) def selectMember(member: BlockMemberSymbol)(using NewResolverState): Unit = member.onComplete: () => member.asTpe.orElse(member.asModOrObj) match case S(symbol) => select(symbol) case N => result.fail(msg"${member.describe.capitalize} cannot be used as a type" -> member.toLoc :: Nil) - result.publish(TypeShape.Abstract) + interpreted.publish(TypeShape.Abstract) term match // Imported prelude declarations can still contain legacy references. case Ref(symbol: TypeSymbol) => select(symbol) @@ -107,32 +124,32 @@ class NewResolver: case S(member: BlockMemberSymbol) => selectMember(member) case _ => result.fail(msg"This reference does not denote a type." -> ref.toLoc :: Nil) - result.publish(TypeShape.Abstract) + interpreted.publish(TypeShape.Abstract) // An alias's lexical qualification substitutes type references; it // does not cross an invocation boundary. Captured function and class // parameters still retain their actual value-scope crossings. - case Capture(base, _: TypeAliasSymbol) => typeResolution(base).listen(result.publish) - case Capture(base, thru) => result.publish(TypeShape.Captured(typeResolution(base), thru)) - case TyApp(base, args) => result.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) + case Capture(base, _: TypeAliasSymbol) => typeResolution(base).listen(interpreted.publish) + case Capture(base, thru) => interpreted.publish(TypeShape.Captured(typeResolution(base), thru)) + case TyApp(base, args) => interpreted.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) case Forall(params, outer, body) => - result.publish(TypeShape.Polymorphic(params.map: param => + interpreted.publish(TypeShape.Polymorphic(params.map: param => TypeQuantifier(TypeShape.Parameter(param.sym, param.sym.inferenceHost), param.lb.map(typeResolution), param.ub.map(typeResolution)) , outer, typeResolution(body))) case CompType(left, right, union) => val l = typeResolution(left) val r = typeResolution(right) - result.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) - case DynTy() => result.publish(TypeShape.Dynamic) - case FunTy(params, ret, _) => result.publish(TypeShape.Function(params, typeResolution(ret))) - case UnitVal() => result.publish(TypeShape.Unit) - case WildcardTy(input, output) => result.publish(TypeShape.Wildcard(input.map(typeResolution), output.map(typeResolution))) + interpreted.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) + case DynTy() => interpreted.publish(TypeShape.Dynamic) + case FunTy(params, ret, _) => interpreted.publish(TypeShape.Function(params, typeResolution(ret))) + case UnitVal() => interpreted.publish(TypeShape.Unit) + case WildcardTy(input, output) => interpreted.publish(TypeShape.Wildcard(input.map(typeResolution), output.map(typeResolution))) case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => - result.publish(TypeShape.Parameter(sym, sym.inferenceHost)) + interpreted.publish(TypeShape.Parameter(sym, sym.inferenceHost)) case Ref(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => - result.publish(TypeShape.Parameter(sym, sym.inferenceHost)) + interpreted.publish(TypeShape.Parameter(sym, sym.inferenceHost)) case Tup(fields) if fields.forall(_.isInstanceOf[Fld]) => - result.publish(TypeShape.Tuple(fields.collect { case Fld(_, sign, _) => typeResolution(sign) })) + interpreted.publish(TypeShape.Tuple(fields.collect { case Fld(_, sign, _) => typeResolution(sign) })) case record: Rcd => val fields = record.stats.collect { case field: RcdField => field } if fields.length != record.stats.length || fields.exists(field => field.field match @@ -140,11 +157,11 @@ class NewResolver: case _ => true) then result.fail(msg"A record type requires statically named fields." -> record.toLoc :: Nil) - result.publish(TypeShape.Abstract) - else result.publish(TypeShape.Record(record, + interpreted.publish(TypeShape.Abstract) + else interpreted.publish(TypeShape.Record(record, fields.reverse.distinctBy(_.sym.nme).reverse.map(field => field -> typeResolution(field.rhs)))) case _: Neg | _: Tup | _: Lit | Missing | Error() => - result.publish(TypeShape.Abstract) + interpreted.publish(TypeShape.Abstract) case _ => listen(term, discardMarks = true): case shape: SymShape => shape.sym.onComplete: () => shape.sym.asTpe.orElse(shape.sym.asModOrObj) match @@ -960,17 +977,7 @@ class NewResolver: case value: TermShape => listener(instantiateShape(value, instances))(using rstate.withInstances(callerInstances)) case NoShape => () def signature(sign: Term, polarity: Bool)(using NewResolverState): DeclaredType = - // Legacy annotations have plain references instead of Capture nodes. - // Rebase substituted class parameters into this member's scope, just - // as a new-resolution Capture does, before the member exits it. - val legacyParameters = mutable.Set.empty[VarSymbol] - def visit(term: Term)(using NewResolverState): Unit = term match - case Ref(symbol: VarSymbol) if capturedBindings.contains(symbol) => legacyParameters += symbol - case _ => term.subTerms.foreach(visit) - if crossesValueScope then visit(sign) - val scopedBindings = capturedBindings.map: (symbol, bound) => - symbol -> (if legacyParameters(symbol) then captureType(bound, td.tsym) else bound) - val tpe = declaredType(typeResolution(sign), scopedBindings, polarity) + val tpe = declaredType(typeResolution(sign), capturedBindings, polarity) if isByName(td) then tpe.instantiate(instances) else tpe val result = resultSignature(td).map(signature(_, positive)) if td.sign.nonEmpty && (td.k is syntax.Fun) && !td.flags.hasResultAnnotation && !isByName(td) then @@ -1354,8 +1361,12 @@ class NewResolver: val context = ExitMark(ResolutionBoundary(cls.sym), S(site), NoMarks) val elements = new TypeResolution(source, messages => resolError(source, messages)) elements.publish(TypeShape.Parameter(param, param.inferenceHost)) - val array = NominalInstanceView(cls, Map(param -> declaredType(elements, Map.empty)), implicitParent(cls))(N)(this) - val shape = MarkedShape(array, context) + // A nominal interface lives at the literal's use site. Its element + // reference points back into the allocation; putting the allocation exit + // on the whole interface would also exit a selected method's class scope + // twice, once here and once in NominalInstanceView.getMemberImpl. + val element = transportType(declaredType(elements, Map.empty), context :: Nil) + val shape = NominalInstanceView(cls, Map(param -> element), implicitParent(cls))(N)(this) rstate.mutableArrays(new Identity(source)) = shape listenTerm(underlying): tuple => listenArrayElements(tuple): element => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 93d95231c8..a24804fb47 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -204,6 +204,10 @@ final class NewResolverState private ( new Cache(inherited.map(_.selfShapes), identity) val defnShapes: Cache[DefinitionSymbol[?], DefnShape] = new Cache(inherited.map(_.defnShapes), identity) + // Immutable source metadata recorded by legacy elaboration. Consumers recover + // captures without modifying the exporter's terms or resolving names again. + val legacyReferenceCaptures: Cache[Identity[Term], Ls[AnyDefinitionSymbol]] = + new Cache(inherited.map(_.legacyReferenceCaptures), identity) val typeInterpretations: Cache[Identity[Term], TypeResolution] = new Cache(inherited.map(_.typeInterpretations), identity) // Recorded before elaborating a declaration's members, including in legacy diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 5eb59db8b3..875aeababa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -53,9 +53,10 @@ object ResolutionBoundary: case ctor: ClassCtorSymbol => ctor.associatedCls case symbol => symbol) -// Scanning every new mark's tail makes chain construction quadratic in its depth. -// Enable these invariant checks only when debugging mark propagation. -private val checkMarkPaths = false +// Scanning each new tail makes chain construction quadratic in its depth. +// Keep the checks enabled so missing scope transfers fail at their source instead +// of allowing repeated boundaries to accumulate in contextual reference keys. +private val checkMarkPaths = true /** A reduced lexical path: entries followed by exits, stored outermost first. * Exiting cancels the leading entry when their sites agree (an absent site is diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 2a19e5aa80..f5226bb9b8 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -150,6 +150,20 @@ callid1(Predef) //│ | listen: trm = [1] //│ | listenTerm: trm = 1 //│ | listen: trm = 1 +//│ | .exit InstanceShape(member:Num⁰) (↘⟨class:Int⁰⟩ϵ) +//│ | | .exit InstanceShape(member:Num⁰) (ϵ) +//│ | | = InstanceShape(member:Num⁰) +//│ | = MarkedShape(InstanceShape(member:Num⁰), ↘⟨class:Int⁰⟩ϵ) +//│ | .enter InstanceShape(missing) (↘⟨class:Int⁰⟩ϵ) +//│ | | .exit MarkedShape (InstanceShape(missing), class:Int⁰, ) +//│ | | = MarkedShape(InstanceShape(missing), ↗⟨class:Int⁰⟩ϵ) +//│ | | .enter MarkedShape(InstanceShape(missing), ↗⟨class:Int⁰⟩ϵ) (ϵ) +//│ | | = MarkedShape(InstanceShape(missing), ↗⟨class:Int⁰⟩ϵ) +//│ | = MarkedShape(InstanceShape(missing), ↗⟨class:Int⁰⟩ϵ) +//│ | .exit NominalInstanceView(class:Num¹) (↘⟨class:Int⁰⟩ϵ) +//│ | | .exit NominalInstanceView(class:Num¹) (ϵ) +//│ | | = NominalInstanceView(class:Num¹) +//│ | = MarkedShape(NominalInstanceView(class:Num¹), ↘⟨class:Int⁰⟩ϵ) //│ | introShape: intro = Lit(IntLit(1)) //│ | .exit IntroShape(1) (ϵ) //│ | = IntroShape(1) From af5e9cc61df60dd9c1e5a663cbf9ae8bb246fdec Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 13:52:20 +0800 Subject: [PATCH 120/228] Consolidate resolution documentation around current contracts and deferred work --- doc/new-resolution-design.md | 184 +-- doc/new-resolution-future-work.md | 103 ++ doc/new-resolution-regular-types.md | 17 +- doc/new-resolution-suite-migration.md | 122 +- doc/new-resolution-type-value-flow.md | 1515 ++++--------------------- doc/reference.md | 58 +- 6 files changed, 478 insertions(+), 1521 deletions(-) create mode 100644 doc/new-resolution-future-work.md diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 40cadbcf48..28ab0ffc6b 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -33,106 +33,38 @@ exported mapped arrays and imported generic functions and methods. ## Captures and generic flow -`InstanceShape` retains a type reference at annotation boundaries; member lookup, -application, and destructuring obtain specialized interfaces through -`listenInstanceViews`. The distinction between supplying a type argument and -adding an ordinary bound, variance rules, and instantiating declared type parameters -once per definition and authoritative syntactic site are documented -in [Instance types and parameter constraints](new-resolution-type-value-flow.md). -The description below records current generic inference. Omitted generic arguments -use source-owned inference holes and retain live inference through partial -signatures. Recursive hole contexts, alias-body omissions, and missing nominal -member types still need the contextual reference work described in that document. - -Type-alias qualification and structural type-field projection introduce no value -scope. Alias captures forward type references, and structural fields retain their -written types without the synthetic boundaries used for value-field lookup. -The tested direct recursive aliases and finite parameter permutations now pass. -A regular alias that resets its argument to `Int` still grows unused binding -environments; non-regular expansion and class-local alias receiver contexts also -remain incomplete. - -Class bodies introduce lexical captures, and a class and its constructor share -one resolution boundary. A method's reference to an outer constructor must include -the enclosing instance boundary as well as the method boundary. Consuming a -reconstructed instance cancels the old instance exit against its capture, retaining -the fresh constructor exit. Explicit `new` uses the same constructor context for -arguments and subsequent parameter lists. - -Context fragments compose from the definition to its consumer; argument flow -traverses that composition in reverse. Capture paths have no truncation or depth -limit. The optional `checkMarkPaths` assertions in -[`Shape.scala`](../hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala) -detect repeated boundaries in either direction. They are disabled by default -because scanning every new mark's tail makes chain construction quadratic in depth. -`newres/RecursiveEnvironment.mls` and `ConstructorFieldRecovery.mls` exercise -recursive calls, nested captures, partial construction, and independent field results. - -Explicit and inferred function/constructor type arguments flow through the -corresponding type-parameter symbols with entry/exit marks. Explicit arguments -receive input constraints without adding value-argument shapes to their output -interface at ordinary call boundaries. Bodyless members of constructed instances -still use a positive-only conversion for supplied class arguments; the callback -input regression in `newres/ConstructorInstances.mls` records this limitation. -Inference also connects nested -nominal parameters, such as `Foo[A]` containing a `Box[A]`. Generic methods use -the same flow as free functions, including callback-result inference and curried -signatures. Declared callable shapes retain their type parameters. Functions and -constructors allocate explicit binders once per original definition and authoritative -syntactic site: explicit type application, by-name invocation, or otherwise the -first term application. The [instantiation-site rules](new-resolution-type-value-flow.md#finite-call-site-instantiation-and-marks) -require later applications and curried tails to retain that group. Ordinary -references to functions with parameter lists keep their schemes until instantiated. - -Nominal argument comparisons retain directed `ContextualType` endpoint pairs. -Invariant arguments install both directions, rather than copying expanded -candidates. `in`/`out` arguments use InvalML's input/output comparison rules; -written wildcards override declaration variance. Substitution selects a bound -argument's part at the occurrence's polarity before applying the enclosing -nominal declaration's variance. Deferred selections retain that choice for both -directions of later constraints; see the -[substitution rules](new-resolution-type-value-flow.md#substitute-at-the-occurrence-before-applying-argument-variance). -A parameter receives a symbolic -instance wrapper, and structured/concrete targets retain listeners for later -bounds. `newres/MutableArrays.mls` includes passing direct and recursive append -cases, including distinct callers through one stored function reference. -`TypeRelationTest` checks graph replay and consumer isolation directly. - -Generic definitions receive a distinct checking activation when their type -parameters are declared. Its `RigidTypeShape` witnesses enforce generic opacity -independently of visibility, exposure, or strict mode. Interface observation turns -a witness into an unknown interface, but call-site inference does not copy that -witness as a bound. Symbolic references remain available for substitution instead. - -Inline explicit binders in partially annotated functions use the same finite -definition/application-site allocation as complete callable signatures. The -shared body carries a flat substitution through deferred tuples, records, -callbacks, and closures. A source-flow event's body activation and its value's -caller-side type references remain distinct, including when recursion rebinds the -same original parameter. Contextual observations select compatible activations; -marks continue to distinguish enclosing callers of a shared inner site. See -`newres/ContextualInference.mls` for stored-function and deferred-field cases. - -Omitted generic arguments use `TypeShape.Hole`, a stable inference host for each -source type-use/formal-position pair. They infer through arguments, result -annotations, and ascriptions; an empty hole waits for evidence. Written fragments -continue to restrict the interface. Exposure checking contributes unknown values -to missing parts of external inputs, including callback results, without widening -written binders. `newres/InferenceHoles.mls` also retains unresolved cases for -recursive holes and omissions shared through an alias body. No hole creates a -call-site parameter instance. - -Standalone specializations of inferred functions retain their consumed group in -`SpecializedShape`; complete callable signatures remove their scheme after -instantiation. By-name functions and methods instantiate before observing their -result. Their explicitly supplied type arguments select the same invocation's -site, without also allocating a group for the underlying reference. Separate -definition signatures consume their binders too; independently quantified result -annotations retain their own schemes. Argument arity is checked even for unused -specializations. `newres/StoredSpecializations.mls` covers stored aliases, inferred -record results, curried calls, and callback checking before a term application. -`newres/InstantiationSites.mls` checks shared mutation and implicit invocations; -see the [type-flow reference](new-resolution-type-value-flow.md#partial-application-and-explicit-specialization). +[Instance types and parameter constraints](new-resolution-type-value-flow.md) +specifies instance wrappers, input/output constraints, variance, canonical binder +instances, shared body views, and partial-signature inference. +[Regular structural types](new-resolution-regular-types.md) specifies normalization +and the conservative recursion restriction. Do not duplicate those algorithms in +member lookup or value-flow handling. + +Context fragments compose from a definition to its consumer; argument flow +traverses that composition in reverse. A class and its constructor share one +resolution boundary. Methods capture their enclosing instance scope as well as +the enclosing function scopes. Alias qualification and structural type-field +projection introduce no value boundary; modules introduce no invocation boundary. + +A normalized mark path contains entries followed by exits, with no repeated +lexical boundary in either direction. `Shape.scala` enables assertions for this +invariant. Checking each new tail costs linear time in its depth; it must not be +replaced by truncating or widening paths. In particular, wildcard exit followed +by wildcard entry is not an identity: it can discard a caller's activation ID. +Reference transport uses the same operations as ordinary value flow. + +Legacy references do not contain `Capture` syntax. Elaboration records the lexical +lookup path as immutable source metadata, and each consumer interprets it in its +private type graph. Both concrete nominal references and parameter references +need these paths. Guessing captures only for substituted class parameters misses +concrete member results and nested callback arguments. + +`newres/SpecializationCaptures.mls`, `RecursiveEnvironment.mls`, and +`ConstructorFieldRecovery.mls` exercise captured specialization, recursion, and +partial construction. `PrimitiveMembers.mls`, `Arrays.mls`, `GenericMethods.mls`, +and `MutableArrays.mls` exercise legacy signature captures and array member paths. +Receiver reconstruction and omitted-argument context precision are +[deferred improvements](new-resolution-future-work.md). ## Declared interfaces and exposure checking @@ -141,10 +73,8 @@ Generic aliases and inherited declared interfaces substitute their arguments; declared member selections carry those contexts without reading implementation value flow. An unannotated member read through a declared interface produces an unknown shape, rather than consulting its initializer or method body. -This is current behavior pending the reviewed partial-signature design: missing -member types will use the selected declaration's contextual inference graph, -while nominal annotations continue to restrict the visible member set. Override -compatibility must be checked before exposing inferred dispatch results. +Inferring these missing member types is a [deferred improvement](new-resolution-future-work.md#inferred-member-signatures) +that also requires override compatibility checks. Callback parameter types constrain implementation parameters, and callback results constrain inferred type arguments. `newres/DeclaredTypes.mls` covers these paths, separate signatures, tuple constraints, and distinct generic instantiations. @@ -180,34 +110,22 @@ order, last-write-wins lookup, computed-key uncertainty, and property identities across imports. See `newres/NamedFields.mls`, `ImportedNamedFields.mls`, and `RecordInterfaces.mls`. -Tuples preserve zero-based projections and expose the builtin Array class as -their parent. For example, `[First(1), Second(2)]` has a `First` value at index 0 -and a `Second` value at index 1. An Array operation such as `map` can read either -element, so its callback receives both candidate shapes. - -For a mutable literal, statically resolved element reads listen to the builtin -`Array[T]` type parameter's symbol. The initializer and later indexed assignments, `fill`, `push`, -and `unshift` send element shapes to that symbol. For example, starting with -`mut [First(1)]` and then pushing `Second(2)` makes both `First` and `Second` -candidates for each statically resolved element read. Overwriting or removing an element does not -remove its shape from these candidates. Resolution does not track which shape -belongs at which index or how long the array is. Each candidate is checked when -resolving an operation on an element; the presence of a `First` candidate cannot -justify accessing a `First`-only member when `Second` is also a candidate. - -The symbol is shared, but its shapes carry context marks that distinguish array -allocations and enclosing function calls. This keeps writes to separate arrays -from affecting each other's element reads. The cache in `NewResolverState` stores -the array's shape before subscribing to its initializer, allowing empty arrays to -receive writes and recursive arrays to refer to themselves. Numeric projections, -spreads, patterns, and callbacks all receive the element shapes through `T`. - -An explicit element type restricts which members resolution may use. If an array -is viewed as `Array[Base]`, inserting a `Child` that extends `Base` does not make -`Child`-only members accessible through that view. Resolution uses `Base`'s -declared members, regardless of the inserted value's more specific shape. In -`Array[A]`, where `A` is a type parameter, element reads instead receive the shapes -inferred for `A`. +Tuples preserve zero-based projections and expose the builtin Array interface. +An Array callback receives candidate shapes from every element. The +[language reference](reference.md#10-arrays) specifies mutable-array behavior and +the restriction imposed by explicit element annotations. + +For a mutable literal, all element reads and writes use the builtin `Array[T]` +parameter's symbol. Marks distinguish allocations and enclosing calls. Cache the +nominal view before subscribing to the initializer so empty arrays can receive +writes and recursive arrays can refer to themselves. Numeric projections, spreads, +patterns, and callbacks all use the same element endpoint. + +The nominal interface is located at the literal's use site. Its element reference +carries the allocation exit; the interface itself does not. Member lookup already +exits the nominal class scope. Adding the allocation exit to the entire view would +therefore cross that boundary twice. Initializer shapes enter the allocation +context before reaching the parameter host. When an array is returned or otherwise exposed to callers, `InterfaceExposure` also follows its element shapes. A function stored in that array must be checked @@ -215,7 +133,7 @@ for calls from outside the compilation unit, just like a directly returned function. `newres/MutableArrays.mls` and `CompilerTest` cover element flow, separation between calls and importing compilation units, and exposed functions. See the [migration worklist](new-resolution-suite-migration.md) for the remaining -`splice` and generic-parameter write propagation gaps. +`splice` contract and storage-reassignment gaps. When an array's element shape is unknown, indexed access produces an unknown shape; that does not authorize arbitrary member access on the result. diff --git a/doc/new-resolution-future-work.md b/doc/new-resolution-future-work.md new file mode 100644 index 0000000000..c22d41ea90 --- /dev/null +++ b/doc/new-resolution-future-work.md @@ -0,0 +1,103 @@ +# Deferred resolution design work + +These improvements are deferred, not completion blockers for the current +implementation batch. They require design review before implementation. Current +contracts are in [instance types and parameter constraints](new-resolution-type-value-flow.md); +remaining suite ports are in the [migration worklist](new-resolution-suite-migration.md). + +## Receiver reconstruction + +```mlscript +class Box[T](val item: T) with + fun copy() = new Box[T](item) +``` + +Reconstruction needs to retain the old receiver's view of `T` separately from the +new constructor's view. Distinct receiver calls currently lose the returned field's +interface. The constructor-value form `Box[T](item)` has the same precision gap. +The unannotated control, `class Box(val item)` with `copy() = new Box(item)`, mixes +field candidates from distinct receivers. This therefore affects ordinary value +inference as well as explicit type parameters. + +A correction must preserve both receiver and new-allocation contexts using the +ordinary marks algebra. Correcting only a binder substitution does not establish +receiver separation. Regressions: `newres/ConstructorInstances.mls`. + +## Inferred member signatures + +Missing types on selected members should use the selected declaration's contextual +inference graph, consistently with partial function signatures: + +```mlscript +class Item(val value: Int) +class Box with + fun item() = Item(1) +private fun read(x: Box) = x.item().value +``` + +The desired result is to infer `Box.item`'s result and permit `.value`. The current +nominal view does not expose that missing result type. The same improvement applies +to missing parameter, constructor-field, and value-member types. + +Keep the actual receiver's contextual flow on these inferred connections while +using the written nominal type for lookup. A `Base` annotation must not expose +`Child`-only members. Annotated portions retain their written types, and selected +members retain their own schemes, including overloaded term alternatives. + +Override compatibility is part of this work: a base implementation returning +`Item` cannot justify `.value` on every dynamic dispatch result if an override can +return a different interface. Check overriding inputs and outputs against the +inherited interface before completing targets or exposing inferred dispatch +results. Preserve imported reference completion and consumer isolation. +Regressions: `newres/DeclaredTypes.mls` and `PartialSignatures.mls`. + +## Omitted-argument contexts + +Missing generic arguments are ordinary inference holes. Their current substitution +can lose caller separation, even without recursion: + +```mlscript +class First(val first: Int) +class Second(val second: Int) +class Pair[A, B](val value: [A, B]) +private fun second(pair: Pair[Int]) = pair.value.1 +[second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] +``` + +Both results receive both candidates and produce missing-member diagnostics. +Removing the parameter annotation avoids this template-substitution path and +preserves ordinary argument flow. Related cases use a shared alias-body omission +or a mutable `Array` annotation, with and without recursion. + +The hole belongs to the annotation inside `second`. Following the captured `Pair` +template rebases its argument outward with a wildcard exit, then projects it back +with a wildcard entry. Applied to incoming bounds, the exit consumes the call's +identified entry; re-entry cannot restore that ID. This is the specified marks +behavior, not permission to cancel those operations on references. + +A possible representation separates the template endpoint from the supplied +argument environment. Following a formal would use the argument's own endpoint; +following a free reference would use the template's context. This remains an open +design: verify deferred member projection, both constraint directions, polarity, +recursive aliases, and finite canonical reference keys. Dropping template transport +loses enclosing binders, while allocating per-call hole instances violates ordinary +inference. Regressions: `newres/InferenceHoles.mls`, `PartialSignatures.mls`, and +`AnnotationContexts.mls`. + +## More precise regularity analysis + +The current conservative check rejects some finite structural types. The +[regularity reference](new-resolution-regular-types.md#deferred-refinement-for-later-review) +contains two refinements for later review: input/output dependency analysis used +also for environment projection, and accounting for correlations between recursive +arguments after Boolean normalization. Neither is required for the current +conservative implementation. + +## Whole-graph convergence audit + +Finite binder allocation, source dependency analysis, and relation replay have +local bounds. A complete argument must also bound accepted contextual reference +keys, nested environments, and formula atoms, then establish listener convergence. +Retain marked parameter references and shared recursive edges; do not expand live +bounds into fresh type trees. Test delayed delivery, mutually recursive calls, +changing compound arguments, and independent consumers at the graph level. diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index 5b0faf9ef2..00af29a7e1 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -4,7 +4,7 @@ This is an internal reference for structural recursion in new resolution. The instance-wrapper and variance semantics are specified in [instance types and parameter constraints](new-resolution-type-value-flow.md). -## Requirement and current status +## Regularity requirement A structural type must unfold into a finite graph of distinct type components. Check regularity after reducing aliases and normalizing unions/intersections; @@ -31,7 +31,7 @@ or correlations between successive substitutions. The diagnostic says that the current check does not support the recursive type; it does not claim to prove non-regularity. The more precise design below is deferred for later review. -## Implemented normalization +## Normalization `TypeFormula` represents a union of intersections as a set of sets of interpreted references. It applies associativity, commutativity, idempotence, distribution, @@ -47,9 +47,9 @@ A | (A & Array[A]) = A `TypeShape.Combined` retains the formula as one type. In particular, supplying a union still creates one instance wrapper, and a negative obligation against that -union stays whole. Member-interface observation can inspect its components, just -as it did for source unions and intersections. Normalization does not turn one -supplied union into several independently supplied types. +union stays whole. Member-interface observation can inspect its components, as +for source unions and intersections. Normalization does not turn one supplied +union into several independently supplied types. Formula atoms retain their lexical bindings, canonical binder instances, and ordinary marks. Different written references to the same unbound parameter and @@ -94,7 +94,7 @@ This does **not** by itself bound the atoms. Constructor growth, retained argume parts that never become observable, and the existing requirements on finite marked contexts remain separate obligations. -## Implemented conservative check +## Conservative check The check connects original formals through alias applications in the reachable source graph. An edge records whether the argument wraps its source formal in a @@ -250,9 +250,8 @@ requirements on the marks algebra remain in force throughout. `newres/TypeGraphTermination.mls` covers regular recursion, permutations, mutual resets, unused arguments, transparent and captured forwarding aliases, forward references, union/intersection saturation, Boolean-only recursion, and the variance -counterexample. Rejected constructor cycles and the two accepted precision -limitations use ordinary `:e` expectations; they no longer rely on stack overflows -or future-diagnostic expectations. +counterexample. Rejected constructor cycles and the two documented precision +limitations use ordinary `:e` expectations. `TypeFormulaTest` checks normalization against Boolean truth tables and repeated alternating substitution. `TypeRelationTest` checks a thousand repeated reductions, diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 7f3d59f239..0307887d22 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -75,7 +75,8 @@ re-resolving imported nodes or querying erased types before erasure. Many fixtures also need source interfaces: for example, `QuoteExample.bind` calls an unannotated callback, and Nofib helpers expose comparators and printers. -The inventory below separates these from compiler and prelude gaps. +Check each fixture's public API and its consumers when porting it; the source +headers identify the remaining legacy fixtures. ### Shape propagation and capture precision @@ -83,35 +84,23 @@ The inventory below separates these from compiler and prelude gaps. treated as the generator body's return value, leaving `.next` unresolved. Model the iterator result produced by lowering; use `codegen/Generators` as the worksheet acceptance case. -- **Mutation and control flow:** mutable-array reads use the element shapes - collected from the initializer and subsequent writes, as described in the - [resolver notes](new-resolution-design.md). Tracking individual positions and - lengths is out of scope. Complete the - [type-argument constraint design](new-resolution-type-value-flow.md), including - supplied member-input - constraints, reconstructed receiver contexts, inferred missing member types, - [regular structural interfaces](new-resolution-regular-types.md), and the whole - graph's termination bound. Alias and Boolean argument normalization is implemented; - a conservative regularity check rejects constructor-bearing dependency cycles. - Refinements using input/output argument parts and relationships between recursive - arguments are deferred; the current diagnostic also rejects some regular types. - Concrete outstanding cases are in - `newres/InstantiationSites.mls`, `StoredSpecializations.mls`, `ConstructorInstances.mls`, - `InferenceHoles.mls`, `PartialSignatures.mls`, and `TypeGraphTermination.mls`. - Captured explicit specializations now bind arguments in the application scope; - `SpecializationCaptures.mls` covers nested function and by-name captures. - Reconstruction still mixes unannotated field flows as well as losing annotated - receiver contexts, so its remaining fix must cover ordinary value inference too. - Loss of caller separation through omitted type arguments is an accepted current - limitation; defer that substitution redesign while completing the other cases. - Decide how reassignment affects mutable storage's inferred interface, including - across compiled worksheet blocks. `newres/MutationFlow.mls` records a reassigned - array still checked against its initializer's tuple length; accumulating both - shapes would still reject valid later indexing. `codegen/SetStmt` also lacks - argument flow through its update callback. Member-variable definitions still - need work. Handler inference needs separate flows for the receiver, values - passed to resumptions, and abortive results (`newres/HandlerResults.mls` and - `codegen/ScopedBlocksAndHandlers`). Agree these designs before implementation. +- **Mutation and control flow:** mutable arrays propagate initializer and write + shapes through their element parameter. Position and length tracking is out of + scope. [Type-flow design improvements](new-resolution-future-work.md) are deferred: + receiver reconstruction, inferred missing member types through nominal views, + omitted-argument caller separation, and a more precise regularity check. These + do not block the current implementation batch. The whole-graph convergence audit + remains open; local allocation and replay bounds are documented in the + [type-flow reference](new-resolution-type-value-flow.md#canonical-references-and-termination-obligations). + For broader mutation migration, decide how reassignment changes storage's + inferred interface, including across worksheet blocks. `newres/MutationFlow.mls` + records a reassigned array checked against its initializer's tuple length; + accumulating both shapes would still reject valid later indexing. + `codegen/SetStmt` needs argument flow through its update callback. Member-variable + definitions also need work. Handler inference needs separate flows for the + receiver, values passed to resumptions, and abortive results + (`newres/HandlerResults.mls` and `codegen/ScopedBlocksAndHandlers`). Review these + designs before implementation. - **Captured activations:** preserve activation identity through captured functions, constructor aliases, reconstruction of the same class, and partial construction. Existing regressions include `CtxSens`, `ValCtxSens`, @@ -169,72 +158,11 @@ recursive UPS matchers. Preserve both runtime results and matcher structure. check the lost undefined-binding use sites in `ups/UpsBugsBacklog` and `ups/syntax/MixedParameters` without changing the selected symbol. -Retry remaining legacy worksheets against the current compiler and prelude before -treating a previously observed diagnostic as an implementation gap. - -## Deferred compilation fixtures - -Paths are relative to `hkmc2/shared/src/test/mlscript-compile/`. These are the -last recorded obstructions, not a fresh failure audit or an exhaustive diagnosis. -Retry each fixture before implementing a fix: subsequent compiler and prelude -changes may have removed its first blocker. Deferred fixtures retain legacy -resolution; imports that exchange options must use `LegacyOption` consistently. - -| Main fixture | Observed obstruction | -| --- | --- | -| `Benchmark.mls` | Exposed `suite.run` and callback interfaces. | -| `Block.mls` | `Str.replaceAll`, `Any.toString`, and pattern-value interfaces. | -| `CSP.mls` | Quasiquote type selections and wildcard opens. | -| `CachedHash.mls` | Retry candidate: implicit Object inheritance supplies the required `this.toString` interface. | -| `Char.mls` | `AnyChar` loses the string shape before its `length` guard. | -| `FingerTreeList.mls` | `Array.reduce` and numeric projection on an array rather than a fixed tuple. | -| `Iter.mls` | Exposed callbacks and iterator `next` interfaces. | -| `LazyArray.mls` | `null.next` and missing pattern-field shapes through mutation. | -| `LazyFingerTree.mls` | Exposed `xs.length` and pattern-field interfaces. | -| `MutMap.mls` | Exposed `m.underlying` and keyword-named `Map.set`. | -| `ObjectBuffer.mls` | Exposed `cls.size` and constructor interfaces. | -| `LegacyOption.mls` | Temporary copy for legacy consumers; delete when those consumers are ported. | -| `Predef.mls` | Exposed generic callbacks and rest-argument `.call`. | -| `QuoteExample.mls` | Exposed callback `k` in `bind`. | -| `QuoteExample1.mls` | Quasiquote wildcard-open references. | -| `Rendering.mls` | Nullable string flow, callbacks, and pattern-field interfaces. | -| `Runtime.mls` | `Map.set` and nullable `contTrace` flow. | -| `Shape.mls` | Pattern-derived receivers for `join`, `every`, `length`, and `name`. | -| `Stack.mls` | Exposed `arr.length` and predicate callbacks. | -| `Term.mls` | `Map.set` and generic pattern/value interfaces. | -| `TreeTracer.mls` | Exposed `message.split` and pattern-reference forms. | -| `XML.mls` | Exposed `value.toValue`. | -| `ups/EvaluationContext.mls` | Exposed `target.freeVars` and context interfaces. | - -| Application fixture | Observed obstruction | -| --- | --- | -| `apps/Accounting.mls` | Array callback arity, `reduce`, and receiver interfaces. | -| `apps/CSV.mls` | Retry with the mutable-array element flow; regexp/nullish result interfaces and `at` results still need checking. | -| `apps/parsing/Extension.mls` | Receivers for `display`, `add`, and `extendChoices` have no resolved target. | -| `apps/parsing/Keywords.mls` | Legacy `Parser` consumers cannot inspect its new-resolution signatures. | -| `apps/parsing/Lexer.mls` | Missing automatic contextual argument insertion and opened binary `~` resolution. | -| `apps/parsing/ParseRule.mls` | Flow through private module/class `let` bindings and imported mutable maps. | -| `apps/parsing/ParseRuleVisualizer.mls` | Imported JS railroad API lacks member interfaces. | -| `apps/parsing/Parser.mls` | Synthetic selections from imported legacy forms reach new resolution. | -| `apps/parsing/Rules.mls` | An `extendChoices` receiver has no resolved target through mutable map lookup. | -| `apps/parsing/Test.mls` | `flags.has` and `tracer.reset` lose shapes through tuple/import flow. | -| `apps/parsing/Token.mls` | Legacy contextual-argument consumers cannot read its new-resolution signatures. | -| `apps/parsing/Tree.mls` | Chained `JSON.stringify(...).slice(...)` lacks a result interface; also coupled to legacy consumers. | -| `apps/parsing/TreeHelpers.mls` | Coupled to `Tree` and its consumers. | -| `apps/parsing-web-demo/main.mls` | String callback interfaces, dynamic receiver flow through aliases/mutation, and tuple-bound `example.name`. | - -| Nofib fixture (under `nofib/`) | Observed obstruction | -| --- | --- | -| `NofibPrelude.mls`, `ansi.mls`, `atom.mls`, `awards.mls`, `constraints.mls`, `cse.mls`, `fish.mls`, `integer.mls`, `lambda.mls`, `lastpiece.mls`, `life.mls`, `mate.mls`, `minimax.mls`, `para.mls`, `power.mls`, `pretty.mls`, `primetest.mls`, `scc.mls`, `secretary.mls` | Exposed callback parameters need callable interfaces. | -| `eliza.mls`, `knights.mls`, `sorting.mls` | Unannotated string receivers and callbacks. | -| `circsim.mls` | Exposed record receivers such as `p.pid` and `p.compType`. | -| `cryptarithm2.mls` | Callable interface for an unannotated constructor field. | -| `cichelli.mls` | Callback interfaces. | -| `treejoin.mls` | Pattern flow. | -| `lcss.mls` | Function result has no interface for `toString`. | - -The remaining WASM fixture, `wasm/Wasm.mls`, needs interfaces for -`WebAssembly.Instance.exports` and its exposed `wasmInst.imports` receiver. +Retry legacy worksheets and compilation fixtures against the current compiler and +prelude before diagnosing a blocker. Keep deferred fixtures on legacy resolution; +imports exchanging options must use `LegacyOption` consistently. The WASM fixture +`wasm/Wasm.mls` needs interfaces for `WebAssembly.Instance.exports` and its exposed +`wasmInst.imports` receiver. ## Validation and completion gates @@ -251,5 +179,5 @@ Follow the [repository test workflow](../README.md#running-the-tests-1): without new failure suppressions or lost negative diagnostics. Remove `LegacyOption` once its consumers use the new interfaces consistently. -Update the counts and blocker inventory after each retained batch. Test totals +Update the counts and remaining work after each retained batch. Test totals include configurations and regression tests, so they are not migration counts. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 083659a98b..431bef3163 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -1,286 +1,128 @@ # Instance types and parameter constraints -`InstanceShape(T)` describes an instance of the type `T`. Types remain in the -`TypeShape`/`DeclaredType` graph. The instance wrapper replaces the earlier -nominal-only representation at annotation boundaries: parameter and result -annotations, ascriptions, generic arguments, and annotated tuple fields retain -a type reference until an operation requests its interface. +This internal reference describes the current type-flow representation in new +resolution. User-facing rules are in the [language reference](reference.md#resolution-interfaces). +[Deferred improvements](new-resolution-future-work.md) cover receiver reconstruction, +inferred member signatures, omitted-argument contexts, and regularity precision. -## Review outcome - -The following semantic decisions are settled: - -- Wrap instance values, retaining references to their annotated types. Supplying - a type argument affects both its input and output uses; an ordinary value - argument contributes a lower bound. -- Instantiate explicit binders once per original definition and authoritative - instantiation site: an explicit term-level type application, an implicit - by-name invocation, or otherwise the first term application. Retain that group - on the resulting value. Reuse it during recursion and use marks to distinguish - enclosing activations of the same site. -- Infer every missing signature part through ordinary marked flow, including - holes within annotations and omitted generic arguments. These positions do not - introduce quantified binders or receive fresh symbols at calls. For now, accept - the [omitted-argument context limitation](#omitted-argument-context-regression): - these holes can lose caller separation. Fixing their substitution contexts is - deferred; it does not block the remaining explicit-argument implementation. -- Apply the same rule to selected members' missing parameter, result, and field - types through nominal annotations. Retain the receiver context and the selected - declaration's member set; check overrides against that interface. -- Follow InvalML's declaration/use-site variance and occurrence-sensitive - substitution, including nested function types. -- Mutable arrays collect element shapes in their element type parameter under - marks. Tracking positions and lengths is outside this design. - -The implementation plan is in [constraint propagation and implementation -order](#constraint-propagation-and-implementation-order). The -[instantiation-site implementation](#partial-application-and-explicit-specialization) -uses the existing finite maps to parameter instances for deferred value views: -an explicit type application already provides their site and parameter group. The [distinction between type-reference operations and -value-scope crossings](#type-reference-scope-audit) is approved. Recursive reference -graphs must preserve caller and receiver contexts without growing binding environments. The variance -substitution rules below are implemented for declared type views. - -Require structural types to have a finite recursive representation, after alias -and union/intersection normalization. The [regularity design](new-resolution-regular-types.md) -describes the implemented normalization and conservative regularity check. A more -precise check using input/output dependencies and correlated substitutions is deferred. -Rejecting unsupported recursion and sharing accepted recursive references are separate -obligations; neither follows from the bound on call-site symbols. - -The finite call-site symbol bound alone is not a termination proof for the whole -resolver. The regularity check rejects constructor-bearing dependency cycles before -structural observation, including some finite cases. Fully supplied alias applications -are reduced and Boolean combinations normalized before their arguments become part -of recursive reference keys. Contextual references preserve class-local aliases -and enclosing binders through nominal member projections. Dependency-based binding projection accepts regular -argument resets, including compound constants and recursively unused arguments. -The [fixed-point conditions](#fixed-points-and-implementation-checks) remain completion gates. - -## Implementation status - -Instance wrappers and preservation of quantified signature binders and bounds -are implemented. Explicit type applications consume schemes before term arguments -are supplied. By-name references and selections consume their definition's binders -before observing its result; explicit arguments choose the site without also -instantiating the underlying reference. Separate definition signatures follow the -same rule, while independently quantified result annotations retain their schemes. -Functions and constructors share the bounded binder-instance cache. Stored -specializations and curried tails retain their groups. Allocation-count tests -cover unused specializations, repeated calls, by-name methods through nominal -views, and distinct invocation sites. - -`DeclaredType.instances` carries a flat substitution through declared components. -Shared inferred bodies retain substitutions through deferred tuples, records, -callbacks, and closures. Nominal argument comparisons retain both endpoint -references and apply declaration/use-site variance, including substitution inside -nested function and nominal types. The explicit-binder recursive array acceptance -cases pass. Supplied function arguments receive input obligations while retaining -their declared output interface. - -Constructed receivers retain symbolic type-parameter endpoints for declared -members, so supplied callback types receive input obligations as well as providing -output interfaces. Suppliedness is recorded on the canonical parameter instance; -marks select its enclosing activation, rather than determining whether it is -supplied. Reconstructed receiver contexts, inferred nominal member interfaces, -and general recursive alias environments still need integration. Omitted arguments -use source-owned inference holes. Omitted-argument rebasing currently loses caller -identity, including without recursion; distinguishing two omissions inside a shared -alias body also remains unfinished. See the context regression below. The -implementation order below remains the full design, not a claim that all its parts -are complete. -See the [resolver notes](new-resolution-design.md) -for current behavior and the [migration worklist](new-resolution-suite-migration.md) -for remaining ports. - -## Type arguments are different from instance bounds +## Type arguments and instance bounds Supplying `Int` as the type argument for a contextual parameter `A` affects both -positive and negative uses of `A`: +its input and output uses: ```text -A has the supplied type Int: - L <: A requires L <: Int A <: U requires Int <: U ``` -An ordinary integer argument to a parameter `x: A` contributes only a lower -bound. It does not supply `Int` as the meaning of every occurrence of `A`. -For `pair[A](x: A, y: A)`, an integer and a string therefore contribute two lower -bounds; neither argument must satisfy the other's type. - -If two distinct type values, `Int` and `Str`, reach the same activation of `A`, -each bound must apply to both. `L <: A` requires both `L <: Int` and `L <: Str`; -`A <: U` requires both `Int <: U` and `Str <: U`. Supplying the single type value -`Int | Str` is different: its complete type expression remains the constraint -target. Decomposing a union in positive position does not justify decomposing it -conjunctively in negative position. - -Concrete mismatches such as `Str <: Int` may remain quiet during this migration. -The absence of a loud diagnostic does not authorize dropping constraints before -they reach the relevant type or parameter. - -These rules concern inputs/outputs, or equivalently lower/upper bounds. Function -parameters reverse polarity and function results preserve it. Array operations -are one application of the rules. +An ordinary integer argument to `x: A` contributes only a lower bound. For +`pair[A](x: A, y: A)`, integer and string arguments contribute two lower bounds; +neither argument must satisfy the other's type. -## Finite call-site instantiation and marks - -**Instantiate each definition's explicitly declared type parameters once per -syntactic instantiation site.** This includes binders in inline annotations and -separate signatures. A site need not contain a term argument list: +If two distinct supplied types `Int` and `Str` reach the same activation of `A`, +each obligation applies to both. Supplying the single type `Int | Str` instead +retains that complete type as the negative constraint target. Positive union +elimination does not justify splitting a negative union into conjunctive obligations. +Concrete mismatches such as `Str <: Int` can remain quiet during migration; this +does not permit dropping the corresponding constraints. -| Operation on an uninstantiated scheme | Authoritative site | -| --- | --- | -| Explicit type application `f[T]` | That type application | -| By-name invocation `make` or `obj.make` | That reference or selection, unless its enclosing type application supplies arguments | -| Ordinary function application `f(x)` | The first term application | -| Constructor application | Its type application if explicit; otherwise its first term application, or saturated zero-list `new` | - -This table concerns term-level instantiation. A type expression such as `Array[Int]` -inside an annotation does not invoke a computation or allocate a call-site group. -A reference to an ordinary function with parameter lists, such as `let g = f`, -does not invoke that function and may retain its uninstantiated scheme. - -Memoize a complete group of parameter symbols using this key: +## Instance wrappers and interface observations -```text -(original definition, authoritative syntactic instantiation site) - -> {original parameter -> instantiated parameter} -``` +`InstanceShape(DeclaredType)` describes an instance of a type. Parameter/result +annotations, ascriptions, and annotated fields retain this wrapper until lookup, +application, or destructuring requests an interface through `listenInstanceViews`. +The type itself stays in the `TypeResolution`/`TypeShape` graph. A written interface +restricts observation even when more specific implementation values reach it. -After instantiation, carry the group on the resulting value. Neither passing that -value through an alias nor supplying later term arguments instantiates the same -scheme again. In `f[T](x)`, the type application owns the group; in `make[T]`, do -not instantiate once at the reference and again at the type application. A -separately quantified result scheme is a different scheme, not a reappearance of -the definition's consumed binders. +A `DeclaredType` contains: -Different sites receive different symbols. Revisiting the same definition at the -same site reuses its group, including during recursion. The key must not contain -the supplied types, incoming bounds, marks, or an already instantiated definition. -Install the group before subscribing to constraints so reentrant propagation -finds it. The only permitted allocation resembling fresh inference variables is -this bounded instantiation; do not allocate another variable per flow, constraint -match, or recursive visit. +- A source `TypeResolution` node. +- Bindings from original formals to contextual argument references. +- A flat substitution from original binders to canonical call-site instances. +- The lexical polarity at which to interpret substitutions. -Symbols identify these instances; marks still identify the contexts in which -their bounds flow. A single call inside a helper shares its parameter instances -across the helper's callers, while enclosing marks distinguish those callers. -Keep the original lexical resolution boundaries and existing normalization; -instantiated symbols must not introduce additional scope boundaries. Mutable -array literals retain the builtin `Array` element parameter with allocation marks; -actual element shapes contribute bounds there. They need no fresh element variable. +An interface view does not replace these references with their current candidates. +Late constraints must remain connected to the same endpoints. Nominal views retain +symbolic parameter endpoints for constructed receivers, including supplied arguments. +Suppliedness is recorded on the canonical parameter instance; marks choose which +activation's supplied bounds receive an obligation. An input obligation does not +add its implementation shape to a supplied parameter's output interface. -Keeping a reference to an unresolved parameter is essential. Copying its current -positive candidates loses its negative uses and its identity as a type argument. -Subscriptions must also account for constraints or candidates arriving later. -Graph caches and listeners belong to the consuming `NewResolverState`; extending -a consumer must not mutate a prelude or an exporter's inference graph. +## Finite call-site instantiation and marks -This follows the finite-allocation idea in §3 of -[Tate's type-outference paper](https://rosstate.org/publications/outference/outference-tate-oopsla25.pdf): -label listeners introduce signature unknowns once per invocation site and nominal -label, reusing them as bounds arrive rather than expanding every concrete type. -Here the key uses the original definition and authoritative instantiation site. The -paper's formal calculus has monomorphic methods; its termination result does not -directly establish termination for our generic functions and marks. +Each explicitly declared binder is instantiated once per original definition and +**authoritative syntactic instantiation site**. Inline binders and separate +signatures follow the same policy: -## Instance wrappers and interface observations +| Operation on an uninstantiated scheme | Authoritative site | +| --- | --- | +| Explicit term-level type application `f[T]` | That type application | +| By-name invocation `make` or `obj.make` | The reference or selection, unless an enclosing type application supplies arguments | +| Ordinary application `f(x)` | The first term application | +| Constructor application | Its explicit type application; otherwise its first term application, or saturated zero-list `new` | + +An annotation such as `Array[Int]` is a type expression, not an invocation. +An ordinary reference to a function with parameter lists can retain its scheme. +A by-name reference has already invoked the computation: retaining its consumed +scheme and independently instantiating it at later uses would misrepresent shared +state returned by that invocation. + +The cache maps `(original definition or source scheme, syntactic site)` to the +complete group of parameter instances. It contains no supplied types, incoming +bounds, marks, or instantiated-definition identities. Install a group before +subscribing to constraints so reentrant observation finds it. Recursion reuses +the group; marks distinguish enclosing activations of a shared inner site. +No other observation, projection, or recursive traversal allocates fresh binders. +`App.resSym`, `New.resSym`, and reference/selection sites provide stable identities; +`typeApplicationSite` memoizes one identity per original `TyApp`. Class and +constructor views use the same original owner. An anonymous polymorphic annotation +uses its source scheme, and each alternative of an overload has its own owner. + +`TypeShape.Polymorphic` and `DeclaredTypeParameter` retain the original binders and +their bounds. Apply the site's substitution to bounds, inputs, results, and nested +callbacks before interface expansion. Enclosing binders remain lexical captures, +not binders of the nested definition being instantiated. -`InstanceShape` holds a `DeclaredType`, including its lexical bindings. -`listenTypeInstances` supplies this wrapper; `listenInstanceViews` interprets it -for an operation such as selection, application, or destructuring. The cached -`listenTypeViews` observations reuse the specialized nominal, callable, record, -and tuple interfaces. `NominalInstanceView` is the nominal member-lookup view -previously called `NominalTypeShape`. +### Partial application and explicit specialization -For example, an instance viewed as `Base` exposes `Base`'s declarations even if -the implementation is a `Child`. Interpreting a function type exposes its declared -domain and result. Interpreting a union for member lookup can observe each -alternative, while transporting its wrapper preserves the original union node. +`f[T]` consumes its scheme even before a term application occurs. `SpecializedShape` +retains the group for an inferred function; an instantiated complete callable view +removes that scheme. Stored aliases and remaining curried lists reuse the group. +A separately quantified result retains its independent scheme. -`inferTypeArguments` retains wrappers when contributing a parameter bound and -observes them for structural comparison. For an explicitly supplied argument, -an input obligation follows its retained type reference instead of becoming an -additional output candidate. Each distinct supplied type receives the obligation; -a supplied union stays whole. The implementation records suppliedness by canonical -parameter instance. One authoritative definition/site always has the same supplied -positions, even when nested comparisons observe it from different scopes. Only -positions with an actual argument are marked supplied. Marks still select the -applicable bounds and filter other enclosing activations; they do not determine -whether a supplied slot is fixed. The metadata is consumer-owned and does not add -symbols or contextual cache keys. +For ordinary explicit applications, lexical captures are applied to the callable +before binding caller-supplied arguments. By-name invocation needs the arguments +before observing its result: the resolver records the reference's captures and +rebases those arguments through the ordinary mark operations into the invocation. +It records that invocation so its result is not specialized a second time. +Arity checks also apply to unused specializations. -## Variance rules +## Variance and substitution -Follow InvalML's argument interpretation in -[`typeAndSubstType`](../hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala) -and argument comparison in -[`constrainArgs`](../hkmc2/shared/src/main/scala/hkmc2/invalml/ConstraintSolver.scala). -Its [`TypeArg`](../hkmc2/shared/src/main/scala/hkmc2/invalml/types.scala) exposes an -input part (`negPart`) and an output part (`posPart`): +Follow InvalML's [`typeAndSubstType`](../hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala) +and [`constrainArgs`](../hkmc2/shared/src/main/scala/hkmc2/invalml/ConstraintSolver.scala). +Its [`TypeArg`](../hkmc2/shared/src/main/scala/hkmc2/invalml/types.scala) supplies +input and output parts: | Argument | Input part | Output part | | --- | --- | --- | | `S` | `S` | `S` | | `in T` | `T` | `Any` | | `out U` | `Nothing` | `U` | +| `in T out U` | `T` | `U` | -Thus `S` means `in S out S`. The internal pair can also represent a written -`in T out U`. For an unqualified argument, a declaration's `in` or `out` annotation -selects the corresponding form. An explicitly written use-site wildcard supplies -its own parts, as it does in InvalML; it is not combined by guessing a variance. - -For actual argument `a` and expected argument `b`, require: - -```text -a.output <: b.output -b.input <: a.input -``` - -A plain invariant `Array[Int]` against `Array[A]` therefore retains both uses of -the element type. `Array[out A]` admits only the output connection; `Array[in A]` -admits only the input connection. Substitution into member types must select the -appropriate part at each polarity, including nested arrows. Copy InvalML's -variance semantics, not its fresh inference-variable implementation. - -`TypeShape.Wildcard` retains the written input/output nodes. Nominal bindings apply -declaration variance only to unqualified arguments. Interface observation follows -the output part; an instance bound constrains the input part. Function constraints -already reverse domains, including callbacks nested in member inputs. Argument -comparison installs the two directed `ContextualType` relations above. Missing -parts use shared `Top`/`Bottom` nodes, independently of inference holes. -`newres/TypeArgumentVariance.mls` covers these paths; applying the same machinery -to standalone function/constructor specialization remains part of the explicit -type-argument integration work. - -Synthesized declaration variance uses `TypeShape.Argument`, whose parts retain -complete contextual type references. It must not transplant an argument's syntax -node into the alias or class's new binding environment: an outer argument with -the same source binder can then resolve back to its own wrapper. Reusing the -argument reference and normalizing repeated synthesized wrappers bounds this -construction. The recursive covariant `Tree` regression exercises this invariant. +Declaration variance applies to an unqualified argument. A written wildcard +supplies its own parts and overrides declaration variance. Comparing actual `a` +with expected `b` installs `a.output <: b.output` and `b.input <: a.input`. +Missing wildcard parts are actual top/bottom types, not inference holes. ### Substitute at the occurrence before applying argument variance -There are two distinct steps in InvalML's `typeAndSubstType`: - -1. Interpret an argument expression at the occurrence's polarity. A reference to - a parameter bound to `in L out U` selects `U` at positive polarity and `L` at - negative polarity. Function domains and written wildcard input parts reverse - this polarity; function results and wildcard output parts preserve it. -2. For an unqualified nominal argument, apply the formal parameter's declared - variance to that interpreted type. An invariant formal uses the same result - for both parts. A written wildcard instead interprets its two explicit parts - at their respective polarities and supplies its own variance. - -In particular, the formal's `in` annotation does not itself choose the input -part of a substituted parameter. InvalML first evaluates `mono(t, pol)`, then -constructs the argument according to `tp.vce`. Nor should selecting a supplied -function type re-substitute it in the enclosing member's environment: it already -denotes a type in the supplier's context. +First interpret an argument expression at its lexical occurrence polarity. A +formal bound to `in L out U` selects `U` positively and `L` negatively. Function +domains and written wildcard input parts reverse polarity; results and structural +fields preserve it. Then apply the enclosing formal's declaration variance to the +interpreted argument. An invariant formal uses that one type for both parts. For example, with `Child <: Base`: @@ -288,1038 +130,149 @@ For example, with `Child <: Base`: class Box[T](val item: T) class Receiver[T] with fun accept(box: Box[T]): () = () -// On Receiver[in Child out Base], accept expects Box[Child]. -``` - -The `T` in the method input has negative polarity, so substitute `Child` first. -The invariant `Box` argument then has `Child` in both parts. Retaining the outer -`in Child out Base` pair inside `Box` would incorrectly introduce `Base` as an -output bound of an argument that originally contained only `Child`. - -Conversely, in `use(f: Sink[T] -> Int)`, `T` has positive polarity: the method -input and callback input reverse it twice. For a receiver argument -`in (Base -> Int) out (Child -> Int)`, the invariant `Sink` argument must therefore -be the single type `Child -> Int`, in both parts. - -[`newres/VarianceSubstitution.mls`](../hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls) -contains both passing examples and controls with the projected types written -directly. It also checks tuples, structural fields, declaration contravariance, -and the polarity reversal inside a written wildcard input. - -`DeclaredType.positive` records the polarity for interpreting substitutions in -its source expression. It is independent of the direction of a later constraint. -Function domains and written wildcard input parts reverse this flag. Structural -field views retain it; nominal member lookup starts from the nominal arguments -already interpreted in the enclosing occurrence. Substituting a bound parameter -selects its part without replacing that part's original lexical environment. - -When the pair is not yet available, `TypeShape.SelectedArgument` retains the -argument reference and the requested part. Both interface observations and -constraints follow that same part, including when it arrives later. The consumer -caches selections by argument reference and polarity; selecting an existing -selection returns it unchanged. Resolved parts are reused directly, except when -a missing wildcard part needs its original source for diagnostics. A forwarding -cycle which exposes no interface stops at the repeated selection, while retaining -subscriptions for later evidence. - -For a fixed finite set of argument references, there are at most two cached -selection nodes per reference. Polarity likewise only doubles an otherwise finite -view domain; no parameter symbol or expanded type tree is allocated by selection. -`TypeRelationTest` checks delayed parts, cyclic selections, identity reuse, and -stable listener counts on relation replay. These are local bounds: they do not -establish the separate whole-graph bound for recursive binding environments. - -## Recursive acceptance example - -This passing regression is retained in -[`newres/MutableArrays.mls`](../hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls) -(the recursive `append` block, immediately after `appendTyped`): - -```mlscript -class Item(val value: Int) -fun append[A](xs: Array[A], value: A, n: Int) = - if n > 0 then append(xs, value, n - 1) else xs.push(value) -let xs = mut [] -append(xs, Item(5), 2) -xs.0.value -``` - -The result is `5`. The relation connects the array's element parameter to the -call-site instance of `A` in both directions, retaining each endpoint's marks. -Recursive calls reuse their parameter instance and propagate the inserted element -back through that graph. The adjacent two-array case checks isolation between -external callers of the shared recursive site. - -## Resolver integration - -For the recursive example above there are three relevant versions of `A`: - -```text -A_body original binder used to check the generic definition -A_outer instance for append(xs, Item(5), 2) -A_recursive instance for append(xs, value, n - 1) -``` - -Further recursive visits reuse `A_recursive`; they do not create -`A_recursive_recursive`. A second external application gets its own instance of -`A`, but still reaches the same recursive application site. Its enclosing marks -must keep the two external contexts distinct. The original abstract candidate of -`A_body` must not be copied into the call instances: instantiate the declared -constraints, not the generic body's accumulated unknown-value candidates. - -Use constraints over these references, rather than restoring wildcard matches -after copying expanded candidates. The argument endpoint retains its caller's -substitution and context; the parameter endpoint uses the callee site's instances. -An application must not overwrite both endpoints with the callee substitution. - -### Definition and application identities - -For an ordinary implicit instantiation, use the stable `Term.App.resSym` as -application identity, rather than taking a site from the callee's marks. Marks can identify a function reference shared by -several applications: - -```mlscript -let g = append -g(xs, First(1), 2) -g(ys, Second(2), 2) -``` - -These applications need distinct parameter instances. The two-array regression -following the recursive `append` block in `newres/MutableArrays.mls` checks this -case with different element interfaces. - -An explicit term-level `Term.TyApp` needs its own stable site identity, memoized -by the original syntax node, never allocated afresh during observation. By-name -invocations use the source reference/selection's site. Resolving an explicit -application must choose that site before interpreting an underlying by-name -reference, so its implicit invocation cannot allocate a competing group. - -For construction without explicit type arguments, use `Term.New.resSym` when it -consumes arguments or saturates a zero-list class; an unapplied constructor uses -its later first application. Canonicalize the constructor and class to the same -original owner. For an anonymous polymorphic annotation, the -owner is its original quantified declaration node. An overload resolving to more -than one definition instantiates each original definition at that site. Where -the language implicitly invokes a getter, its source reference/selection is the -invocation site; it is not a fresh site per resolver visit. Neither imported -views nor instantiated callable views should manufacture a new original owner. - -### Binder substitution before observation - -Normalize binders from inline declarations and separate signatures into a -reusable scheme with its parameter bounds, declared type fragments, and links to -inference for unannotated parts. Do not require a complete signature. -`TypeShape.Polymorphic` retains `Forall` binders and bound references; -`DeclaredTypeParameter` carries their interpreted bounds on callable views. -These original identities must be used when instantiating separate signatures. - -An instantiated callable must retain one immutable substitution from that -scheme's own binders to the site's parameter symbols. Use it for parameter -annotations, results, nested callback types, bounds, and type arguments before -`listenInstanceViews` expands a parameter. Enclosing class or function parameters -remain captures; they are not binders of the nested definition being instantiated. - -Argument matching alone is insufficient. The generic body was elaborated using -the original binders, and a returned tuple or closure can defer observing an -annotated value until after the call. Carry the same substitution through those -deferred observations. Keep generic-body checking on the original abstract -activation; do not mutate the body or unconditionally connect its inference host -to each call instance. The representation of these substituted body-result views -is described below. - -### Shared graph and contextual views - -Retain one source graph per definition. Its nodes describe written type structure, -references to binders, and inferred flows from parameters, expressions, and holes. -Give references to that graph an explicit view consisting of: - -```text -source node + originating inference host -substitution of this node's free explicit binders -existing normalized marks -``` - -The binder substitution selects original binders or memoized call-site instances. -It is a flat mapping over the source node's free binders, not a stack of past -substitutions. Keep compound supplied types as graph references connected by -constraints; do not insert their successive expansions into the substitution. -Alias and nominal argument bindings likewise retain references to argument nodes, -with recursive bindings represented by back-edges rather than nested map copies. -This requires replacing the recursive structural use of `DeclaredType.bindings` -where it would otherwise grow a new environment on every recursive visit. - -Memoize each view before observing its source node. Observing an inferred flow -subscribes to its existing host in the consuming resolver and applies the view -to arriving references. Observing a compound value exposes its outer shape while -giving its deferred children the corresponding view. This applies to tuple and -record fields, callable results, nested annotations, and closure captures; merely -rewriting the outer `InstanceShape` is insufficient. Compose views by replacing -bindings for the callee's own binders and retaining lexical captures, then discard -bindings unused by the observed source node. Do not wrap a view in another view. - -For `pair[A](x: A, y) = [x, y]`, the result's first field observes the annotation -node under `A -> A_s`; the second subscribes to `y` under the call's marks. A -closure returning `x` keeps the first reference after the enclosing call returns. -The body, field nodes, and `y` symbol are shared; only their views differ. - -The implemented body transport keeps two substitutions separate. A value such as -`InstanceShape(A_outer)` retains the caller's type reference. Its flow into a body -also carries that body's activation, for example `A -> A_inner` at a recursive -call. `ActivatedShape` records this second map on source-flow events; -`ContextualShape` and the tuple/record views retain deferred observations of values. -Dispatch unpacks the activation before performing an operation. Source listeners -accept all activations; an observation with a chosen substitution accepts only -compatible activations of its source node. It must not rewrite an already bound -caller reference to the callee's parameter. `NewResolverState.withInstances` -memoizes flat activation views that share the consumer's hosts, and `inGraph` -preserves the incoming activation when selecting an imported listener's graph. - -`newres/ContextualInference.mls` exercises separate calls through one stored -function reference, mixed annotated/inferred tuple and record fields, callbacks, -and returned closures. These cases use the existing reference marks unchanged. - -Generic-body checking observes the source graph with its original abstract -binders. Call inference observes it with the site's substitution. The abstract -unknown produced to check operations on a rigid binder is a checking witness, -not a lower bound to copy into the instantiated graph. Preserve the existing -diagnostics for operations unsupported by `A`; do not suppress all unknown -values or remove ordinary exposure-checking constraints. Keep the symbolic -binder dependency until an observer has chosen its view. In particular, -`registerTypeParameters` cannot keep seeding a host whose expanded candidates are -then blindly reused as a call's inferred result. - -### Constraints and marks - -Memoize a directed constraint by its two contextual type references, including -their originating hosts. Keep three operations distinct: - -- An ordinary instance contributes a lower-bound obligation to its expected type. -- Supplying a type reference retains that reference and delivers lower and upper - obligations to it, including obligations that arrive later. -- Comparing compound types follows their structure and argument variance, using - the same contextual references at every recursive step. - -Register listeners before replaying existing facts. Adding either a bound or a -supplied type must process the other facts already present, so event order does -not matter. For invariant arguments, install both directed constraints between -the retained endpoints; do not obtain the second by reversing a path already -applied to expanded candidates. Preserve a supplied union as a whole in negative -position. Deduplicate relations and delivered obligations by semantic references, -not by diagnostic witnesses. - -The implemented `constrainTypes` memoizes pairs of `ContextualType` endpoints -before following them. A parameter receives an instance wrapper retaining the -source reference; a structured or concrete target subscribes to that reference's -later bounds. Quiet concrete mismatch diagnostics do not discard these obligations. -`TypeRelationTest` checks cyclic propagation, early/late bounds, every distinct -upper target, preservation of a whole negative union, reverse endpoint contexts, -and independent importers. The supplied-argument tests additionally check input -obligations arriving before and after supplied references, chained parameters in -different marked contexts, isolation between activations, and cycles without -listener growth. These checks do not establish the whole-graph termination bound; suppliedness -metadata and endpoint context have separate roles as described above. - -Existing marks still transport instance flow through lexical scopes and distinguish -enclosing activations sharing a static inner call. Apply their current entry/exit -operations to each endpoint in its own view. A callee's symbol allocation key -does not include marks, and a caller's view is not replaced by a wildcard when -forming the reverse constraint. The proposal adds no mark syntax, path truncation, -or cloned lexical boundaries. Enable the existing path invariants in focused -solver tests to catch a mistaken context composition rather than masking it. - -### Partial signatures and inference holes - -Instantiation applies to explicit type binders even when the rest of a definition -is only partially annotated. The recursive `append[A]` example already has an -inferred result. A more direct example mixes annotated and unannotated inputs: - -```mlscript -private fun pair[A](x: A, y) = [x, y] -``` - -At an application site `s`, instantiate `A` as `A_s`. The annotation of `x` -becomes `InstanceShape(A_s)`. The unannotated `y` retains its existing value-flow -symbol and obtains argument shapes under the call's marks. Infer the result from -the body: its first field retains the reference to `A_s`, and its second field -retains the marked flow from `y`. Neither `y` nor the result needs an invented -quantified parameter or a fresh type variable at the call. - -The substitution must survive delayed tuple-field observations. Likewise, in -`private fun apply[A](x: A, f) = f(x)`, the unannotated callback and its result -must remain connected to the contextual `A`. Both examples, called with distinct -element interfaces, currently pass in -[`newres/PartialSignatures.mls`](../hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls). -The instantiation refactor must preserve that behavior. - -Represent a partial signature position by position: - -| Part of the definition | Source of its interface at a call | -| --- | --- | -| Explicit type binder `A` | The memoized call-site instance `A_s` | -| Written type fragment | Its instance wrapper, using the call's substitution | -| Unannotated value parameter | Its existing inference host and marks | -| Unannotated result | The body's flow graph, retaining substitution and marks | -| Inferable hole within a type | A stable inference node for that source hole, under marks | - -**Inferable holes use ordinary marked inference, not anonymous quantified -parameters.** This is the agreed semantics; hole syntax is not selected here. -For schematic `Array[?]`, retain the declared `Array` structure and connect its -element position to the hole's inferred flow. Reuse the source hole's node across -visits and distinguish contexts with marks. No call-site symbol copy is allocated -for the hole. Constraints follow the position's input/output polarity as usual. -An empty hole is pending inference, not an `UnknownValueShape` candidate to emit -immediately: later evidence may resolve it. At completion, a hole without usable -evidence supplies no member interface and does not grant dynamic access. An -unknown alternative actually contributed by exposure checking is different and -must remain even if some concrete candidates arrive too. - -An inference hole must be distinguishable from an intentionally abstract type, -an `in`/`out` wildcard, and an unresolved or invalid annotation. These must not -all collapse to `TypeShape.Abstract`, which the current interpreter uses for -several unsupported forms. Inferring one missing piece must not add members to -an explicitly written concrete interface elsewhere in the annotation. - -**Omitted generic arguments are inference holes too.** For `Pair[A, B]`, a written -`Pair[Int]` supplies `Int` for `A` and a hole for `B`; bare `Pair` has two distinct -holes. Identify each omission by its source type-use occurrence and missing formal -parameter. Two occurrences do not share a hole just because they name the same -class. Reinterpreting an occurrence, following an alias, or observing another -member reuses its contextual hole reference. Apply declaration-site variance to -the missing argument just as to an unqualified written argument. Explicit -`in`/`out` arguments retain their stated defaults; their absent parts are not holes. - -For `type HalfPair[T] = Pair[Int, T]`, a bare `HalfPair` introduces a hole for -`T` at that use, and the alias body forwards that reference into `Pair`'s second -argument. An omission written inside an alias body has its own source node and -is transported with the alias use's context; expansion must not allocate more -hole symbols. Excess arguments still indicate an arity error. - -The intended positive `Pair[Int]`, `HalfPair`, and bare `Array` cases in -`newres/PartialSignatures.mls` are tracked as `:fixme`s under the accepted -omitted-argument context limitation. Existing `DeclaredTypes.mls` examples selecting -members from omitted arguments without any supporting flow remain negative tests: a hole is inferable, not evidence of an -arbitrary member. `TypeShape.Hole` retains a source inference host, cached by the -type-use node and omitted formal parameter. Observation and constraint propagation -share the argument-completion operation, so they reach the same hole. Bounds can -arrive after an observation; an empty host emits no unknown candidate. Intentional -abstraction remains `TypeShape.Abstract`. Type validation rejects excess arguments, -including in unused annotations. - -Result annotations and ascriptions constrain their implementations, allowing their -holes to receive evidence while their written fragments keep restricting the -interface. Exposure checking contributes unknown shapes only to missing output -parts of incoming values, including external callback results. It does not add -arbitrary lower bounds to written binders fixed by a partial application. These -unknowns represent real possible inputs and remain alongside local evidence. -`newres/InferenceHoles.mls` checks source/formal-position isolation, these annotation -boundaries, and exposed interfaces. - -Two context cases remain explicit `:fixme`s in that worksheet. A recursive -`append(xs: Array, value, n: Int)` currently mixes the element interfaces of two -external callers, whereas the explicit-binder version has separate call-site -symbols. Holes must retain ordinary marked inference; copying the hole at each -call is not an acceptable repair. Also, with `type SomeBox = Box`, the hole in the -alias body is currently shared by both parameters of -`both(left: SomeBox, right: SomeBox)`, even though their alias-use references differ. -A future correction must preserve those uses without expanding the alias or -allocating more hole symbols; it is deferred under the accepted limitation. -Neither case is covered by the passing -source-occurrence isolation test, which uses two directly written `Pair` annotations. - -The scheme therefore cannot be a closed type synthesized from whatever shapes -happen to be available first. It must retain live links to inferred portions of -the definition, including bounds arriving later or through recursion. Existing -exposure checks for unannotated parameters and generic-body checking still apply; -partial annotations must not silently disable either. - -### Inferred member signatures through nominal annotations - -The agreed partial-signature semantics also apply to a selected member's missing -types through a nominal annotation. For example: - -```mlscript -class Item(val value: Int) -class Box with - fun item() = Item(1) -private fun read(x: Box) = x.item().value -read(new Box) ``` -Use `Box.item`'s inferred result here, consistently with partial function -signatures. Missing parameter types, constructor-field types, and value-member -types likewise link to the selected declaration's inference graph. Keep the -receiver's context on those links; using the class's unmarked global inference -would mix distinct instances. A nominal constraint must retain the actual -receiver's contextual flow as an input to these missing member types. The current -wrapper containing only a closed declared interface cannot recover an unannotated -constructor field after that receiver flow has been discarded. Keep the receiver -reference on the inferred member connection, while using the written nominal -type for lookup; do not replace the annotation's view with the actual receiver's -whole shape. Explicitly annotated member portions still use their written types. -Class-qualified selections and overloaded names -use the selected member's own scheme, including the term alternative of an -overloaded name. The corresponding examples in `DeclaredTypes.mls` are now -`:fixme` acceptance cases instead of requirements to reject inference. - -The nominal member set stays fixed: a `Base` annotation must not expose members -found only on `Child`. Overrides must satisfy the selected declaration's input/output -constraints. It is insufficient to assume that the base method's body describes -every dynamic dispatch result. In particular, a base method returning `Item` and -an override returning a type without `Item`'s members cannot justify selecting -those members from the dispatched result. `PartialSignatures.mls` retains this -negative regression. Connect override signatures to the inherited interface -before completing member targets, following function input/output variance; -do not expose an unchecked base implementation shape as a dispatch guarantee. -Preserve the existing member-target completion checks for imported code as well. - -### Partial application and explicit specialization - -Explicit type application is an instantiation site, even before any term -arguments are supplied. For `let g = f[Int]`, allocate the definition's group at -`f[Int]`, attach `Int` to its parameter instance, and retain that map on `g`. -Later applications of `g` reuse the map. They still have their own value-flow -marks; sharing the type-parameter group does not make their term arguments or -results the same runtime value. Check type-argument arity at specialization, -including when the result is unused. - -Without explicit type arguments, a function with parameter lists instantiates -when its first list is consumed. A curried tail retains that group for later -lists. By contrast, referencing a by-name function or selecting a by-name method -already invokes its body, so instantiate before observing its result. For example: - -```mlscript -private fun make[A] = - let cells = mut [] - (value: A) => - cells.push(value) - cells.0 -let shared = make -``` - -`shared` closes over one array allocated by the invocation at `make`. Its later -calls must all use that invocation's instance of `A`. Giving each call to -`shared` an independent `A` would treat one shared array as unrelated element -types. Separate expressions `let left = make` and `let right = make` are separate -invocation sites. `newres/InstantiationSites.mls` checks shared storage, independent -by-name method invocations, and rejection of an incompatible result selection -through both inferred and annotated closures. The annotated variant, -`make[A]: A -> A`, rejects `shared(First(3)); shared(Second(4)).second` (written on -separate lines in the test), since the second call returns the stored `First`. -A result annotation must not re-generalize the by-name definition's binder. The same rule applies to explicitly specialized -`make[T]`. Only a separately quantified result scheme retains its own binders, -subject to ordinary generic-body checking. - -`SpecializedShape` retains the group consumed by a type application of an inferred -definition. Complete callable signatures remove their scheme when instantiated. -Both distinguish a consumed scheme from an open scheme carrying lexical captures; -later calls must not instantiate the consumed scheme again. The substitution is -retained through callback checking, tuple/record results, closures, and constructor -aliases. Elaboration defers observing the base reference of a type application -until its arguments are available, preventing a second by-name instantiation. - -The supplied types belong to the type application's scope. For an ordinary -function or constructor, first transport the referenced callable through all -lexical captures, then bind its supplied arguments using that complete path. -Binding arguments before entering those captures makes specialization and the -later term application disagree about their common scope. In particular, -`wrap[B](value: B) = identity[B](value)` could accumulate exit marks even though -its source has no recursion. For a by-name definition, the reference itself -executes the body: rebase supplied arguments to that reference's scope before -invocation, then transport the result outward. Do not apply the supplied arguments -a second time to a separately quantified result. These transfers use ordinary -mark operations; no exit/reentry pair is treated as an identity. -`newres/SpecializationCaptures.mls` checks both forms through nested captures, -caller type parameters, and constructor specialization. These regressions also -pass with `checkMarkPaths` enabled; the default remains unchanged because other -existing paths still violate its invariants. - -Constructors use the same site policy. `C[T]` and `new C[T]` instantiate at their -explicit type application, including when term arguments remain to be supplied. -Without explicit arguments, an unapplied `new C` waits for its first term -application; a saturated zero-list `new C` invokes construction at the `new` site. -Later lists and aliases reuse the selected group. Array spreads observe that -element parameter, including bounds inferred from inserted values. - -`newres/StoredSpecializations.mls` covers independent specializations, deferred -record results, curried calls, and unused arity errors. Compiler-level allocation -checks assert that two explicit specializations allocate two groups even when -unused, whereas repeated calls through one specialization allocate only one group -for that scheme. By-name function/method cases, separate signatures, independently -quantified results, shared mutation, and specialization inside generic bodies are -also covered. Broader graph convergence remains a separate completion gate. - -`newres/ConstructorInstances.mls` retains the failing reconstruction regressions. -Reconstruction with `class Box[T](val item: T) with { fun copy() = new Box[T](item) }` -must retain the receiver's view of `T` separately from the new constructor's view; -the first of two distinct receiver calls currently loses its resulting element -interface. The unannotated control, `class Box(val item)` with -`copy() = new Box(item)`, mixes field candidates from distinct receivers. Thus the -remaining receiver-context problem also affects ordinary inferred value flow; -correcting only the type-binder map does not establish receiver separation. -The captured constructor-value form `Box[T](item)` now terminates, but its returned -field still needs the same receiver-context work as explicit `new`. -The separate supplied-callback regression now passes: - -```mlscript -class Item(val value: Int) -let callbacks = new mut Array[Item -> Int](0) -callbacks.push((x) => x.value) -``` - -The expected input of `x` is `Item`, including when the array is never invoked. -`instanceBindings` preserves a symbolic `TypeShape.Parameter` endpoint for supplied -and inferred class arguments alike. Nested nominal comparisons can observe that -endpoint without outer constraint marks. Suppliedness therefore follows the -canonical parameter instance, while the retained endpoint marks choose which -supplied bound receives each obligation. The callback-input regressions pass, -including rejection of a subclass-only input member and preservation of the -written callback result. No implementation candidate is added to a supplied -parameter by an input constraint. Reconstruction of `Box[T]` above remains open. - -Pre-application observation of an inferred result is still missing. -`newres/StoredSpecializations.mls` retains this concrete regression: - -```mlscript -class Item(val value: Int) -private fun identity[A](value: A) = value -private fun use[B](f: Item -> B): B = f(Item(9)) -use(identity[Item]).value -``` - -This regression now returns `9`. `identity[Item]` itself supplies the authoritative -instantiation site. Checking it against `Item -> B` can therefore observe the -inferred result under `A -> A_specialization`, with `Item` already attached to -that instance. No call of the specialized value needs to have been observed. - -This example does not require deferred views that map binders directly to -supplied type expressions. Use the existing finite map to parameter instances; -do not introduce that broader representation solely to repair this regression. Shared reference graphs remain necessary for compound -arguments and recursive environments, independently of this instantiation fix. - -### Constraint propagation and implementation order - -1. Preserve partial declaration/signature schemes and their original identities, - including links to inferred portions. Distinguish inference holes from - intentionally abstract types and variance wildcards. Add a - consumer-owned cache of complete call-site instances, with origin links back - to the declared binders. Select the authoritative site before observing the - value: explicit specialization and by-name invocation consume the definition's - scheme, and later applications preserve its group. The annotated by-name - shared-storage regression and allocation-count tests now pass. Keep omissions - source-owned; do not add mutable caches - to symbols. Preserve `Forall` binders and their bounds instead of erasing them. -2. Transport the memoized substitution through callable and result views before - expanding instance wrappers. Keep the existing marks for value flow and - lexical captures. Separate generic-body checking witnesses from the symbolic - constraints replayed in a call view. Verify delayed fields and returned closures. -3. Relate the resulting type references in both input and output positions, - retaining their hosts, substitutions, and marks. Ordinary arguments contribute - bounds; supplied types receive all obligations at their applicable polarity. - Apply the InvalML variance rules above. -4. Supplied member arguments retain symbolic references, and suppliedness is keyed - by canonical instance rather than observation path. Functions and constructors - share instantiation-site binder allocation and retain consumed groups. Preserve - this behavior while finishing reconstructed receiver and deferred body views. -5. Route declared member lookup to partial member schemes, retaining receiver - contexts for inferred fields and results. Check override compatibility before - completing member targets. Convert the member-inference `:fixme`s together; - keep tests rejecting subclass-only members and unsupported override results. - -Keep this work inside resolution and type interpretation. Lowering still consumes -completed member targets and value shapes; runtime values acquire no type-argument -objects. The main source changes are in `TypeShape.scala` (schemes and contextual -references), `NewResolverState.scala` (consumer-owned memo tables), -`NewResolver.scala` (view observation and constraint delivery), and `Shape.scala` -(transporting views through deferred children). Elaborator signature registration -must preserve binders and missing positions before the body is observed. - -### Type-reference scope audit - -The remaining graph work has a concrete termination regression in -`newres/TypeGraphTermination.mls`: - -```mlscript -type Chain[A] = {value: A, next: Chain[Array[A]]} -private fun walk[A](chain: Chain[A], n: Int) = - if n > 0 then walk(chain.next, n - 1) else () -``` - -The conservative check now rejects this non-regular variant even without a call -to `walk`. Earlier unrestricted expansion also encountered repeated exits with -`checkMarkPaths` enabled; rejecting this type does not establish the marks algebra's -bound for other recursive constraints. The regular variant with -`next: Chain[A]` and the finite selection `chain.next.next.value` now pass, -including with mark checks enabled. Both constraint expansion and mark paths need -validation independently of runtime recursion depth. - -The approved correction distinguishes these operations: - -- Expanding a type alias substitutes references to its arguments in its source - type graph. The alias declaration introduces no value invocation. Keep each - argument's caller context; do not add a value entry/exit for the alias's own - lexical qualification. -- Selecting a structural record type's field follows its written type reference. - `RcdField.make` creates a synthetic captured definition for value-field lookup; - that synthetic value boundary must not be introduced by a type-field projection. - Keep the field symbol as the resolved selection target. -- Function and class captures still cross actual value scopes and use the existing - mark operations. These paths must remain on references to captured parameters, - even when aliases or structural fields forward them. - -This approval concerns which operations cross a value scope, not permission to -truncate repeated marks, widen an alias, or allocate more inference variables. -The interpreter now forwards alias qualifications without a value capture, and -structural field declarations retain their written type without a synthetic -capture/exit. Worksheets cover regular recursion, finite parameter permutations, -mutually recursive aliases, function-local aliases, and structural callbacks. -An imported recursive alias is checked through two independent consumer modules. -These cases pass with mark checks enabled. A graph-level structural cycle test -also checks late bounds and verifies that repeated relations add no listeners or -parameter instances. - -Class-local aliases and local nominal annotations retain their deferred member -contexts through the contextual-reference transport described below. These passing -regressions do not establish the whole-graph bound: repeated-mark failures in generic -array-method paths still require separate checks. Dependency projection accepts -regular argument resets, including compound constants. The conservative regularity -check rejects the expanding alias before observation. - -The same graph audit must distinguish an open definition template from an already -interpreted reference. In `Box[T].copy() = new Box[T](item)`, the source `T` in the -supplied argument belongs to the receiver, while the constructed result refers to -the new site's instance of `T`. One merged map cannot choose both meanings after -the argument reference has been interpreted in the wrong view. Observe saved -source-graph operations with their caller references before attaching the callee's -instance map. Do not implement this by rerunning `resolveNew` over arbitrary syntax: -legacy constructions can have value shapes without a new-resolution producer, -and completed reference targets must remain immutable. A saved graph operation -must retain its selected definition and interpreted argument references. - -### Contextual references and receiver projection - -`TypeShape.Contextual(ContextualType(reference, marks))` retains an interpreted -reference together with its path into the observation scope. Substitution maps and -deferred fields keep these references as `DeclaredType` values. `transportType` -flattens an existing contextual node before interning the resulting endpoint; -contextual nodes contain one normalized mark path, never nested transport nodes. -Their dependencies are already in the saved reference, so they do not read an -ambient substitution map. - -For example: - -```mlscript -class Item(val value: Int) -class Box[A](val value: A) with - type Slot = {item: A} - fun get(): Slot = {item: value} -(new Box[Item](Item(4))).get().item.value -``` - -The `Slot` reference enters `get`'s scope even though its immediate interpretation -is an unmarked record interface. The method result leaves that scope, while the -record's deferred `item` type retains the receiver's interpretation of `A`. Scope -transport never depends on whether the current outer shape happens to be marked. -Both this example and the corresponding separate-signature case are covered in -`newres/TypeGraphTermination.mls`. - -Nominal member projection explicitly leaves the declaring class's scope. Its own -type arguments enter that scope before substituting member annotations. Modules -and objects introduce no such invocation boundary. Enclosing -binders keep their original contexts: their annotation's `Capture` nodes supply -the class entry. This also handles a local class capturing a function parameter: - -```mlscript -class Item(val value: Int) -private fun local[A](value: A) = - class Local(val item: A) - let box = (new Local(value) as Local) - box.item -local[Item](Item(7)).value -``` - -A captured template and its application arguments have different starting scopes. -For `Chain[A]` inside a function, `Chain` can denote an outer template while `A` -already belongs to the function. `TypeApplication` rebases supplied arguments into -the template's scope and records the same transfer for omitted positions. Expanding -the template transports its interface back. Views, constraints, and hole exposure -all use this operation; inferred holes receive the same context as supplied arguments. -Nominal constraints compare argument endpoints in the common observation scope, -including both their input and output parts. - -Deferred references use the ordinary value-flow mark algebra. Leaving a scope -with a wildcard exit consumes a candidate's entry mark, including its call-site -identity. Re-entering with a wildcard entry does not restore that identity, even -when both operations name the same lexical scope. Those two operations must not -cancel each other on the reference: doing so would incorrectly preserve an earlier -caller's identity. `TypeRelationTest` compares deferred transport with -ordinary candidate transport for wildcard and explicit exits, entries, and consumer -sites, including bounds published before and after observation. Repeated transport -of the same reference reuses its cache entry without allocating binder instances. - -Rebasing application arguments into a captured template's scope is subject to these -same rules. It cannot assume that leaving and re-entering that scope is an identity. -If substitution requires a different relationship between the template and argument -contexts, that relationship needs an explicit design and supporting examples; it -must not be implemented by changing wildcard cancellation for deferred references. -The omitted-argument limitation is accepted for now; its correction is deferred. - -`transportShape` composes paths on deferred instance references at projection and -result boundaries before delivering them to another caller. Leaving a wildcard -entry outside an instance wrapper could otherwise consume a subsequent concrete -call exit before the wrapper's own path was considered. Separate signatures retain -context when supplying parameter types and when following arrow results. By-name -members use the complete receiver path when consuming their binders. - -The finite-source/finite-instance argument still requires bounded mark paths and -regular type environments. Interning normalized endpoints prevents transport -wrapper histories from growing; it does not prove those two separate bounds or -justify truncating paths when an invariant fails. - -### Omitted-argument context regression - -Removing the incorrect wildcard exit-entry cancellation exposes four failing -blocks, retained as `:fixme` regressions in `newres/PartialSignatures.mls` and -`newres/InferenceHoles.mls`. The failure does not require recursion: - -```mlscript -class First(val first: Int) -class Second(val second: Int) -class Pair[A, B](val value: [A, B]) -private fun second(pair: Pair[Int]) = pair.value.1 -[second(Pair([0, First(1)])).first, second(Pair([0, Second(2)])).second] -``` - -The intended result is `[1, 2]`. Currently both result selections receive both -`First` and `Second`, causing missing-member diagnostics. The other failing blocks -cover a bare `HalfPair` alias and mutable `Array` annotations with and without -recursive calls. - -The hole for `B` belongs to the annotation occurrence in `second`. Following the -captured `Pair` template moves that hole reference outward with a wildcard exit -from `second`. Projecting the argument back into the annotation scope then adds a -wildcard entry. The constraint interpreter applies the reversed reference path to -each incoming bound before publishing it into the hole. For a bound entering -`second` at call `c1`, the wildcard exit consumes the entry carrying `c1`; the -wildcard re-entry replaces it with an entry without an ID. The second call loses -its ID in the same way. The hole therefore already contains mixed caller bounds -before its output is observed. - -This is evidence of an incorrect context relationship in the interpreter, not a -proof that the ordinary marks algebra is insufficient. A replacement must explain -how an argument reference remains associated with its annotation scope while being -substituted into an outer template, including deferred fields and both constraint -directions. It must retain ordinary inference for holes and cannot invent binder -instances or assume that a wildcard exit-entry pair is reversible. Review that -representation before implementing a replacement. - -### Template context versus argument context - -`newres/AnnotationContexts.mls` distinguishes a template's enclosing parameter from -an argument supplied where that template is used: - -```mlscript -private fun enclosing[A](x: A) = - type Both[B] = {left: A, right: B} - private fun wrap[B](y: B): Both[B] = {left: x, right: y} - wrap[Second](Second(2)) -``` - -`Both`'s free `A` belongs to `enclosing`, whereas the supplied `B` belongs to `wrap`. -The current implementation handles this explicit-argument example. The worksheet -checks two enclosing calls with different types, a passing unannotated control, -and a failing omitted-argument version marked `:fixme`. - -Simply removing `args.move(inverseMarks(reference.marks))` from the three type -interpreters is not a fix. That experiment leaves the omitted-argument failures -and makes the explicit-argument example fail a mark assertion: an entry into -`wrap` is matched against an exit from `enclosing`. The experimental change was -removed. Both directions of a constraint, as well as deferred member observation, -need the argument's original scope to be understood. Dropping the template's -transport instead is not a general solution either: the free `A` still needs its -path from `enclosing` into `wrap`. The template's free references and the supplied -arguments cannot uniformly receive the same treatment. - -The current interpreter substitutes arguments into the template and then applies -the template's transport to the resulting interface. That transport consequently -acts on substituted arguments as well as on references from the template itself. -Moving arguments backward first compensates for their different starting scopes, -but can lose caller identity through wildcard exits. - -A possible correction is to keep the template endpoint separate from the argument -environment, so that following a formal uses the supplied reference at its own -endpoint, while following a free reference uses the template's context. This is a -change to how substitution and transport compose, not permission to cancel a -wildcard exit-entry pair. It has not been implemented or established as a small -local change. Review the representation and its interaction with nominal member -projection, recursive aliases, occurrence polarity, and both constraint directions -before proceeding. Any new interpretation nodes must retain the finite source and -canonical-context bounds; allocating a new environment identity on each recursive -unfolding would not be acceptable. - -### Regular structural types - -The restriction is that unfolding an accepted structural type must admit a finite -graph of distinct type components, with recursive occurrences represented by -back-edges. A finite alias definition alone does not establish this property: - -```mlscript -type Chain[A] = {value: A, next: Chain[A]} -type Growing[A] = {value: A, next: Growing[Array[A]]} -``` - -`Chain[Int]` repeats the same interface and can be represented by a cycle. -`Growing[Int]` exposes successive `value` types `Int`, `Array[Int]`, -`Array[Array[Int]]`, and so on. It is non-regular and should be diagnosed rather -than expanded indefinitely or silently approximated. The third block of -`newres/TypeGraphTermination.mls` checks its rejection by the conservative -regularity diagnostic before structural observation. - -Regularity is not a requirement that recursive arguments be textually unchanged. -For example, `{value: A, next: Alternating[B, A]}` as the body of -`Alternating[A, B]` has a finite two-state unfolding. Mutually recursive aliases -and finite changes of arguments must also be considered. Alias and Boolean -normalization is implemented. The current finite source-graph check rejects cycles -that put an original binder underneath a non-Boolean constructor. It deliberately -rejects some regular types: argument-part selection and absorption across recursive -steps can make such a cycle finite. The diagnostic states this implementation -restriction rather than claiming to prove non-regularity. The -[refined design](new-resolution-regular-types.md#deferred-refinement-for-later-review) -is deferred. Any replacement must terminate on rejected inputs too; waiting for an -unfolding cache to stop growing is not a decision procedure. Do not impose a depth limit. - -Growth along one recursive edge is not sufficient to reject a type: - -```mlscript -type Left[A] = {value: A, next: Right[Array[A]]} -type Right[B] = {value: B, next: Left[Str]} -``` - -Starting from `Left[Int]` visits `Right[Array[Int]]`, then settles into the cycle -`Left[Str]` / `Right[Array[Str]]`. The argument reset breaks the expanding -dependency. `TypeGraphTermination.mls` includes this finite mutual unfolding as -an acceptance case alongside the parameter-permutation and unused-argument cases. - -There is also a representation obligation for accepted types. Currently -`DeclaredType.bindings` is a recursive map of `DeclaredType` values, and the whole -reference is used as a view-cache key. `declaredType` substitutes a directly bound -parameter and projects compound references onto their free binders. A genuinely -expanding dependency, such as repeatedly binding `A` to `Array[A]`, can still build: - -```text -E0 = {A -> Int} -E1 = {A -> (Array[A], E0)} -E2 = {A -> (Array[A], E1)} -... -``` - -The source expression and binder identities stay fixed while the environments -grow. Removing synthetic value-scope crossings does not bound these environments. -The regular argument-reset regression in `TypeGraphTermination.mls` is: - -```mlscript -type Reset[A] = {value: A, next: Reset[Int]} -fun read(chain: Reset[Str]): Int = chain.next.next.value -``` - -The unfolding has only the `Reset[Str]` and `Reset[Int]` interfaces. Compilation -now terminates: the closed `Int` reference has no free binders, so it retains no -previous binding map. The same holds for a fixed compound argument `Array[Int]`. -Accepted regular types need finite reference keys and shared recursive edges, -not a fresh nested environment on each visit. - -Dependency pruning alone does not identify transparent forwarding aliases. Alias -application reduction now makes this regular regression reach a fixed point: - -```mlscript -type Identity[X] = X -type Chain[A] = {value: A, next: Chain[Identity[A]]} -fun read(chain: Chain[Int]): Int = chain.next.next.value -``` - -`declaredType` reduces a fully supplied application of a resolved alias before -storing it as another reference's argument. Arguments are interpreted in the -caller's environment first; then declaration variance is applied and the alias's -formals are bound for its body. Existing occurrence-polarity selection still -chooses each substituted argument's input or output part. Thus `Identity[A]` -reduces to the same interpreted reference as `A`, instead of retaining another -`(Identity[A], previous-environment)` layer on every recursive visit. - -Reduction follows alias applications until a non-alias type constructor, an -unresolved target, a partial application, or an alias already being expanded is -reached. The source-alias guard grows on body expansion, while processing argument -syntax descends the finite source expression. Nested applications such as -`Identity[Identity[A]]` reduce their operands before adding the outer alias to the -guard. The reducer allocates no binder instances and does not unfold the children -of nominal, record, or function types. Captures retain the ordinary mark operations. -Forward references retain their bindings until dependency analysis can complete. - -For the forwarding cases, reducing the arguments and pruning unused bindings -makes repeated projections return the same reference keys. `TypeRelationTest` -checks a thousand reductions for equality, unchanged caller marks, stable listener -counts, and zero binder allocation. `TypeGraphTermination.mls` covers nested -forwarding aliases, parameter selection/permutation, forward definitions, a captured -alias, and an unproductive cycle with growing arguments. This does not prove the -whole-graph bound. The conservative regularity check rejects constructor-bearing -cycles while treating transparent alias forwarding as substitution. See the -[normalization implementation and variance counterexample](new-resolution-regular-types.md) -for the Boolean normal form, captured alias reduction, and the precision limits -of the current binder-only growth graph. - -The implemented dependency analysis operates on source `TypeResolution` nodes. -An edge forwards the child's free binders, excluding those bound by an alias or -quantifier. An applied alias forwards an argument's dependencies only when its -body depends on the corresponding formal. All equations are monotone over the -finite set of source binders, so iteration reaches a least fixed point. This also -accepts `Loop[A] = {value: Int, next: Loop[Array[A]]}`: no observable component uses -`A`, and the cycle alone cannot introduce that dependency. Nominal arguments -remain relevant regardless of whether the class's members mention them. - -Nominal declarations conservatively retain all enclosing explicit type binders. -Elaboration records this immutable set before elaborating members, including for -legacy exporters. Consumers read it from the declaring graph. This preserves -parameters used through nested aliases, member annotations, or inferred member -types without making dependency analysis wait on member inference. The class's -own parameters are supplied by its type application, rather than treated as free. - -No summary is finalized while a reachable source type lacks a target. Interface -observation, constraints, and hole exposure wait on shared dependency hosts; -when the target arrives, each observer projects its own saved environment. There -is at most one waiting subscription per source/dependency pair. Completed summaries -are immutable; inference hosts and waiting callbacks use the usual consumer-private -graph copies. Valid written types have one target, while synthesized argument, -selection, and inferred-value nodes retain their own references and do not read -an ambient binding map. The forward-alias worksheet and a graph-level recursive -tuple test check delayed discovery, independent substitutions, and replay without -additional listeners or parameter instances. - -This bounds dependency analysis and eliminates irrelevant environment growth. -Together with the conservative source-graph check, it prevents the recorded -constructor-growth cases. It does not establish the whole resolver's representation -bound; accepted reference environments and inferred constraints still need that audit. - -Keep structural alias unfolding distinct from inferred constraints at a recursive -generic function call. The latter reuses a site's parameter symbol and can add an -edge from the source `Array[A]` expression to that symbol without eagerly -substituting its accumulated bounds. A regularity restriction on written -structural interfaces does not by itself establish or replace the finite-domain -argument for that constraint propagation. - -### Fixed points and implementation checks - -There are at most as many allocated parameter instances as the sum of each -reachable definition's binder count over its syntactic instantiation sites. -Explicit type applications and implicit by-name invocation sites are finite source -occurrences too; observing them repeatedly does not add sites. Source hole -nodes and written type/term nodes are finite independently of recursive visits. -View substitutions range over the finite original and instantiated binders in -lexical scope. With the existing no-repeated-boundary mark invariant, their -normalized contexts also range over a finite domain. These facts bound the set -of memoized views and endpoint pairs **provided** structural interfaces satisfy -the regularity restriction, compound arguments remain shared graph edges and no -cache key includes a growing substitution or capture history. -Monotone, deduplicated propagation then reaches a fixed point. - -Check this at the graph layer as well as with worksheets. Replaying an existing -definition/site must leave the instance count unchanged; replaying the same view -or relation must add neither a listener nor a node. Include mutually recursive -definitions and changing arguments such as a recursive call with `Array[A]`: -the recursive site's parameter must receive a cyclic reference to the source -`Array[A]` expression, not generate `Array[Array[...]]` nodes on every pass. -Verify obligations delivered before and after edge creation, and two callers of -one inner site with different enclosing marks. Count cache growth until saturation; -passing a shallow recursion test is not sufficient evidence of termination. - -`TypeInstantiationTest` checks that repeated scheme/site allocation reuses the -whole group, distinct applications through a stored declaration allocate distinct -groups, and consumers do not copy generic checking candidates into their instances. -For the implemented signature views, the substitution map contains only original -binder keys and canonical instance-symbol values. Composition cannot add a nested -environment or a compound type to that map. Instantiated callable views are cached -before installing supplied-argument listeners. This bounds this part of the graph; -the activation views similarly use finite binder maps and a fixed base state, -never chains of activation states. Unit tests check repeated composition and -imported-view identity, shared consumer hosts, and exporter isolation. Whole-graph -listener convergence and recursive alias bindings still need the corresponding -checks before the full design is complete. - -Shape substitution is memoized too: repeated observations reuse the same deferred -tuple/record identity, so synthesizing its nominal array interface cannot create -a fresh type node on each traversal. Relation keys contain endpoint references, -flat binder maps, and existing normalized marks; they contain no candidate history. -Replaying a relation adds no listener or candidate. The recursive changing-array -and mutual-recursion cases in `newres/ContextualInference.mls` terminate with mark -invariant checks enabled. For a fixed finite set of source type references, there -are finitely many endpoint/map/mark combinations, and replay adds no listeners. -That local bound does not establish the remaining whole-graph alias-environment -bound, which must also prove that source references cannot proliferate. - -Constructor views retain source type-argument nodes and flat binder maps, not -expanded argument candidates. Binder allocation must use the original class and -the authoritative instantiation site. Constructor argument subscriptions are -memoized before observing their arguments. `TypeInstantiationTest` checks the -site counts above, including reuse across curried tails and saturated zero-list -construction. `typeApplicationSite` caches one site identity per source `TyApp` -node across activation views. It allocates neither a new source occurrence nor a -type-parameter group on repeated observation; by-name references reuse their -existing reference/selection site identity. These local allocation checks do not establish -the remaining whole-graph alias bound or repair reconstructed receiver contexts. - -For omitted arguments, each source type-use/formal-position pair allocates one -`TypeShape.Hole` host. No recursive traversal or call allocates a parameter symbol -for it. `TypeRelationTest` checks reuse across repeated observations, empty hosts -waiting for evidence, cyclic constraints with stable listener counts, and private -bounds in separate importers sharing the same source identity. This bounds hole -allocation and replay; it does not establish the missing context precision for -alias-body omissions noted above. The omitted-array regressions currently mix -caller contexts, both with and without recursion, as described in the context -regression above. - -During implementation, assert that a call instance's origin is an original binder, -all of one scheme's binders are allocated before its constraints are activated, -views are composed rather than nested, and generic checking witnesses never become -ordinary instantiated lower bounds. Retain consumer-private host copies and the -existing prohibition on changing completed member targets. Deferred references and -ordinary values must use the same mark algebra. If that algebra cannot express a -required context relationship, document a concrete example and propose an extension -before changing it. - -## Acceptance checks - -- An initially empty mutable array receives an element through `Array[A]`. -- Two calls using different arrays and incompatible element interfaces remain - independent, including two callers of the recursive example above. -- A repeated visit to the same definition/site reuses its parameter symbols; - two applications through an uninstantiated ordinary function alias obtain - distinct instances, whereas calls through one explicit specialization reuse - its group. By-name references/selections instantiate before exposing their - results, without re-generalizing shared mutable state. An inner site shared by - different enclosing activations remains distinguished by marks. -- Inline and separate polymorphic signatures instantiate consistently, with or - without explicit call-site type arguments. Partial applications retain their - substitutions and captured outer binders retain their original contexts. -- Mixed annotated/unannotated inputs and inferred results preserve the passing - tuple and callback cases in `newres/PartialSignatures.mls`. Nested inference - holes retain written structure and use marked inference without extra - call-site symbols; known annotation fragments still restrict the interface. -- Selected members infer missing types through nominal annotations, including - constructor fields, method results, and overloaded value members. Receiver - contexts remain distinct; subclass-only members and unsafe override results - remain rejected. Deferred closure observations preserve the outer substitution. -- Recursive input/output relations terminate with bounded call-site instances, - no repeated-boundary paths, and no unbounded binding-environment construction. -- Declaration-site `in`/`out`, use-site `in`/`out`, their overrides, and nested - function polarity agree with InvalML. -- Explicit function type arguments constrain callback inputs even when the - function never calls the callback locally. -- Two supplied types at one activation each receive every relevant bound; one - supplied union remains a single negative constraint target. Inspect delivery - directly where concrete mismatch diagnostics are intentionally quiet. -- Candidates arriving before or after relations produce the same result. - Separate importers leave the exporter and prelude hosts unchanged. -- Generic-body checking, interface exposure, and completed member targets remain - intact. Run `ctest` before focused worksheets and `hkmc2AllTests/test` before - completion; review and commit the generated golden outputs. - -Precise array positions/lengths, loud concrete-mismatch diagnostics, optional -`splice` arguments, general storage reassignment, and handler inference remain -separate work. +On `Receiver[in Child out Base]`, `accept` expects `Box[Child]`: the occurrence of +`T` is negative, and the invariant `Box` argument uses the selected `Child` for +both parts. A declaration's `in` annotation does not itself change the lexical +polarity at which a substituted argument is evaluated. + +`DeclaredType.positive` records this lexical polarity, independently of a later +constraint's direction. `TypeShape.Wildcard` retains written parts; +`TypeShape.Argument` retains contextual references for synthesized variance. +Neither transplants supplied syntax into the callee's binding environment. + +`TypeShape.SelectedArgument` saves a requested part when the argument is deferred. +Both subsequent constraint directions use that same selected type. Selections are +cached by argument reference and polarity; selecting an existing selection is +idempotent. Forwarding cycles retain subscriptions but stop repeated observation. +For a fixed argument-reference set, selection adds at most two nodes per reference. + +## Shared bodies and contextual constraints + +The inferred body is a shared graph. Instantiated views carry flat original-binder +to instance-symbol substitutions through deferred tuples, records, callbacks, and +closures. They do not copy expanded argument candidates into a new body graph. +Shape substitution is memoized so repeated observations reuse aggregate identities. + +`ActivatedShape` records the body activation that produced an event. +`ContextualShape` carries the value's caller-side view. These substitutions must +remain separate: recursion can bind the same original parameter differently in +the callee body and in a captured caller value. Composition keeps a fixed base +state and flat maps, not a chain of activation environments. Source listeners +accept all activations; contextual observations accept compatible ones. +`NewResolverState.withInstances` shares consumer hosts, and `inGraph` preserves +the incoming activation when invoking an imported listener. + +Generic bodies also receive a checking activation containing `RigidTypeShape` +witnesses. Those witnesses enforce generic opacity even in private/non-strict +code. Interface observation sees an unknown interface; call-site inference must +not copy a checking witness into its bounds. + +`constrainTypes` memoizes directed pairs of `ContextualType` endpoints before +installing subscriptions. Invariant arguments install both directions. A parameter +receives a symbolic instance wrapper; structured and concrete endpoints retain +listeners for future bounds. Delivery before and after edge creation must agree. +Relation replay adds no candidates, listeners, or parameter instances. + +## Scope transport + +`ContextualType` pairs a reference with an ordinary normalized mark path. +`transportType` uses the same mark operations as value flow and +flattens existing contextual nodes before interning the endpoint; references contain +one normalized path, not nested transport histories. `inverseMarks` reverses +directions for constraint transport; it is not a mathematical inverse +on candidates. A wildcard exit can consume an entry carrying a call-site ID. +Re-entering without an ID does not restore it. Exit followed by entry must not be +cancelled as an identity, including on deferred references. + +The relevant scope crossings are: + +- Function/method entry and exit, and lexical captures of enclosing definitions. +- Nominal member projection: capture argument references into the class and exit + the class when publishing its selected member view. +- Constructor invocation: use the same instance boundary for its class and + constructor, including later parameter lists. + +Alias qualification and structural type-field projection introduce no value scope. +Modules introduce no invocation boundary. Transport must follow the source +reference's scope, including references nested inside structured types; inspecting +only whether an outer shape is marked cannot decide this. `transportShape` +composes paths on deferred instance references before delivering them at projection +and result boundaries. Leaving a wildcard entry outside a wrapper could consume a +call exit before the wrapper's own path was considered. + +New-resolution syntax records lexical `Capture` nodes. Legacy elaboration records +the same lookup path in `legacyReferenceCaptures`, while the lexical binding is +known. A consuming resolver interprets that immutable metadata as captured type +references in its own graph, including selection prefixes. It must not guess a +missing path from a member's type parameters or mutate imported syntax. + +For mutable array literals, the nominal interface lives at the literal's use site. +The allocation context belongs to its element-parameter reference. Attaching that +exit to the whole interface would duplicate the class exit during member lookup. + +## Partial signatures and inference holes + +Unannotated function parameters and results use ordinary marked inference. +Omitted generic arguments use one `TypeShape.Hole` host per source type-use and +formal position. They receive constraints from arguments, results, and ascriptions; +no invocation allocates another hole or a quantified binder for it. For example, +`Pair[Int]` omits the second formal of `Pair[A, B]`, while bare `Pair` omits both. +Distinct source occurrences have distinct holes; revisiting one occurrence or +expanding its alias reuses it. Declaration variance applies to omitted arguments, +and excess arguments are rejected, including in unused annotations. An empty hole +waits for evidence instead of immediately publishing an unknown candidate. + +Written fragments continue to restrict the interface. Abstract types remain +`TypeShape.Abstract`; variance extremes are not holes. Interface exposure adds +unknown values to genuinely missing external inputs, including callback results. +Those unknown candidates remain alongside any local evidence. + +Current limitations are [omitted-argument context precision](new-resolution-future-work.md#omitted-argument-contexts) +and [missing selected member types](new-resolution-future-work.md#inferred-member-signatures). + +## Canonical references and termination obligations + +Source dependency analysis computes free original binders over the finite +`TypeResolution` graph. Alias/quantifier edges hide their bound formals; an alias +application forwards argument dependencies only for formals used by its body. +Monotone finite-set equations reach a least fixed point. Nominal arguments remain +relevant, and nominal declarations conservatively retain all enclosing explicit +binders recorded by elaboration, including dependencies through local aliases. + +No summary is finalized while a reachable source target is unresolved. Observations, +constraints, and hole exposure wait on shared dependency hosts, then project their +own saved environments. Synthetic formula/argument/selection nodes follow their +saved references instead of reading an ambient binding map. This removes irrelevant +bindings without freezing inference candidates or treating forward references as closed. + +[Regular structural types](new-resolution-regular-types.md) specifies alias reduction, +Boolean normalization, and the conservative constructor-cycle rejection check. +Accepted recursive references must share graph edges rather than grow substituted +environments. Structural recursion and recursive generic function constraints are +different: a call can add an edge to a reusable parameter instance without eagerly +unfolding its accumulated bounds. + +The following local bounds hold: finitely many definition/site binder instances, +source holes, flat binder substitutions, and normalized paths over distinct lexical +boundaries. Formula normalization is finite for a fixed atom set. These bounds do +not alone establish finiteness of nested binding environments or the atom set. +A whole-graph termination argument still needs to bound all accepted reference keys +and demonstrate listener convergence. Cache keys must never contain growing +substitution histories; depth limits and dropped marks do not establish a fixed point. + +These representations are internal to resolution. Lowering consumes completed +targets and value shapes; runtime values acquire no type-argument objects. + +## Validation + +Graph tests cover instance counts and replay (`TypeInstantiationTest`), directed +relations, delayed targets and consumer isolation (`TypeRelationTest`), and Boolean +normalization (`TypeFormulaTest`). `CompilerCacheTest` checks exporter immutability. + +Worksheet coverage under `newres` includes `MutableArrays`, `ContextualInference`, +`InstantiationSites`, `StoredSpecializations`, `SpecializationCaptures`, +`TypeArgumentVariance`, `VarianceSubstitution`, `AnnotationContexts`, and +`TypeGraphTermination`. Deferred cases retain explicit regression expectations; +see the [future-work reference](new-resolution-future-work.md). diff --git a/doc/reference.md b/doc/reference.md index 2bd6e2174a..a4078ef28d 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -1309,7 +1309,8 @@ to member lookup and calls. A receiver annotated `Base` exposes Base's declared members and preserves virtual dispatch; concrete arguments cannot add subclass members to that interface. The same rule applies to parameters, result annotations, separate signatures, and constructor fields. Reading an unannotated member through -an annotated interface does not infer its shape from its initializer or method body. +an annotated interface currently does not infer its shape from its initializer or +method body. Inference of missing selected-member types is a planned improvement. Structural record annotations likewise expose only their declared fields. Compilation files check that their exposed functions and values work for inputs @@ -1358,12 +1359,67 @@ Bounded type parameters: fun f[A <: Num](x: A): A = x ``` +Under new resolution, supplying a type argument affects both inputs and outputs. +For an invariant `Array[A]`, supplying `Int` means that lower bounds of `A` are +checked against `Int`, and upper bounds must accept `Int`. Passing an ordinary +integer value to `x: A` instead contributes only a lower bound; it does not fix +all uses of `A` to `Int`. + +Use-site type arguments have input and output parts: + +| Argument | Input | Output | +| --- | --- | --- | +| `S` | `S` | `S` | +| `in T` | `T` | `Any` | +| `out U` | `Nothing` | `U` | +| `in T out U` | `T` | `U` | + +For an unqualified argument, the declaration's `in` or `out` annotation selects +the corresponding form. An explicitly written use-site variance supplies its own +parts. Substitution selects the output part in positive positions and the input +part in negative positions. Function inputs reverse polarity; results preserve it. +Interpret an argument at its occurrence before applying the enclosing declaration's +variance. For example, a method input `Box[T]` on `Receiver[in Child out Base]` +uses `Box[Child]` when `Box` is invariant. + +Each explicit generic parameter is instantiated once at the expression that +specializes or invokes its definition: `f[T]`, a by-name reference/selection, or +otherwise the first term application. Stored specializations and later curried +argument lists share that instantiation. A by-name reference invokes its body even +without parentheses, so later uses of its result share any state it created. +An ordinary reference to a function with parameter lists can retain its generic +scheme until specialization or invocation. + +Missing parameter/result annotations use inference. Omitted generic arguments +likewise count as inference holes rather than anonymous generic parameters; written +parts continue to restrict the interface. Currently an omitted argument inside a +shared annotation or alias can mix inferred types from different callers. These +annotations therefore do not always preserve the precision of a wholly unannotated +parameter. Concrete type-mismatch diagnostics are also incomplete during the +new-resolution migration. + ### Structural Record Types ```mlscript type Point = { x: Num, y: Num } ``` +Under new resolution, recursive structural types must have a finite graph +representation of their unfolding. The current check conservatively rejects constructor-bearing cycles between used +type parameters, after alias and union/intersection normalization. For example: + +```mlscript +type Chain[A] = {value: A, next: Chain[A]} +type Reset[A] = {value: A, next: Reset[Array[Int]]} +type Saturating[A] = {value: A, next: Saturating[A | Int]} +type Growing[A] = {value: A, next: Growing[Array[A]]} +``` + +The first three are supported. `Growing[Int]` would expose `Int`, `Array[Int]`, +`Array[Array[Int]]`, and so on, and is rejected. The check also rejects some finite +types whose regularity depends on variance or relationships between recursive +arguments. Its diagnostic indicates a limitation of the current check. + ### Function Types ```mlscript From 9dea35c385709f23afd8d7a83cd83d13708d1d5e Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 14:13:59 +0800 Subject: [PATCH 121/228] Elaborate the prelude with new resolution and share completed symbol lookup --- doc/new-resolution-design.md | 16 +++-- doc/new-resolution-suite-migration.md | 16 ++--- doc/new-resolution-type-value-flow.md | 8 +-- .../test/scala/hkmc2/CompilerCacheTest.scala | 21 ++++++ .../src/main/scala/hkmc2/CompilerCtx.scala | 19 ++++-- .../scala/hkmc2/semantics/Elaborator.scala | 29 +++----- .../scala/hkmc2/semantics/NewResolver.scala | 68 ++++++++----------- .../hkmc2/semantics/NewResolverState.scala | 4 -- .../src/main/scala/hkmc2/semantics/Term.scala | 27 +++++--- .../src/test/mlscript/decls/Prelude.mls | 2 +- .../src/test/mlscript/newres/Functions.mls | 1 + .../src/test/scala/hkmc2/MLsDiffMaker.scala | 12 +++- 12 files changed, 119 insertions(+), 104 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 28ab0ffc6b..4c0793c98a 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -53,16 +53,20 @@ replaced by truncating or widening paths. In particular, wildcard exit followed by wildcard entry is not an identity: it can discard a caller's activation ID. Reference transport uses the same operations as ordinary value flow. -Legacy references do not contain `Capture` syntax. Elaboration records the lexical -lookup path as immutable source metadata, and each consumer interprets it in its -private type graph. Both concrete nominal references and parameter references -need these paths. Guessing captures only for substituted class parameters misses -concrete member results and nested callback arguments. +The prelude uses its own `#lang(0.3.x)` directive. Its loader applies file +configuration before elaboration and erasure, and supplies the block's original +symbols for builtin lookup during bootstrap. Its signatures therefore carry the +same `Capture` syntax as other new-resolution declarations. + +Legacy consumers can read completed new-resolution signature symbols through +`legacyResolvedSym`. The lookup asserts that the referenced block is complete and +shares erasure's symbol-selection rules, including ambiguity and error checks. +It does not resolve imported syntax again or observe incomplete candidate sets. `newres/SpecializationCaptures.mls`, `RecursiveEnvironment.mls`, and `ConstructorFieldRecovery.mls` exercise captured specialization, recursion, and partial construction. `PrimitiveMembers.mls`, `Arrays.mls`, `GenericMethods.mls`, -and `MutableArrays.mls` exercise legacy signature captures and array member paths. +and `MutableArrays.mls` exercise prelude signature captures and array member paths. Receiver reconstruction and omitted-argument context precision are [deferred improvements](new-resolution-future-work.md). diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 0307887d22..2639188d5b 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -63,15 +63,13 @@ and the temporary legacy `LegacyOption` compatibility fixture. ### Mixed-mode imports and fixture interfaces -`Option` uses new resolution, but legacy consumers such as `apps/parsing/Parser` -inspect imported return annotations through `Resolver.resolveType`/`resolveSign`. -This queries legacy symbols on new-resolution syntax. The affected dependency -graph imports `LegacyOption` temporarily; consumers exchanging `Some`/`None` -values, including reflection and `Block`, must share the same constructor identities. -Port these consumers together and delete `LegacyOption`, restoring `Option` imports. -The same signature boundary blocks independent ports of `Token` and `Keywords`. -A general interoperability bridge must consume completed type information without -re-resolving imported nodes or querying erased types before erasure. +Legacy consumers can read completed new-resolution symbols in imported signatures. +The prelude uses new resolution. The parser dependency graph uses `LegacyOption` +temporarily; consumers exchanging `Some`/`None` values, including reflection and +`Block`, must share the same constructor identities. Port these consumers together +and delete `LegacyOption`, restoring `Option` imports. Retry `Token` and `Keywords` +with their consumers using the completed-symbol compatibility path. Imported nodes +must not be re-resolved, and erased types must not be queried before erasure. Many fixtures also need source interfaces: for example, `QuoteExample.bind` calls an unannotated callback, and Nofib helpers expose comparators and printers. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 431bef3163..f381ab24ed 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -202,11 +202,9 @@ composes paths on deferred instance references before delivering them at project and result boundaries. Leaving a wildcard entry outside a wrapper could consume a call exit before the wrapper's own path was considered. -New-resolution syntax records lexical `Capture` nodes. Legacy elaboration records -the same lookup path in `legacyReferenceCaptures`, while the lexical binding is -known. A consuming resolver interprets that immutable metadata as captured type -references in its own graph, including selection prefixes. It must not guess a -missing path from a member's type parameters or mutate imported syntax. +New-resolution syntax records lexical `Capture` nodes, including in the prelude. +Imported type interpretations retain these source references in the consuming +graph; they do not reconstruct capture paths from selected member parameters. For mutable array literals, the nominal interface lives at the literal's use site. The allocation context belongs to its element-parameter reference. Attaching that diff --git a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala index eab7d613ee..f138c35234 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala @@ -78,6 +78,27 @@ class CompilerCacheTest extends AnyFunSuite: graph.withDependency(fileC, fileA)(cycle => assert(cycle == List(fileC, fileA, fileB, fileC))): fail("The dependency closing the cycle should not be requested") + test("prelude directives select resolution independently of the session default"): + import io.PlatformPath.given + import semantics.* + import utils.* + + val paths = TestFolders.compilerPaths(os.pwd) + val cfg = Config.default(TestFolders.mainTestDir(os.pwd)) + assert(!cfg.language.useNewResolution) + val compiler = CompilerCtx.fresh(io.FileSystem.default, paths, cfg) + given DebugPrinter = new DebugPrinter + given TL = new TraceLogger: + override def doTrace: Boolean = false + given Raise = diagnostic => fail(diagnostic.theMsg) + val prelude = compiler.getPrelude(paths.preludeFile) + def hasCapturedAnnotation(statement: Statement): Boolean = statement match + case capture: Term.Capture if capture.typeInterpretation.nonEmpty => true + case _ => statement.subStatements.exists(hasCapturedAnnotation) + assert(hasCapturedAnnotation(prelude.term)) + assert(compiler.getPrelude(paths.preludeFile) eq prelude) + assert(prelude.state.newResolverState.allocatedTypeInstanceCount == 0) + test("legacy artifacts bind unused syntax hosts before consumers observe them"): import io.PlatformPath.given import semantics.* diff --git a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala index b37884f384..067c5e6d30 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala @@ -202,8 +202,9 @@ class CompilerCtx( // The prelude context is shared so every compilation unit sees the same prelude // symbols. Callers still elaborate their own files with a fresh State; the frozen // State remains the owner captured by the prelude symbols themselves. - // The prelude is elaborated once per context, under its root configuration: were it - // elaborated per requester, cached compilation units would keep referring to whichever + // The prelude is elaborated once per context, under its own file directives applied + // to the root configuration. Were it elaborated per requester, cached compilation + // units would keep referring to whichever // elaboration came first, and a body inlined across units would then carry prelude symbols // that the importing file does not recognize. val lastMod = fs.getLastChangedTimestamp(file) @@ -219,13 +220,19 @@ class CompilerCtx( given Config = rootConfig given CompilerCtx = this val parse = ParserSetup(file) - val elab = Elaborator(tl, file.up, Ctx.empty) - val initCtx = State.init.nestLocal("prelude") - val (blk, ctx) = elab.importFrom(parse.resultBlk, Nil)(using initCtx) + val elaborationConfig = Config.elaborationConfig(parse.resultBlk) + // The prelude defines the builtins consulted during its own resolution. + // Seed lookup with the block's original symbols, as block elaboration does, + // so those lookups share the declarations that are being elaborated. + val initCtx = State.init.nestLocal("prelude").withMembers(parse.resultBlk.definedSymbols) + val elab = + given Config = elaborationConfig + Elaborator(tl, file.up, initCtx) + val (blk, ctx) = elaborationConfig.givenIn(elab.importFrom(parse.resultBlk, Nil)(using initCtx)) // Prelude declarations have no executable program, but their signatures and nominal hierarchy // must be erased before any compilation unit can use them. given Ctx = ctx - codegen.Erasure(blk) + elaborationConfig.givenIn(codegen.Erasure(blk)) PreludeArtifact(parse.resultBlk, blk, ctx, state, rootConfig, lastMod), ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index cba0f3d5d9..de32081983 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -174,14 +174,6 @@ object Elaborator: if config.language.useNewResolution then inherited.map(capture) else inherited - /** Legacy syntax omits Capture nodes, but its type references can later be - * interpreted by a new-resolution consumer. Save the same lexical path - * while lookup still knows where the binding was found. - */ - def referenceCaptures(name: Str): Ls[AnyDefinitionSymbol] = - if env.contains(name) then Nil - else parent.toList.flatMap(_.referenceCaptures(name)) ::: outer.resolutionBoundary.toList - private def capture(elem: Ctx.Elem): Ctx.Elem = outer.resolutionBoundary.fold(elem)(Ctx.CaptElem(elem, _)) @@ -2758,19 +2750,14 @@ extends Importer: msg"Expected a parameter list (a tuple of parameters), but found ${t.describe}" -> t.toLoc :: Nil (ParamList(ParamListFlags.empty, Nil, N).withLocOf(t), ctx) - def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = - val result = ctx.get(id.name) match - case candidate @ (S(_: Ctx.WildcardElem) | N) => - // Primitive operators are implicit bindings outside the Ctx environments; - // like explicit bindings, they take precedence over wildcard sources. - state.builtinOpsMap.get(id.name) match - case S(bi) => S(bi.ref(id)) - case N => candidate.map(_.ref(id)) - case S(elem) => S(elem.ref(id)) - if !newResolution then result.foreach: reference => - rstate.data(reference) - rstate.legacyReferenceCaptures(new Identity(reference)) = ctx.referenceCaptures(id.name) - result + def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = ctx.get(id.name) match + case candidate @ (S(_: Ctx.WildcardElem) | N) => + // Primitive operators are implicit bindings outside the Ctx environments; + // like explicit bindings, they take precedence over wildcard sources. + state.builtinOpsMap.get(id.name) match + case S(bi) => S(bi.ref(id)) + case N => candidate.map(_.ref(id)) + case S(elem) => S(elem.ref(id)) def pattern(t: Tree): Ctxl[Pattern] = import ucs.{Ctor, unapply, error}, ucs.extractors.*, Keyword.*, Pattern.*, InvalidReason.* diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 3b704aa5a2..ac6e2dcf3d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -64,24 +64,7 @@ class NewResolver: rs.typeInterpretations.get(new Identity(term)).orElse(term.typeInterpretation) match case S(result) => result case N => - val interpreted = new TypeResolution(term, messages => resolError(term, messages)) - def legacyCaptures(source: Term): Ls[AnyDefinitionSymbol] = - val graph = source.originalData.owner - val recorded = if graph == null then N else graph.nn.legacyReferenceCaptures.get(new Identity(source)) - recorded.getOrElse(source match - case SynthSel(prefix, _) => legacyCaptures(prefix) - case Sel(prefix, _) => legacyCaptures(prefix) - case _ => Nil) - // The captured interpretation is consumer-owned. Source metadata is - // finite lexical syntax; it neither allocates call instances nor depends - // on the number of times an imported annotation is observed. - val result = legacyCaptures(term).foldLeft(interpreted): (base, scope) => - scope match - case _: TypeAliasSymbol => base - case _ => - val captured = new TypeResolution(term, messages => resolError(term, messages)) - captured.publish(TypeShape.Captured(base, scope)) - captured + val result = new TypeResolution(term, messages => resolError(term, messages)) // Cache before following a recursive alias. Imported legacy annotations // have no stored interpretation: their new inference graph must remain // private to this consumer, including listeners on synthesized hosts. @@ -92,27 +75,27 @@ class NewResolver: rstate.recordResolution(ref, ref.resolvedTargets.contains(symbol))(ref.resolvedTargets ::= symbol) case _ => () def definition(defn: Definition)(using NewResolverState): Unit = defn match - case cls: ClassLikeDef => interpreted.publish(TypeShape.Nominal(cls)) + case cls: ClassLikeDef => result.publish(TypeShape.Nominal(cls)) case alias: TypeDef => - interpreted.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) + result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) // Validate unused annotations too, once their forward source graph // is complete. Interface observation uses the same gate below. withCanonicalType(DeclaredType(result, Map.empty, Map.empty, true))(_ => ()) - case _ => interpreted.publish(TypeShape.Abstract) + case _ => result.publish(TypeShape.Abstract) symbol.defn match case S(defn) => definition(defn) case N => symbol.defnListeners += definition def reject(shape: Shape)(using NewResolverState): Unit = result.fail(msg"${shape.describe.capitalize} cannot be used as a type" -> shape.toLoc :: Nil) - interpreted.publish(TypeShape.Abstract) + result.publish(TypeShape.Abstract) def selectMember(member: BlockMemberSymbol)(using NewResolverState): Unit = member.onComplete: () => member.asTpe.orElse(member.asModOrObj) match case S(symbol) => select(symbol) case N => result.fail(msg"${member.describe.capitalize} cannot be used as a type" -> member.toLoc :: Nil) - interpreted.publish(TypeShape.Abstract) + result.publish(TypeShape.Abstract) term match - // Imported prelude declarations can still contain legacy references. + // Imported declarations can still contain legacy references. case Ref(symbol: TypeSymbol) => select(symbol) case Ref(member: BlockMemberSymbol) => selectMember(member) // Imported parent declarations can contain legacy synthesized selections. @@ -124,32 +107,32 @@ class NewResolver: case S(member: BlockMemberSymbol) => selectMember(member) case _ => result.fail(msg"This reference does not denote a type." -> ref.toLoc :: Nil) - interpreted.publish(TypeShape.Abstract) + result.publish(TypeShape.Abstract) // An alias's lexical qualification substitutes type references; it // does not cross an invocation boundary. Captured function and class // parameters still retain their actual value-scope crossings. - case Capture(base, _: TypeAliasSymbol) => typeResolution(base).listen(interpreted.publish) - case Capture(base, thru) => interpreted.publish(TypeShape.Captured(typeResolution(base), thru)) - case TyApp(base, args) => interpreted.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) + case Capture(base, _: TypeAliasSymbol) => typeResolution(base).listen(result.publish) + case Capture(base, thru) => result.publish(TypeShape.Captured(typeResolution(base), thru)) + case TyApp(base, args) => result.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) case Forall(params, outer, body) => - interpreted.publish(TypeShape.Polymorphic(params.map: param => + result.publish(TypeShape.Polymorphic(params.map: param => TypeQuantifier(TypeShape.Parameter(param.sym, param.sym.inferenceHost), param.lb.map(typeResolution), param.ub.map(typeResolution)) , outer, typeResolution(body))) case CompType(left, right, union) => val l = typeResolution(left) val r = typeResolution(right) - interpreted.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) - case DynTy() => interpreted.publish(TypeShape.Dynamic) - case FunTy(params, ret, _) => interpreted.publish(TypeShape.Function(params, typeResolution(ret))) - case UnitVal() => interpreted.publish(TypeShape.Unit) - case WildcardTy(input, output) => interpreted.publish(TypeShape.Wildcard(input.map(typeResolution), output.map(typeResolution))) + result.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) + case DynTy() => result.publish(TypeShape.Dynamic) + case FunTy(params, ret, _) => result.publish(TypeShape.Function(params, typeResolution(ret))) + case UnitVal() => result.publish(TypeShape.Unit) + case WildcardTy(input, output) => result.publish(TypeShape.Wildcard(input.map(typeResolution), output.map(typeResolution))) case SimpleRef(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => - interpreted.publish(TypeShape.Parameter(sym, sym.inferenceHost)) + result.publish(TypeShape.Parameter(sym, sym.inferenceHost)) case Ref(sym: VarSymbol) if sym.decl.exists(_.isInstanceOf[TyParam]) => - interpreted.publish(TypeShape.Parameter(sym, sym.inferenceHost)) + result.publish(TypeShape.Parameter(sym, sym.inferenceHost)) case Tup(fields) if fields.forall(_.isInstanceOf[Fld]) => - interpreted.publish(TypeShape.Tuple(fields.collect { case Fld(_, sign, _) => typeResolution(sign) })) + result.publish(TypeShape.Tuple(fields.collect { case Fld(_, sign, _) => typeResolution(sign) })) case record: Rcd => val fields = record.stats.collect { case field: RcdField => field } if fields.length != record.stats.length || fields.exists(field => field.field match @@ -157,11 +140,16 @@ class NewResolver: case _ => true) then result.fail(msg"A record type requires statically named fields." -> record.toLoc :: Nil) - interpreted.publish(TypeShape.Abstract) - else interpreted.publish(TypeShape.Record(record, + result.publish(TypeShape.Abstract) + else result.publish(TypeShape.Record(record, fields.reverse.distinctBy(_.sym.nme).reverse.map(field => field -> typeResolution(field.rhs)))) + // Signed numeric literals use operator applications in the syntax tree. + // Like unsigned singleton types, they have no member interface here. + case App(Ref(op: BuiltinSymbol), Tup(Fld(_, Lit(_: Tree.IntLit | _: Tree.DecLit), _) :: Nil)) + if op.nme === "-" || op.nme === "+" => + result.publish(TypeShape.Abstract) case _: Neg | _: Tup | _: Lit | Missing | Error() => - interpreted.publish(TypeShape.Abstract) + result.publish(TypeShape.Abstract) case _ => listen(term, discardMarks = true): case shape: SymShape => shape.sym.onComplete: () => shape.sym.asTpe.orElse(shape.sym.asModOrObj) match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index a24804fb47..93d95231c8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -204,10 +204,6 @@ final class NewResolverState private ( new Cache(inherited.map(_.selfShapes), identity) val defnShapes: Cache[DefinitionSymbol[?], DefnShape] = new Cache(inherited.map(_.defnShapes), identity) - // Immutable source metadata recorded by legacy elaboration. Consumers recover - // captures without modifying the exporter's terms or resolving names again. - val legacyReferenceCaptures: Cache[Identity[Term], Ls[AnyDefinitionSymbol]] = - new Cache(inherited.map(_.legacyReferenceCaptures), identity) val typeInterpretations: Cache[Identity[Term], TypeResolution] = new Cache(inherited.map(_.typeInterpretations), identity) // Recorded before elaborating a declaration's members, including in legacy diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 77255e7087..38a6371c90 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -367,7 +367,9 @@ sealed trait NewSelImpl extends NewResolvableImpl: var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution // Class identity and captures must survive even when candidates share an inherited member. var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil - def hasAmbiguousClass(using Erasure): Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: + def hasAmbiguousClass(using Erasure): Bool = hasAmbiguousClassImpl + // Also used by the guarded symbol lookup for completed imports. + private[semantics] def hasAmbiguousClassImpl: Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: _.withoutCaptures match case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.sizeCompare(1) > 0 case _ => false @@ -528,12 +530,17 @@ enum Term extends Statement, ShapePublisher: * symbol is resolved during the resolution stage. Reading its final value requires * erasure; elaboration must listen for shapes instead. */ - def resolvedSym(using Erasure): Opt[Symbol] = expanded match + def resolvedSym(using Erasure): Opt[Symbol] = resolvedSymImpl + + /** Read-only lookup shared by erasure and legacy consumers of completed imports. + * The public accessors establish completion before reading new-resolution targets. + */ + private def resolvedSymImpl: Opt[Symbol] = expanded match case res: Resolved => S(res.sym) case SimpleRef(sym) => S(sym) - case Capture(base, _) => base.resolvedSym + case Capture(base, _) => base.resolvedSymImpl case ref: UnresolvedRef if ref.resolvedMembers.distinct.sizeCompare(1) =/= 0 => N - case sel: NewSel if sel.hasAmbiguousClass => N + case sel: NewSel if sel.hasAmbiguousClassImpl => N case ref: NewResolvable => if ref.isErroneous then N else ref.resolvedTargets.distinct match case sym :: Nil => S(sym) @@ -542,15 +549,17 @@ enum Term extends Statement, ShapePublisher: case sel: Sel => sel.sym case sel: SynthSel => sel.sym case sel: SelProj => sel.sym - case app: TyApp => app.lhs.resolvedSym + case app: TyApp => app.lhs.resolvedSymImpl case _ => N - /** The old resolver may inspect its own expansions while resolving legacy terms. - * This deliberately rejects new references: their candidate sets are not final yet. + /** The old resolver may inspect its own expansions while resolving legacy terms, + * and completed references in imported new-resolution signatures. Never observe + * a new reference while its defining block can still change its selected targets. */ private[semantics] def legacyResolvedSym: Opt[Symbol] = expanded match - case _: NewResolvable | _: NewRefImpl | _: Capture => - lastWords("Legacy symbol query on a new-resolution term") + case term: (NewResolvable | NewRefImpl | Capture) => + assert(term.originalData.completed, "Legacy symbol query on an incomplete new-resolution term") + term.resolvedSymImpl case app: TyApp => app.lhs.legacyResolvedSym case term => term.symbol diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index a4d7594fd7..c3e08ad902 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -1,4 +1,4 @@ - +#lang(0.3.x) declare type Any declare type Anything diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index f5226bb9b8..4c2be5f681 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -150,6 +150,7 @@ callid1(Predef) //│ | listen: trm = [1] //│ | listenTerm: trm = 1 //│ | listen: trm = 1 +//│ | listen: trm = member:Num⁰ //│ | .exit InstanceShape(member:Num⁰) (↘⟨class:Int⁰⟩ϵ) //│ | | .exit InstanceShape(member:Num⁰) (ϵ) //│ | | = InstanceShape(member:Num⁰) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 75d893d3b7..b761060b50 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -467,14 +467,20 @@ abstract class MLsDiffMaker extends DiffMaker: case _ => () def processTrees(trees: Ls[syntax.Tree])(using Config, Raise): Unit = - val elab = Elaborator(etl, file.up, prelude) + val blk = new syntax.Tree.Block(trees) + val elaborationConfig = Config.elaborationConfig(blk) // val blockSymbol = // semantics.TopLevelSymbol("block#"+blockNum) blockNum += 1 // given Elaborator.Ctx = curCtx.nest(S(blockSymbol)) given Elaborator.Ctx = curCtx.nestLocal(s"block:${blockNum}") - val blk = new syntax.Tree.Block(trees) - val (e, newCtx) = elab.topLevel(blk) + // Match the compiler's prelude bootstrap: builtin lookup uses the original + // declaration symbols, including while those declarations are elaborated. + if file == preludeFile then prelude = summon[Elaborator.Ctx].withMembers(blk.definedSymbols) + val elab = + given Config = elaborationConfig + Elaborator(etl, file.up, prelude) + val (e, newCtx) = elaborationConfig.givenIn(elab.topLevel(blk)) curCtx = newCtx extractConfig(e.stats) From 4026001983268e913f36b2f2e0e753f08bc15ca9 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 14:23:48 +0800 Subject: [PATCH 122/228] Migrate parser token modules and remove the legacy Option copy --- doc/new-resolution-suite-migration.md | 46 ++++++++++++------- .../src/test/mlscript-compile/Block.mls | 6 +-- .../test/mlscript-compile/FingerTreeList.mls | 4 +- .../shared/src/test/mlscript-compile/Iter.mls | 4 +- .../test/mlscript-compile/LegacyOption.mls | 33 ------------- .../src/test/mlscript-compile/MutMap.mls | 4 +- .../src/test/mlscript-compile/Shape.mls | 6 +-- .../apps/parsing-web-demo/main.mls | 4 +- .../apps/parsing/BasicExpr.mls | 4 +- .../mlscript-compile/apps/parsing/Expr.mls | 4 +- .../apps/parsing/Extension.mls | 4 +- .../apps/parsing/Keywords.mls | 10 ++-- .../mlscript-compile/apps/parsing/Lexer.mls | 4 +- .../apps/parsing/ParseRule.mls | 12 ++--- .../apps/parsing/ParseRuleVisualizer.mls | 6 +-- .../mlscript-compile/apps/parsing/Parser.mls | 20 ++++---- .../apps/parsing/PrattParsing.mls | 4 +- .../apps/parsing/RecursiveDescent.mls | 4 +- .../mlscript-compile/apps/parsing/Rules.mls | 6 +-- .../mlscript-compile/apps/parsing/Test.mls | 4 +- .../mlscript-compile/apps/parsing/Token.mls | 6 ++- .../mlscript-compile/apps/parsing/Tree.mls | 4 +- .../apps/parsing/TreeHelpers.mls | 4 +- .../ups/EvaluationContext.mls | 4 +- .../test/mlscript/block-staging/PrintCode.mls | 4 +- .../test/mlscript/codegen/OpenWildcard.mls | 34 +++++++------- .../ups/examples/EvaluationContext.mls | 6 +-- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 5 +- 28 files changed, 120 insertions(+), 136 deletions(-) delete mode 100644 hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 2639188d5b..18d9511e8b 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -28,14 +28,13 @@ imply that its dependencies have been migrated. | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | -| Main (including quotes, UPS, and regression/compatibility fixtures) | 26 | 23 | 49 | -| Applications | 6 | 14 | 20 | +| Main (including quotes, UPS, and regression fixtures) | 26 | 22 | 48 | +| Applications | 8 | 12 | 20 | | Nofib | 12 | 27 | 39 | | WASM | 0 | 1 | 1 | -| Total | 44 | 65 | 109 | +| Total | 46 | 62 | 108 | -These totals include the new-resolution `NamedFieldLibrary` regression fixture -and the temporary legacy `LegacyOption` compatibility fixture. +These totals include the new-resolution `NamedFieldLibrary` regression fixture. ## Migration procedure @@ -64,12 +63,29 @@ and the temporary legacy `LegacyOption` compatibility fixture. ### Mixed-mode imports and fixture interfaces Legacy consumers can read completed new-resolution symbols in imported signatures. -The prelude uses new resolution. The parser dependency graph uses `LegacyOption` -temporarily; consumers exchanging `Some`/`None` values, including reflection and -`Block`, must share the same constructor identities. Port these consumers together -and delete `LegacyOption`, restoring `Option` imports. Retry `Token` and `Keywords` -with their consumers using the completed-symbol compatibility path. Imported nodes -must not be re-resolved, and erased types must not be queried before erasure. +The prelude, `Option`, `apps/parsing/Token`, and `apps/parsing/Keywords` use new +resolution; all option consumers share `Option`'s constructor identities. Imported +nodes must not be re-resolved, and erased types must not be queried before erasure. +Other parser implementation modules retain legacy resolution and need individual +migration checks even though their worksheets use new resolution. + +The remaining option consumers have these blockers when compiled with +`#lang(0.3.x)`: + +| Fixtures | Current blockers | +| --- | --- | +| `Block`, `Shape` | Missing nominal members and callback arity mismatches; `Block` also needs a public interface for `showArm`. | +| `Iter`, `MutMap`, `ups/EvaluationContext` | Missing public parameter interfaces and unresolved member selections. | +| `FingerTreeList` | Repeated `reduce` lookup errors on rest tuples; compilation exceeds the 25-second test limit. Convergence needs investigation. | +| `parsing/Extension`, `ParseRule`, `Test`, `TreeHelpers` | Repeated-entry mark assertion during nominal field projection: `NominalInstanceView.getMemberImpl` calls `captureType` on an argument whose path already contains that scope. Determine the source of the duplicate transfer without weakening the marks algebra. | +| `parsing/Lexer` | Opened binary `~` conflicts with the builtin; calls with trailing contextual parameters leave function values where tokens are expected. | +| `parsing/Parser` | Pattern-field flow and unresolved nominal members. | +| `parsing/ParseRuleVisualizer`, `Rules`, `parsing-web-demo/main` | Missing host/public interfaces and unresolved selections. | +| `parsing/Tree` | `JSON.stringify` has no result interface, so selecting `slice` on its result fails. | + +To reproduce a blocker, temporarily add the language directive to the named +compilation fixture and run `ctest ` or `catest ` as appropriate. +The blocked fixtures retain their existing resolution mode. Many fixtures also need source interfaces: for example, `QuoteExample.bind` calls an unannotated callback, and Nofib helpers expose comparators and printers. @@ -157,9 +173,8 @@ recursive UPS matchers. Preserve both runtime results and matcher structure. `ups/syntax/MixedParameters` without changing the selected symbol. Retry legacy worksheets and compilation fixtures against the current compiler and -prelude before diagnosing a blocker. Keep deferred fixtures on legacy resolution; -imports exchanging options must use `LegacyOption` consistently. The WASM fixture -`wasm/Wasm.mls` needs interfaces for `WebAssembly.Instance.exports` and its exposed +prelude before diagnosing a blocker. Keep deferred fixtures on legacy resolution. +The WASM fixture `wasm/Wasm.mls` needs interfaces for `WebAssembly.Instance.exports` and its exposed `wasmInst.imports` receiver. ## Validation and completion gates @@ -174,8 +189,7 @@ Follow the [repository test workflow](../README.md#running-the-tests-1): 3. Run `hkmc2AllTests/test` for each retained batch. Commit intentional golden updates together with the change, using the agent's identity for agent commits. 4. Remove legacy suite configurations only after all covered files migrate, - without new failure suppressions or lost negative diagnostics. Remove - `LegacyOption` once its consumers use the new interfaces consistently. + without new failure suppressions or lost negative diagnostics. Update the counts and remaining work after each retained batch. Test totals include configurations and regression tests, so they are not migration counts. diff --git a/hkmc2/shared/src/test/mlscript-compile/Block.mls b/hkmc2/shared/src/test/mlscript-compile/Block.mls index 39b31fe967..ef323a67d6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Block.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Block.mls @@ -1,15 +1,15 @@ import "./Predef.mls" -import "./LegacyOption.mls" +import "./Option.mls" import "./StrOps.mls" import "./Runtime.mls" open Predef open StrOps -open LegacyOption +open Option module Block with... -type Opt[A] = LegacyOption[A] +type Opt[A] = Option[A] // dependancies referenced in Block classes, referencing implementation in Term.mls diff --git a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls index f3e4e921f9..b9c84587af 100644 --- a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls +++ b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls @@ -1,7 +1,7 @@ -import "./LegacyOption.mls" +import "./Option.mls" import "./Iter.mls" -open LegacyOption +open Option open Iter open FingerTreeList diff --git a/hkmc2/shared/src/test/mlscript-compile/Iter.mls b/hkmc2/shared/src/test/mlscript-compile/Iter.mls index bbc4b4943d..78eb2ca8fd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Iter.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Iter.mls @@ -1,9 +1,9 @@ #config(liftDefns: Some(LiftDefns)) -import "./LegacyOption.mls" +import "./Option.mls" import "./Stack.mls" -open LegacyOption { Some, None } +open Option { Some, None } open Stack { Nil, Cons } diff --git a/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls b/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls deleted file mode 100644 index fb7648acdc..0000000000 --- a/hkmc2/shared/src/test/mlscript-compile/LegacyOption.mls +++ /dev/null @@ -1,33 +0,0 @@ -// Temporary copy for consumers that still inspect signatures with the legacy resolver. -// Delete this file when its consumers are ported to new resolution, and change -// their imports back to Option.mls. Consumers that exchange Some/None values -// must use the same copy so constructor identities agree. - - -type LegacyOption[A] = LegacyOption.{ Some[A] | None } - -module LegacyOption with ... - - -data class Some[T](value: T) -object None - -fun isDefined(x) = if x is - Some then true - None then false - -data class Both(fst, snd) - - -fun getOrElse(opt, default) = if opt is - Some(value) then value - None then default - -fun flatMap(opt, f) = if opt is - Some(value) then f(value) - None then None - -module unsafe with - fun get(opt) = if opt is - Some(value) then value - None then throw Error("None.get") diff --git a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls index dad5cc1a75..34ebbbf402 100644 --- a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls +++ b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls @@ -1,8 +1,8 @@ -import "./LegacyOption.mls" +import "./Option.mls" import "./Predef.mls" open Predef -open LegacyOption { Some, None } +open Option { Some, None } module MutMap with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/Shape.mls b/hkmc2/shared/src/test/mlscript-compile/Shape.mls index f24e53e3f0..d8a3dcca30 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Shape.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Shape.mls @@ -1,8 +1,8 @@ import "./Block.mls" -import "./LegacyOption.mls" +import "./Option.mls" open Block { Literal, ClassSymbol, showSymbol, isPrimitiveType, isPrimitiveTypeOf } -open LegacyOption +open Option type Shape = Shape.Shape @@ -57,7 +57,7 @@ fun silh(p: Case): Shape = if p is Class(sym, Array(size).fill(Dyn)) Block.Tup(n) then Arr(Array(n).fill(Dyn)) -// TODO: use LegacyOption instead, since all of them return at most one shape +// TODO: use Option instead, since all of them return at most one shape fun filter(s: Shape, p: Case): Array[Shape] = if [s, p] is [Lit(l1), Block.Lit(l2)] and l1 == l2 then [s] diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls index 71897207ad..c3fa60c204 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls @@ -16,7 +16,7 @@ import "../parsing/Lexer.mls" import "../../StrOps.mls" import "../../Iter.mls" import "../../XML.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Runtime.mls" import "../../Predef.mls" import "../parsing/TreeHelpers.mls" @@ -28,7 +28,7 @@ import "./Examples.mls" open XML { elem } open Predef -open LegacyOption { Some, None } +open Option { Some, None } open Examples module Main with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls index 5399e3f142..53cec827e8 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/BasicExpr.mls @@ -1,11 +1,11 @@ #lang(0.3.x) import "../../StrOps.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Predef.mls" open StrOps { parenthesizedIf } -open LegacyOption { Some, None } +open Option { Some, None } open Predef { mkStr } type OptionT[A] = Some[A] | None diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls index 6d58caa0b9..046cb30188 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Expr.mls @@ -1,11 +1,11 @@ #lang(0.3.x) import "../../StrOps.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Predef.mls" open StrOps { parenthesizedIf } -open LegacyOption { Some, None } +open Option { Some, None } type OptionT[A] = Some[A] | None diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls index 409677de87..345201a51f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls @@ -4,14 +4,14 @@ import "./Token.mls" import "./ParseRule.mls" import "./Tree.mls" import "../../Stack.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Predef.mls" import "../../Iter.mls" import "../../MutMap.mls" open Predef open Stack -open LegacyOption { Some, None } +open Option { Some, None } open Token { LiteralKind } open ParseRule { Choice } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls index f232afea39..d2bc4acfd2 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls @@ -1,10 +1,12 @@ -import "../../LegacyOption.mls" +#lang(0.3.x) + +import "../../Option.mls" import "../../Iter.mls" import "../../Predef.mls" import "../../MutMap.mls" open Predef -open LegacyOption { Some, None } +open Option { Some, None } module Keywords with ... @@ -105,7 +107,7 @@ let precMap = makePrecMap of "." // The largest precedence value of all operators. -val periodPrec = precMap |> MutMap.get(".") |> LegacyOption.unsafe.get +val periodPrec = precMap |> MutMap.get(".") |> Option.unsafe.get val _period = keyword of ".", Some(periodPrec), Some(periodPrec) @@ -114,7 +116,7 @@ val appPrec = maxOperatorPrec - 1 val prefixPrec = appPrec - 1 // Get the precedence of a single character operator. -fun charPrec(op) = precMap |> MutMap.get(op) |> LegacyOption.unsafe.get +fun charPrec(op) = precMap |> MutMap.get(op) |> Option.unsafe.get fun charPrecOpt(op) = precMap |> MutMap.get(op) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls index ddf68bdc8c..e4c2621aca 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls @@ -6,14 +6,14 @@ open Predef import "../../Char.mls" import "../../Stack.mls" import "../../StrOps.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Iter.mls" import "./Token.mls" open Stack open StrOps -open LegacyOption { Some, None} +open Option { Some, None} open Token { LiteralKind, LineLookupTable } type TokenType = Token.Token diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls index e10817809b..9570e97034 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls @@ -1,13 +1,13 @@ import "../../MutMap.mls" import "../../Iter.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Stack.mls" import "../../Predef.mls" import "./Keywords.mls" import "./Token.mls" import "./Tree.mls" -open LegacyOption { Some, None } +open Option { Some, None } open Predef { check, tuple } open Stack @@ -21,8 +21,8 @@ module Choice with data class Ref[A, B]( kind: Str, process: (Tree, B) -> A, - outerPrec: LegacyOption[Int], - innerPrec: LegacyOption[Int], + outerPrec: Option[Int], + innerPrec: Option[Int], rest: ParseRule[B] ) extends Choice[A] @@ -42,7 +42,7 @@ module Choice with init: ParseRule[B], optional: Bool, rest: ParseRule[C], - process: (LegacyOption[B], C) -> A + process: (Option[B], C) -> A ) extends Choice[A] let ensureChoices(xs, name) = xs @@ -130,7 +130,7 @@ module Choice with End(value) then End(op(value)) data class Lazy[out A](init: () -> A) with - mut val cached: LegacyOption[A] = None + mut val cached: Option[A] = None fun reset() = set cached = None fun get() = if cached is Some(v) then v diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls index 95af281b1e..e5293b9a8b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls @@ -1,7 +1,7 @@ import "../../Predef.mls" import "../../Stack.mls" import "../../Iter.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../TreeTracer.mls" import "../../XML.mls" import "../../MutMap.mls" @@ -12,7 +12,7 @@ import "./Parser.mls" open Predef open Stack -open LegacyOption +open Option open ParseRule { Choice } open Rules { syntaxKinds } open TreeTracer @@ -88,7 +88,7 @@ fun render[T](rr, title: Str, rule: ParseRule.ParseRule[T]) = referencedKinds = new Set() diagrams.push of ...currentKinds Iter.mapping of kind => - let theRule = syntaxKinds |> MutMap.get(kind) |> LegacyOption.unsafe.get + let theRule = syntaxKinds |> MutMap.get(kind) |> Option.unsafe.get [kind, diagram of renderRule(theRule)] Iter.toArray() diagrams diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls index 754cea2e1a..1b9244f435 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls @@ -1,5 +1,5 @@ import "../../Predef.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Stack.mls" import "../../TreeTracer.mls" import "../../Iter.mls" @@ -15,7 +15,7 @@ import "./Rules.mls" import "./ParseRule.mls" open Predef -open LegacyOption +open Option open Stack open ParseRule { Choice } open Choice { Ref } @@ -98,8 +98,8 @@ fun parse(tokens) = do tracer.print of "the current rule is " + rule.display rule.refChoice is Some(Ref(kind, process, outerPrec, innerPrec, rest)) and do tracer.print of "try to parse kind \"" + kind + "\" at " + TokenHelpers.preview(tokens), source.line - let outerPrec' = outerPrec LegacyOption.getOrElse(Keywords.maxKeywordPrec) - let innerPrec' = innerPrec LegacyOption.getOrElse(prec) + let outerPrec' = outerPrec Option.getOrElse(Keywords.maxKeywordPrec) + let innerPrec' = innerPrec Option.getOrElse(prec) outerPrec' > prec and let acc = parseKind(kind, prec) acc Tree.nonEmptyError() and parseRule(prec, rest) is @@ -164,7 +164,7 @@ fun parse(tokens) = Nil then Tree.error("unexpected end of input") fun exprCont(acc: Tree, prec: Int, options) = if tokens is - let infix = options.rule.refChoice LegacyOption.flatMap of case + let infix = options.rule.refChoice Option.flatMap of case Ref(kind, process, None, None, rest) and kind == options.kind then { process: process, rule: rest } else throw Error("Kind " + options.kind + " does not have infix rules") @@ -178,8 +178,8 @@ fun parse(tokens) = keyword.leftPrecOrMin > prec and rule.refChoice is Some(Ref(kind, process, outerPrec, innerPrec, rest)) and do tracer.print of "try to parse kind \"" + kind + "\" at " + TokenHelpers.preview(tokens), source.line - let outerPrec' = outerPrec LegacyOption.getOrElse(Keywords.maxOperatorPrec) - let innerPrec' = innerPrec LegacyOption.getOrElse(outerPrec') + let outerPrec' = outerPrec Option.getOrElse(Keywords.maxOperatorPrec) + let innerPrec' = innerPrec Option.getOrElse(outerPrec') outerPrec' > prec then consume let rhs = parseKind(kind, keyword.rightPrecOrMin) @@ -204,10 +204,10 @@ fun parse(tokens) = token :: _ and infix.rule.refChoice is Some(Ref(kind, process, outerPrec, innerPrec, rest)) and do tracer.print of "found reference to " + kind + " with outerPrec = " + outerPrec, source.line - let outerPrec' = outerPrec LegacyOption.getOrElse(Keywords.maxOperatorPrec) - let innerPrec' = innerPrec LegacyOption.getOrElse(outerPrec') + let outerPrec' = outerPrec Option.getOrElse(Keywords.maxOperatorPrec) + let innerPrec' = innerPrec Option.getOrElse(outerPrec') outerPrec' > prec and - parseKind(kind, innerPrec LegacyOption.getOrElse(outerPrec')) is + parseKind(kind, innerPrec Option.getOrElse(outerPrec')) is Tree.Empty then do tracer.print of "nothing was parsed", source.line acc diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/PrattParsing.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/PrattParsing.mls index 726b53fbe0..27e0866d1a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/PrattParsing.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/PrattParsing.mls @@ -2,7 +2,7 @@ import "../../Predef.mls" import "../../Stack.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Iter.mls" import "../../StrOps.mls" import "./Lexer.mls" @@ -10,7 +10,7 @@ import "./Token.mls" import "./Expr.mls" open Stack -open LegacyOption { Some, None } +open Option { Some, None } open Token { Round, LiteralKind } type Stack[A] = Cons[A] | Nil diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/RecursiveDescent.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/RecursiveDescent.mls index b60e6d167b..32a6c5dc95 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/RecursiveDescent.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/RecursiveDescent.mls @@ -2,14 +2,14 @@ import "../../Predef.mls" import "../../Stack.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Iter.mls" import "./Token.mls" import "./BasicExpr.mls" open Predef { mkStr } open Stack -open LegacyOption { Some, None } +open Option { Some, None } open Token { Round, LiteralKind } open BasicExpr { Expr } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls index 49297d108a..966ed0177b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls @@ -1,5 +1,5 @@ import "../../Predef.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Stack.mls" import "../../MutMap.mls" import "../../Iter.mls" @@ -9,7 +9,7 @@ import "./Tree.mls" import "./ParseRule.mls" open Predef -open LegacyOption +open Option open Stack open ParseRule { Choice, rule } open Token { Curly, Square, Round } @@ -24,7 +24,7 @@ val syntaxKinds = MutMap.empty val extendedKinds = new Set() -fun getRuleByKind(kind: Str): ParseRule.ParseRule[out Anything] = syntaxKinds |> MutMap.get(kind) |> LegacyOption.unsafe.get +fun getRuleByKind(kind: Str): ParseRule.ParseRule[out Anything] = syntaxKinds |> MutMap.get(kind) |> Option.unsafe.get fun define(name: Str)(...choices) = syntaxKinds |> MutMap.updateWith(name) of case None then Some(rule(name, ...choices)) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls index a3fe4513b5..e52fa72200 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls @@ -6,13 +6,13 @@ import "./TokenHelpers.mls" import "./TreeHelpers.mls" import "./Parser.mls" import "../../Iter.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Predef.mls" import "../../Stack.mls" open Token { LiteralKind } open Parser { parse, tracer } -open LegacyOption { Some, None } +open Option { Some, None } open Predef { print } open Stack open TreeHelpers { showAsTree } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls index 2dbf9dcaf4..1b7baa5882 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Token.mls @@ -1,7 +1,9 @@ -import "../../LegacyOption.mls" +#lang(0.3.x) + +import "../../Option.mls" import "../../Predef.mls" -open LegacyOption { Some, None } +open Option { Some, None } open Predef { mkStr } module Token with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls index 95fe5545b8..b6b62e9ccb 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls @@ -1,14 +1,14 @@ import "../../Iter.mls" import "../../Predef.mls" import "../../Stack.mls" -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../StrOps.mls" import "./Keywords.mls" import "./Token.mls" open Predef { fold, mkStr } open Stack -open LegacyOption { Some, None } +open Option { Some, None } open Token { LiteralKind, BracketKind } open Keywords { opPrec, INT_MAX } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls index 6f56def6c5..c2ec694112 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls @@ -1,4 +1,4 @@ -import "../../LegacyOption.mls" +import "../../Option.mls" import "../../Predef.mls" import "../../Stack.mls" import "./Tree.mls" @@ -8,7 +8,7 @@ import "./Token.mls" open Predef open Stack open Token { LiteralKind } -open LegacyOption { Some, None } +open Option { Some, None } module TreeHelpers with ... diff --git a/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls b/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls index 1cbd2d1763..6519116f08 100644 --- a/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls +++ b/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls @@ -1,6 +1,6 @@ -import "../LegacyOption.mls" +import "../Option.mls" -open LegacyOption { Some, None } +open Option { Some, None } open annotations module EvaluationContext with ... diff --git a/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls b/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls index bab6ea584f..621c69fca0 100644 --- a/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls +++ b/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls @@ -2,10 +2,10 @@ :js import "../../mlscript-compile/Block.mls" -import "../../mlscript-compile/LegacyOption.mls" +import "../../mlscript-compile/Option.mls" open Block -open LegacyOption +open Option printCode(FunDefn(Symbol("f"), [[Symbol("x")]], Return(ValueLit(1)))) //│ > fun f(x) = 1 diff --git a/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls b/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls index c981479d75..7db99c837b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/OpenWildcard.mls @@ -1,14 +1,14 @@ :js -import "../../mlscript-compile/LegacyOption.mls" +import "../../mlscript-compile/Option.mls" module OtherModule with val None = 123 :sjs -open LegacyOption +open Option //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -17,10 +17,10 @@ open LegacyOption None isDefined() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let x; -//│ x = LegacyOption.None; -//│ if (x instanceof LegacyOption.Some.class) { +//│ x = Option.None; +//│ if (x instanceof Option.Some.class) { //│ return true -//│ } else if (x instanceof LegacyOption.None.class) { +//│ } else if (x instanceof Option.None.class) { //│ return false //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -30,7 +30,7 @@ None isDefined() Some(1) isDefined() //│ = true -None === LegacyOption.None +None === Option.None //│ = true @@ -38,14 +38,14 @@ fun none() = None open OtherModule -None === LegacyOption.None +None === Option.None //│ = false -none() === LegacyOption.None +none() === Option.None //│ = true -open LegacyOption +open Option fun none() = None console.log(None) open OtherModule @@ -59,24 +59,24 @@ none() // * `open` statements are hygienic and are not affected by redefinitions :sjs -val LegacyOption = "Oops" +val Option = "Oops" //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let LegacyOption1; LegacyOption1 = "Oops"; +//│ let Option1; Option1 = "Oops"; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ LegacyOption = "Oops" +//│ Option = "Oops" :sjs Some(123) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return LegacyOption.Some(123) +//│ return Option.Some(123) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = Some(123) -module LegacyOption with +module Option with val None = 123 :sjs -open LegacyOption +open Option //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -84,14 +84,14 @@ open LegacyOption :sjs Some //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return LegacyOption.Some +//│ return Option.Some //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun Some { class: class Some } :sjs None //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return LegacyOption3.None +//│ return Option3.None //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls b/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls index 8acc483ace..57e4af7a1b 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls @@ -4,11 +4,11 @@ // Author: Luyu Cheng -import "../../../mlscript-compile/LegacyOption.mls" +import "../../../mlscript-compile/Option.mls" import "../../../mlscript-compile/MutMap.mls" open annotations -open LegacyOption { Some, None } +open Option { Some, None } data class Term with @@ -71,7 +71,7 @@ Var("a").freshVar("b") fun (<~>) alphaEquivalent(lhs, rhs) = let varMap = MutMap.empty - pattern Var' = Var(n) => varMap |> MutMap.get(n) |. LegacyOption.getOrElse(n) + pattern Var' = Var(n) => varMap |> MutMap.get(n) |. Option.getOrElse(n) fun go(lhs, rhs) = if lhs is Var(n) and rhs is Var' as n' then n === n' App(l, r) and r is App(l', r') then go(l, l') and go(r, r') diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index b761060b50..6da79ee983 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -20,9 +20,8 @@ abstract class MLsDiffMaker extends DiffMaker: val runtimeSourceFile: io.Path = predefFile.up / "Runtime.mls" // * Contains MLscript runtime sources val termFile: io.Path = predefFile.up / "Term.mjs" // * Contains MLscript runtime term definitions val blockFile: io.Path = predefFile.up / "Block.mjs" // * Contains MLscript runtime block definitions - // Reflection must use the same option constructors as Block.mls. Switch both - // back to Option when Block and its consumers no longer need LegacyOption. - val optionFile: io.Path = predefFile.up / "LegacyOption.mjs" + // Reflection must use the same option constructors as Block.mls. + val optionFile: io.Path = predefFile.up / "Option.mjs" val wd = file.up From ffdf119539bdf4eee7ca783e89addb6ce7f0355a Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 14:29:41 +0800 Subject: [PATCH 123/228] Remove redundant JVM compiler tests and prefer diff coverage --- AGENTS.md | 5 + doc/new-resolution-design.md | 7 +- doc/new-resolution-regular-types.md | 4 +- doc/new-resolution-type-value-flow.md | 4 +- .../test/scala/hkmc2/CompilerCacheTest.scala | 59 ---------- .../src/test/scala/hkmc2/ErasureTest.scala | 39 ------- .../test/scala/hkmc2/TypeFormulaTest.scala | 10 -- .../scala/hkmc2/TypeInstantiationTest.scala | 103 ------------------ .../test/scala/hkmc2/TypeRelationTest.scala | 89 +-------------- 9 files changed, 15 insertions(+), 305 deletions(-) delete mode 100644 hkmc2/jvm/src/test/scala/hkmc2/ErasureTest.scala diff --git a/AGENTS.md b/AGENTS.md index 95b16fe91e..88539b272c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,11 @@ Any commit that does not include the latest changes to test outputs will fail th Please also read the files in `.github/skills/hkmc2-difftests`. +Use `.mls` diff tests for compiler behavior. Do not add Scala tests that duplicate +that coverage or merely pin internal representation or assertion details. Reserve +unit tests for properties diff tests cannot reliably observe, such as concurrency, +cache isolation, bounded graph growth, or algebraic laws; state the extra coverage. + When you create a commit, author it under your (the agent's) own identity, not under the user's identity. Set this identity for the commit command only diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 4c0793c98a..4ce2667c5e 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -24,10 +24,9 @@ active against private host data, but cannot change completed reference targets. This applies even between worksheet blocks sharing an elaborator state. An unknown reaching a sealed elimination can still report an error without changing its target. -[`CompilerCacheTest`](../hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala) -checks independent consumers and unchanged source candidates, listeners, and -legacy annotations. [`PublisherTest`](../hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala) -covers cycles, reentrant replay, transitive private inference, and bounded copying +[`PublisherTest`](../hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala) +checks independent consumers and unchanged source candidates and listeners, +cycles, reentrant replay, transitive private inference, and bounded copying in the presence of unrelated exporter hosts. `newres/GenericMethods.mls` covers exported mapped arrays and imported generic functions and methods. diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index 00af29a7e1..2a458e1f5c 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -253,8 +253,8 @@ references, union/intersection saturation, Boolean-only recursion, and the varia counterexample. Rejected constructor cycles and the two documented precision limitations use ordinary `:e` expectations. -`TypeFormulaTest` checks normalization against Boolean truth tables and repeated -alternating substitution. `TypeRelationTest` checks a thousand repeated reductions, +`TypeFormulaTest` checks normalization against Boolean truth tables. +`TypeRelationTest` checks a thousand repeated reductions, unchanged binder allocation and source-listener counts, late bounds, whole negative union targets, and the loss of caller identity under captured alias rebasing. These checks support normalization's local invariants; they do not establish the whole diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index f381ab24ed..97bd342a93 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -265,9 +265,9 @@ targets and value shapes; runtime values acquire no type-argument objects. ## Validation -Graph tests cover instance counts and replay (`TypeInstantiationTest`), directed +Graph tests cover bounded instance allocation and replay (`TypeInstantiationTest`), directed relations, delayed targets and consumer isolation (`TypeRelationTest`), and Boolean -normalization (`TypeFormulaTest`). `CompilerCacheTest` checks exporter immutability. +normalization (`TypeFormulaTest`). `PublisherTest` checks exporter immutability. Worksheet coverage under `newres` includes `MutableArrays`, `ContextualInference`, `InstantiationSites`, `StoredSpecializations`, `SpecializationCaptures`, diff --git a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala index f138c35234..e0a2a344b4 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala @@ -78,65 +78,6 @@ class CompilerCacheTest extends AnyFunSuite: graph.withDependency(fileC, fileA)(cycle => assert(cycle == List(fileC, fileA, fileB, fileC))): fail("The dependency closing the cycle should not be requested") - test("prelude directives select resolution independently of the session default"): - import io.PlatformPath.given - import semantics.* - import utils.* - - val paths = TestFolders.compilerPaths(os.pwd) - val cfg = Config.default(TestFolders.mainTestDir(os.pwd)) - assert(!cfg.language.useNewResolution) - val compiler = CompilerCtx.fresh(io.FileSystem.default, paths, cfg) - given DebugPrinter = new DebugPrinter - given TL = new TraceLogger: - override def doTrace: Boolean = false - given Raise = diagnostic => fail(diagnostic.theMsg) - val prelude = compiler.getPrelude(paths.preludeFile) - def hasCapturedAnnotation(statement: Statement): Boolean = statement match - case capture: Term.Capture if capture.typeInterpretation.nonEmpty => true - case _ => statement.subStatements.exists(hasCapturedAnnotation) - assert(hasCapturedAnnotation(prelude.term)) - assert(compiler.getPrelude(paths.preludeFile) eq prelude) - assert(prelude.state.newResolverState.allocatedTypeInstanceCount == 0) - - test("legacy artifacts bind unused syntax hosts before consumers observe them"): - import io.PlatformPath.given - import semantics.* - import utils.* - - val tempDir = os.temp.dir(prefix = "compiler-cache-ownership-") - val file: io.Path = tempDir / "Library.mls" - os.write(tempDir / "Library.mls", - """|module Library with... - |fun identity(x) = x - |""".stripMargin) - try - val paths = TestFolders.compilerPaths(os.pwd) - val cfg = Config.default(TestFolders.mainTestDir(os.pwd)) - assert(!cfg.language.useNewResolution) - val compiler = CompilerCtx.fresh(io.FileSystem.default, paths, cfg) - given DebugPrinter = new DebugPrinter - given TL = new TraceLogger: - override def doTrace: Boolean = false - given Raise = diagnostic => fail(diagnostic.theMsg) - val prelude = compiler.getPrelude(paths.preludeFile) - val artifact = compiler.getElaboratedBlock(file, prelude.ctx) - val consumers = List.fill(2)(new Elaborator.State().newResolverState) - def check(statement: Statement, source: NewResolverState): Unit = - statement match - case term: Term => - assert(term.originalData.owner eq source) - assert(term.originalData.completed) - val before = term.shapeListeners.size - consumers.foreach: consumer => - term.inferenceHost(using consumer).subscribe(_ => ())(using consumer) - assert(term.shapeListeners.size == before) - case _ => () - statement.subStatements.foreach(check(_, source)) - check(prelude.term, prelude.state.newResolverState) - check(artifact.term, artifact.state.newResolverState) - finally os.remove.all(tempDir) - test("active dependency graph permits shared acyclic dependencies"): val graph = new ActiveDependencyGraph val fileA = io.Path("/A.mls") diff --git a/hkmc2/jvm/src/test/scala/hkmc2/ErasureTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/ErasureTest.scala deleted file mode 100644 index f07c48167a..0000000000 --- a/hkmc2/jvm/src/test/scala/hkmc2/ErasureTest.scala +++ /dev/null @@ -1,39 +0,0 @@ -package hkmc2 - -import org.scalatest.funsuite.AnyFunSuite - -import hkmc2.codegen.ErasedType -import hkmc2.semantics.{Elaborator, TermSymbol, VarSymbol} -import hkmc2.syntax.{Fun, Tree} -import hkmc2.utils.*, shorthands.* - - -class ErasureTest extends AnyFunSuite: - test("parameter type reads fail before erasure without caching an unknown representation"): - given Elaborator.State = new Elaborator.State - val parameter = VarSymbol(Tree.Ident("parameter")) - intercept[AssertionError](parameter.erasedType) - intercept[AssertionError](parameter.erasedValueType) - intercept[AssertionError](parameter.erasedValueType_!) - intercept[AssertionError](parameter.erasedType_!) - parameter.erasedType = S(ErasedType.Primitive(codegen.PrimitiveType.Int32)) - assert(parameter.erasedValueType == parameter.erasedType) - assert(parameter.erasedValueType_! == ErasedType.Primitive(codegen.PrimitiveType.Int32)) - assert(parameter.erasedType_! == parameter.erasedValueType_!) - intercept[AssertionError](parameter.erasedType = N) - - test("an unknown representation is initialized and cannot be initialized twice"): - given Elaborator.State = new Elaborator.State - val parameter = VarSymbol(Tree.Ident("unannotated")) - parameter.erasedType = N - assert(parameter.isErased) - assert(parameter.erasedValueType.isEmpty) - assert(parameter.erasedValueType_! == ErasedType.Unknown) - intercept[AssertionError](parameter.erasedType = S(ErasedType.Unknown)) - - test("definition result types cannot be read before erasure"): - given Elaborator.State = new Elaborator.State - val definition = TermSymbol(Fun, N, Tree.Ident("function")) - intercept[AssertionError](definition.declaredResultType) - definition.erasedType = S(ErasedType.FuncRef(S(false), Nil :: Nil, S(ErasedType.Unknown))) - assert(definition.declaredResultType.contains(ErasedType.Unknown)) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala index cba224a675..ac7eb01387 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala @@ -28,13 +28,3 @@ class TypeFormulaTest extends AnyFunSuite: assert(a.union(b).orderedAtoms == Vector(0, 1)) assert(b.union(a).orderedAtoms == Vector(1, 0)) assert(a.union(a.intersection(b)).orderedAtoms == Vector(0)) - - test("alternating recursive substitutions saturate without accumulating syntax"): - val a = TypeFormula.atom("A") - val b = TypeFormula.atom("B") - val c = TypeFormula.atom("C") - var value = a - val expected = a.union(b).intersection(c) - (1 to 1000).foreach: _ => - value = value.union(b).intersection(c) - assert(value == expected) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala index 98f956ed19..aa72b13f67 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala @@ -7,109 +7,6 @@ import hkmc2.semantics.* class TypeInstantiationTest extends AnyFunSuite: - private def allocatedInstances(source: String): Int = - import io.PlatformPath.given - val directory = os.temp.dir(prefix = "call-site-instances-") - val file: io.Path = directory / "Library.mls" - os.write(directory / "Library.mls", "#lang(0.3.x, strictResolution: true)\n" + source) - try - val paths = TestFolders.compilerPaths(os.pwd) - val compiler = CompilerCtx.fresh(io.FileSystem.default, paths, - Config.default(TestFolders.mainTestDir(os.pwd))) - given DebugPrinter = new DebugPrinter - given TL = new TraceLogger: - override def doTrace: Boolean = false - given Raise = diagnostic => fail(diagnostic.theMsg) - val prelude = compiler.getPrelude(paths.preludeFile) - val artifact = compiler.getElaboratedBlock(file, prelude.ctx) - assert(prelude.state.newResolverState.allocatedTypeInstanceCount == 0) - artifact.state.newResolverState.allocatedTypeInstanceCount - finally os.remove.all(directory) - - test("polymorphic declaration calls allocate by application rather than shared reference"): - assert(allocatedInstances( - """|module Library with - | fun identity[A](x: A): A = x - | val shared = identity - | val first = shared(1) - | val second = shared("two") - |""".stripMargin) == 2) - - test("constructor instances belong to explicit specializations or unspecialized term applications"): - val cases = List( - """|class Box[T](val item: T) - |private val specialized = Box[Int] - |private val deferred = new Box[Int] - |""".stripMargin -> 2, - """|class Box[T](val item: T) - |private val shared = Box - |private val specialized = shared[Int] - |private val direct = specialized(1) - |private val explicit = new specialized(2) - |""".stripMargin -> 1, - """|class Box[T](val item: T) - |private val deferred = new Box[Int] - |private val first = deferred(1) - |private val second = deferred(2) - |""".stripMargin -> 1, - """|class Box[T](val item: T)(val other: Int) - |private val partial = new Box[Int](1) - |private val first = partial(2) - |private val second = partial(3) - |""".stripMargin -> 1, - """|class Box[T] with - | fun identity(x: T): T = x - |private val first = new Box[Int] - |private val second = new Box[Str] - |""".stripMargin -> 2) - cases.foreach: (source, count) => - withClue(source): - assert(allocatedInstances(source) == count) - - test("specializations consume binders before any later calls or observations"): - val definitions = List( - "private fun identity[A](x: A) = x", - "private fun identity[A](x: A): A = x") - definitions.foreach: definition => - val source = definition + "\nprivate val specialized = identity[Int]\n" - assert(allocatedInstances(source) == 1) - assert(allocatedInstances(source + - "private val first = specialized(1)\nprivate val second = specialized(2)\n") == 1) - - test("by-name invocations consume binders once, with explicit arguments choosing the site"): - val definitions = List( - "private fun make[A] = (x: A) => x", - "private fun make[A]: A -> A = (x: A) => x", - "private fun make: [A] -> A -> A\nprivate fun make = x => x") - definitions.foreach: definition => - List("make", "make[Int]").foreach: invocation => - val source = definition + "\nprivate val shared = " + invocation + "\n" - withClue(source): - assert(allocatedInstances(source) == 1) - assert(allocatedInstances(source + - "private val first = shared(1)\nprivate val second = shared(2)\n") == 1) - assert(allocatedInstances(source + "private val other = " + invocation + "\n") == 2) - - test("selected by-name methods retain the invocation's group through nominal views"): - List("factory.make", "factory.make[Int]").foreach: invocation => - val source = """|class Factory with - | fun make[A]: A -> A = (x: A) => x - |private val factory: Factory = new Factory - |""".stripMargin + "private val shared = " + invocation + "\n" - withClue(source): - assert(allocatedInstances(source) == 1) - assert(allocatedInstances(source + - "private val first = shared(1)\nprivate val second = shared(2)\n") == 1) - assert(allocatedInstances(source + "private val other = " + invocation + "\n") == 2) - - test("a separately quantified result retains its own scheme after a by-name invocation"): - val source = """|private fun make: [A] -> A -> A = x => x - |private val shared = make - |""".stripMargin - assert(allocatedInstances(source) == 0) - assert(allocatedInstances(source + - "private val first = shared(1)\nprivate val second = shared(2)\n") == 2) - test("a definition and static site allocate their binder group once"): given owner: Elaborator.State = new Elaborator.State val state = owner.newResolverState diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index b2d307d1d6..075bd78f4b 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -56,33 +56,6 @@ class TypeRelationTest extends AnyFunSuite: assert((a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) assert(seen.toList == List(first, second)) - test("wildcard exit and reentry forget early and late caller identities"): - val h = new Harness - import h.given - val (a, at) = h.parameter("A") - val owner = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("owner"))) - val firstSite = FlowSymbol.app() - val secondSite = FlowSymbol.app() - val exit = ExitMark(owner, N, NoMarks) :: Nil - val enter = EntryMark(owner, N, NoMarks) :: Nil - val outside = h.resolver.transportType(at, exit) - val back = h.resolver.transportType(outside, enter) - assert(back != at) - val first = IntroShape(Term.UnitVal(), N) - val second = DynShape() - h.resolver.publishParameter(a, MarkedShape.enter(first, owner, S(firstSite))) - val inside = h.observe(ContextualType(back, Nil)) - val seen = h.observe(ContextualType(back, ExitMark(owner, S(firstSite), NoMarks) :: Nil)) - h.resolver.publishParameter(a, MarkedShape.enter(second, owner, S(secondSite))) - assert(inside.toList == List(first, second).map(MarkedShape.enter(_, owner, N))) - assert(seen.toList == List(first, second)) - val listeners = a.inferenceHost.listeners.size - (1 to 1000).foreach: _ => - assert(h.resolver.transportType(at, exit) eq outside) - assert(h.resolver.transportType(outside, enter) eq back) - assert(a.inferenceHost.listeners.size == listeners) - assert(h.state.allocatedTypeInstanceCount == 0) - test("reference exit and reentry agree with value transport for wildcard and explicit sites"): // Check both publication orders and every combination of wildcard/explicit // exit, reentry, and consumer sites against the ordinary value operations. @@ -114,21 +87,6 @@ class TypeRelationTest extends AnyFunSuite: assert(h.observe(ContextualType(consumed, Nil)).toSet == expected(List(first, second), List(exit, entry, consumer))) - test("reference rebasing never cancels explicit invocation entries"): - val h = new Harness - import h.given - val (a, at) = h.parameter("A") - val owner = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("owner"))) - val original = FlowSymbol.app() - val invoked = FlowSymbol.app() - val outside = h.resolver.transportType(at, ExitMark(owner, S(original), NoMarks) :: Nil) - val inside = h.resolver.transportType(outside, EntryMark(owner, S(invoked), NoMarks) :: Nil) - assert(inside != at) - val bound = IntroShape(Term.UnitVal(), N) - h.resolver.publishParameter(a, MarkedShape.enter(bound, owner, S(original))) - val seen = h.observe(ContextualType(inside, Nil)) - assert(seen.toList == List(MarkedShape.enter(bound, owner, S(invoked)))) - test("alias argument normalization reaches a fixed point without changing its marks"): val h = new Harness import h.given @@ -342,29 +300,6 @@ class TypeRelationTest extends AnyFunSuite: assert(seen.toList == List(value)) assert(h.state.allocatedTypeInstanceCount == 0) - test("omitted arguments reuse source-owned holes and wait for evidence"): - val h = new Harness - import h.given - val (a, _) = h.parameter("A") - val (b, _) = h.parameter("B") - val firstUse = h.tpe(TypeShape.Abstract).resolution - val secondUse = h.tpe(TypeShape.Abstract).resolution - val hole = h.resolver.omittedType(firstUse, a) - val otherPosition = h.resolver.omittedType(firstUse, b) - val otherUse = h.resolver.omittedType(secondUse, a) - assert(!(hole.resolution eq otherPosition.resolution)) - assert(!(hole.resolution eq otherUse.resolution)) - val seen = h.observe(ContextualType(hole, Nil)) - val unrelated = h.observe(ContextualType(otherPosition, Nil)) - assert(seen.isEmpty) - val bound = IntroShape(Term.UnitVal(), N) - h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil), ContextualType(hole, Nil)) - assert(seen.toList == List(bound)) - assert(unrelated.isEmpty) - (1 to 1000).foreach: _ => - assert(h.resolver.omittedType(firstUse, a) eq hole) - assert(h.state.allocatedTypeInstanceCount == 0) - test("cyclic hole constraints saturate without allocating parameter instances"): val h = new Harness import h.given @@ -423,6 +358,9 @@ class TypeRelationTest extends AnyFunSuite: assert(right.allocatedTypeInstanceCount == 0) detach() + // Primitive incompatibilities do not yet produce diagnostics, so diff tests + // cannot establish that these input obligations were recorded at all. Check + // the constraint targets directly, including a whole union versus its parts. test("each concrete upper target receives later bounds and a union stays whole"): val h = new Harness import h.given @@ -542,27 +480,6 @@ class TypeRelationTest extends AnyFunSuite: assert(a.currentShapes.toSet == Set(InstanceShape(bt))) assert(b.currentShapes.toSet == Set(InstanceShape(at), InstanceShape(concrete))) - test("reverse relations retain each endpoint's activation and exclude another caller"): - val h = new Harness - import h.given - val (a, at) = h.parameter("A") - val (b, bt) = h.parameter("B") - val source = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("source"))) - val target = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("target"))) - val site = FlowSymbol.app() - val other = FlowSymbol.app() - val sourceMarks = ExitMark(source, S(site), NoMarks) :: Nil - val targetMarks = ExitMark(target, S(site), NoMarks) :: Nil - val ar = ContextualType(at, sourceMarks) - val br = ContextualType(bt, targetMarks) - h.resolver.constrainTypes(ar, br) - h.resolver.constrainTypes(br, ar) - val seen = h.observe(ar) - val accepted = IntroShape(Term.UnitVal(), N) - h.resolver.publishParameter(b, accepted.enter(targetMarks)) - h.resolver.publishParameter(b, MarkedShape.enter(DynShape(), target, S(other))) - assert(seen.toList == List(accepted)) - test("repeated deferred views reuse source identities for synthesized interfaces"): val h = new Harness import h.given From e7fa5064c5b5751b872c66b6e9ae8d7d8528ac7a Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 14:55:46 +0800 Subject: [PATCH 124/228] Test resolution mark laws and document type-graph review failures --- doc/new-resolution-future-work.md | 85 ++++++++++++++++-- doc/new-resolution-regular-types.md | 14 +++ doc/new-resolution-suite-migration.md | 3 +- doc/new-resolution-type-value-flow.md | 44 +++++++--- .../jvm/src/test/scala/hkmc2/MarksTest.scala | 87 +++++++++++++++++++ .../test/scala/hkmc2/TypeFormulaTest.scala | 24 +++++ .../main/scala/hkmc2/semantics/Shape.scala | 2 +- .../newres/InheritedTypeArguments.mls | 39 +++++++++ .../test/mlscript/newres/QuantifiedBounds.mls | 34 ++++++++ .../newres/RecursiveBooleanInterfaces.mls | 28 ++++++ .../newres/TypeGraphPartialAliases.mls | 50 +++++++++++ .../newres/TypeGraphProjectionSites.mls | 38 ++++++++ .../mlscript/newres/TypeGraphTermination.mls | 20 +++++ 13 files changed, 451 insertions(+), 17 deletions(-) create mode 100644 hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala create mode 100644 hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/TypeGraphPartialAliases.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/TypeGraphProjectionSites.mls diff --git a/doc/new-resolution-future-work.md b/doc/new-resolution-future-work.md index c22d41ea90..3c4c770c46 100644 --- a/doc/new-resolution-future-work.md +++ b/doc/new-resolution-future-work.md @@ -1,10 +1,84 @@ # Deferred resolution design work -These improvements are deferred, not completion blockers for the current -implementation batch. They require design review before implementation. Current +This reference records confirmed implementation gaps and deferred design work. +The convergence failures below must be corrected before claiming that accepted +type graphs are finite. Other design refinements require review before implementation. Current contracts are in [instance types and parameter constraints](new-resolution-type-value-flow.md); remaining suite ports are in the [migration worklist](new-resolution-suite-migration.md). +## Confirmed convergence failures + +### Partially supplied forwarding aliases + +```mlscript +type First[A, B] = A +type Chain[A] = {value: A, next: Chain[First[A]]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () +``` + +This has the same finite unfolding as `Chain[A] = {value: A, next: Chain[A]}`. +`declaredType` reduces an applied alias only when every formal is supplied, whereas +deferred interpretation fills omissions with source holes. Keeping `First[A]` as +an argument retains another binding environment on each recursive constraint; +eventually hashing `DeclaredType` overflows. Supplying the unused `B` makes the +example terminate. The same failure occurs through a forwarding alias and with +argument permutations. Unify the argument-binding rules while preserving omission +identities, variance, captures, and the alias expansion guard. +Regressions: `newres/TypeGraphPartialAliases.mls`. + +### Fresh sites during structural member constraints + +```mlscript +type Link = {next: Link} +class Node with + fun next[A] = this +private fun take(x: Link) = () +take(new Node) +``` + +`constrainRecord` allocates a fresh `FlowSymbol.memSym` for the expected field on +every visit. Selecting `next` invokes the by-name definition and uses this new +symbol as its instantiation site. The resulting substitution distinguishes another +receiver view, which repeats the structural constraint and allocates again. +The definition/site cache cannot bound allocation when the sites themselves grow. +An unused binder is sufficient; removing it makes the example terminate. Declared +results and separate quantified signatures also reproduce the overflow. +Synthetic projections need stable identities tied to the finite source graph, +with ordinary marks retaining activation separation. +Regressions: `newres/TypeGraphProjectionSites.mls`. + +## Quantified bounds + +`TypeQuantifier` and `DeclaredTypeParameter` store lower and upper bounds, but +`instantiateCallable` substitutes only the parameter/result types and supplied +arguments. It never installs the bounds as relations on the new instances. +For example, a result of `[A extends Item] -> () -> A` does not expose `Item`'s +members when the type argument is inferred. A separately signed implementation +also cannot use its parameter's declared upper-bound interface. This is distinct +from postponing concrete mismatch diagnostics: the constraint edges and checking +interfaces themselves are missing. Regressions: `newres/QuantifiedBounds.mls`. + +## Inherited type-argument constraints + +Matching an actual subclass against `Parent[A]` currently leaves `A` unconstrained. +The nominal branch of `inferTypeArguments` compares exact class identities and +does not follow the instantiated parent view. Thus `read[A](p: Parent[A]) = p.item` +loses its result interface for both constructed and declared `Child[Item]` inputs, +while `Parent[Item]` works. The field has a complete signature, so this is separate +from inferred member signatures. The exact-class restriction predates the current +type-relation implementation. Follow the parent's bindings and scope transfers +when installing both variance directions. Regressions: `newres/InheritedTypeArguments.mls`. + +## Productive Boolean alias cycles + +The alias observation guard publishes an opaque candidate when revisiting an alias. +For `Choice[A] = A | Choice[A]`, this adds an unknown alternative alongside `A`, +preventing member lookup even though the productive unfolding exposes only `A`. +This behavior predates the current Boolean normalization. A correction must also +specify unproductive cycles and recursive intersections; simply removing every +opaque candidate is not a general solution. Regression: `newres/RecursiveBooleanInterfaces.mls`. + ## Receiver reconstruction ```mlscript @@ -95,9 +169,10 @@ conservative implementation. ## Whole-graph convergence audit -Finite binder allocation, source dependency analysis, and relation replay have -local bounds. A complete argument must also bound accepted contextual reference -keys, nested environments, and formula atoms, then establish listener convergence. +Binder allocation for fixed definition/site keys, source dependency analysis, +and relation replay have local bounds. A complete argument must also bound the +sites themselves, accepted contextual reference keys, nested environments, and +formula atoms, then establish listener convergence. Retain marked parameter references and shared recursive edges; do not expand live bounds into fresh type trees. Test delayed delivery, mutually recursive calls, changing compound arguments, and independent consumers at the graph level. diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index 2a458e1f5c..9fca291f85 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -94,6 +94,15 @@ This does **not** by itself bound the atoms. Constructor growth, retained argume parts that never become observable, and the existing requirements on finite marked contexts remain separate obligations. +In particular, eager reduction currently excludes partially supplied aliases. +With `type First[A, B] = A`, the recursive argument `First[A]` is semantically +identical to `A`, but retains a new environment on each recursive constraint. +`TypeGraphPartialAliases.mls` records the resulting stack overflow alongside the +terminating `First[A, Int]` control. Passing the conservative regularity check +therefore does not currently imply that the implementation reaches a fixed point. +Omitted positions must keep their source-hole identities when normalization is +extended; allocating holes during each expansion would create another source of growth. + ## Conservative check The check connects original formals through alias applications in the reachable @@ -135,6 +144,11 @@ The constructor inside the input part is not an observable growing component of this structural unfolding. A binder-only edge from `A` to `A` loses that fact. The worksheet records the rejection explicitly as a current precision limitation. +The implementation also labels written wildcard dependencies as constructor-bearing, +so even `next: Chain[out A]` is rejected although it contains no growing constructor +and every positive `value` component remains `A`. The same worksheet records this +simpler false positive; the diagnostic's constructor explanation is conservative too. + The representation needs the same distinction. Current free-binder projection retains the whole bound argument whenever a formal is relevant, including parts not selected by the structural body. Recursive observation can therefore accumulate diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 18d9511e8b..880b92b7b4 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -104,7 +104,8 @@ headers identify the remaining legacy fixtures. receiver reconstruction, inferred missing member types through nominal views, omitted-argument caller separation, and a more precise regularity check. These do not block the current implementation batch. The whole-graph convergence audit - remains open; local allocation and replay bounds are documented in the + has found [concrete termination failures](new-resolution-future-work.md#confirmed-convergence-failures); + local allocation and replay bounds are documented in the [type-flow reference](new-resolution-type-value-flow.md#canonical-references-and-termination-obligations). For broader mutation migration, decide how reassignment changes storage's inferred interface, including across worksheet blocks. `newres/MutationFlow.mls` diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 97bd342a93..f36b36c858 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -79,9 +79,13 @@ constructor views use the same original owner. An anonymous polymorphic annotati uses its source scheme, and each alternative of an overload has its own owner. `TypeShape.Polymorphic` and `DeclaredTypeParameter` retain the original binders and -their bounds. Apply the site's substitution to bounds, inputs, results, and nested -callbacks before interface expansion. Enclosing binders remain lexical captures, -not binders of the nested definition being instantiated. +their bounds. Inputs, results, and nested callbacks receive the site's substitution +before interface expansion. Bounds are currently retained only as metadata: +`instantiateCallable` does not install their constraints. Applying the same +substitution to bounds and connecting them to the instances remains a +[required correction](new-resolution-future-work.md#quantified-bounds). +Enclosing binders remain lexical captures, not binders of the nested definition +being instantiated. ### Partial application and explicit specialization @@ -194,6 +198,15 @@ The relevant scope crossings are: - Constructor invocation: use the same instance boundary for its class and constructor, including later parameter lists. +`Marks` stores the most recent crossing first. `shape.exit(path)` applies the +tail before the head; a list of path fragments is applied from left to right. +`shape.enter(fragments)` reverses both directions and fragment order. Thus +`shape.enter(p :: q :: Nil)` agrees with `shape.enter(q).enter(p)`. +For one boundary, entering at site `i` and then exiting at site `j` cancels when +either site is absent or the sites agree, and rejects the candidate otherwise. +Exiting and then entering retains both crossings. Associativity of composition +does not make these two operations mutual inverses. + Alias qualification and structural type-field projection introduce no value scope. Modules introduce no invocation boundary. Transport must follow the source reference's scope, including references nested inside structured types; inspecting @@ -252,12 +265,19 @@ environments. Structural recursion and recursive generic function constraints ar different: a call can add an edge to a reusable parameter instance without eagerly unfolding its accumulated bounds. -The following local bounds hold: finitely many definition/site binder instances, -source holes, flat binder substitutions, and normalized paths over distinct lexical -boundaries. Formula normalization is finite for a fixed atom set. These bounds do -not alone establish finiteness of nested binding environments or the atom set. -A whole-graph termination argument still needs to bound all accepted reference keys -and demonstrate listener convergence. Cache keys must never contain growing +For a fixed set of instantiation sites, the binder cache allocates finitely many +instances, so flat substitutions over the original binders also have a finite range. +Source holes have stable identities. Normalized paths contain distinct lexical +boundaries in each direction; their bounded length gives finitely many paths only +when their site labels also range over a finite set. +Formula normalization is finite for a fixed atom set. These bounds do not alone +establish finiteness of nested binding environments or the atom set. + +There are now [concrete counterexamples](new-resolution-future-work.md#confirmed-convergence-failures): +structural member constraints manufacture fresh invocation sites, and partially +supplied forwarding aliases retain growing environments. Both can overflow on +semantically finite recursive types. A whole-graph termination argument must cover +these paths as well as listener convergence. Cache keys must never contain growing substitution histories; depth limits and dropped marks do not establish a fixed point. These representations are internal to resolution. Lowering consumes completed @@ -267,7 +287,11 @@ targets and value shapes; runtime values acquire no type-argument objects. Graph tests cover bounded instance allocation and replay (`TypeInstantiationTest`), directed relations, delayed targets and consumer isolation (`TypeRelationTest`), and Boolean -normalization (`TypeFormulaTest`). `PublisherTest` checks exporter immutability. +normalization, including substitutions that identify atoms (`TypeFormulaTest`). +`MarksTest` checks activation matching, normalized composition, regrouping, reverse +transport, and wildcard identity loss across nested and sibling scopes. +These algebraic checks cover combinations that worksheet examples cannot exhaust. +`PublisherTest` checks exporter immutability. Worksheet coverage under `newres` includes `MutableArrays`, `ContextualInference`, `InstantiationSites`, `StoredSpecializations`, `SpecializationCaptures`, diff --git a/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala new file mode 100644 index 0000000000..f90230607b --- /dev/null +++ b/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala @@ -0,0 +1,87 @@ +package hkmc2 +package semantics + +import org.scalatest.funsuite.AnyFunSuite +import hkmc2.syntax.{Fun, Tree} +import hkmc2.utils.*, shorthands.* + + +/** Algebraic laws over paths and activation sites complement worksheet examples: + * in particular, check all wildcard/identified combinations and regroupings. + */ +class MarksTest extends AnyFunSuite: + private class Harness: + given Elaborator.State = new Elaborator.State + given DebugPrinter = new DebugPrinter + given TraceLogger = new TraceLogger: + override def doTrace: Boolean = false + val value = DynShape() + val outer = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("outer"))) + val left = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("left"))) + val right = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("right"))) + val scopes = List(Nil, List(outer), List(outer, left), List(outer, right)) + val sites = List(N, S(FlowSymbol.app()), S(FlowSymbol.app())) + def entries(scopes: List[ResolutionBoundary], site: Opt[FlowSymbol]): Marks = + scopes.foldLeft[Marks](NoMarks)((rest, boundary) => EntryMark(boundary, site, rest)) + def exits(scopes: List[ResolutionBoundary], site: Opt[FlowSymbol]): Marks = + scopes.foldRight[ExitMarks](NoMarks)((boundary, rest) => ExitMark(boundary, site, rest)) + def path(from: List[ResolutionBoundary], to: List[ResolutionBoundary], + exitSite: Opt[FlowSymbol], entrySite: Opt[FlowSymbol]): List[Marks] = + List(exits(from, exitSite), entries(to, entrySite)) + def normalized(path: List[Marks]): List[Marks] = value.exit(path) match + case Marked(_, NoMarks) => Nil + case Marked(_, marks) => List(marks) + case NoShape => fail("A path with no cancellable entry cannot reject its source") + + test("scope transport agrees with matching activation stacks"): + val h = new Harness + import h.given + for from <- h.scopes; to <- h.scopes; origin <- h.sites; leaving <- h.sites; arriving <- h.sites do + val input = h.value.exit(h.entries(from, origin)) + val path = h.path(from, to, leaving, arriving) + // Exiting consumes the entire input stack. A capture matches either site; + // otherwise different identified activations have disjoint value flow. + val compatible = from.isEmpty || origin.isEmpty || leaving.isEmpty || origin == leaving + val expected = if compatible then h.value.exit(h.entries(to, arriving)) else NoShape + assert(input.exit(path) == expected) + assert(input.exit(h.normalized(path)) == expected) + + test("normalizing fragments preserves composition and argument transport reverses their order"): + val h = new Harness + import h.given + for from <- h.scopes; via <- h.scopes; to <- h.scopes; first <- h.sites; second <- h.sites do + val p = h.path(from, via, first, first) + val q = h.path(via, to, second, second) + val parts = h.normalized(p) ::: h.normalized(q) + val input = h.value.exit(h.entries(from, first)) + assert(input.exit(p ::: q) == input.exit(parts)) + assert(input.exit(parts) == input.exit(h.normalized(p)).exit(h.normalized(q))) + val output = h.value.exit(h.entries(to, second)) + assert(output.enter(parts) == output.enter(h.normalized(q)).enter(h.normalized(p))) + assert(output.enter(p ::: q) == output.enter(parts)) + + test("regrouping three normalized paths preserves filtering"): + val h = new Harness + import h.given + def compose(value: TermShape | NoShape, path: TermShape | NoShape): TermShape | NoShape = path match + case NoShape => NoShape + case Marked(_, marks) => value.exit(marks) + for start <- h.scopes; middle <- h.scopes; finish <- h.scopes + a <- h.sites; b <- h.sites; c <- h.sites do + val p = h.value.exit(h.path(start, middle, a, b)) + val q = h.value.exit(h.path(middle, finish, c, a)) + val r = h.value.exit(h.path(finish, start, b, c)) + assert(compose(compose(p, q), r) == compose(p, compose(q, r))) + + test("wildcard exit followed by entry preserves provenance but loses the consumed site"): + val h = new Harness + import h.given + val scope = List(h.outer) + val source = h.value.exit(h.entries(scope, h.sites(1))) + val transferred = source.exit(h.path(scope, scope, N, N)) + assert(transferred != source) + assert(source.exit(h.exits(scope, h.sites(2))) == NoShape) + assert(transferred.exit(h.exits(scope, h.sites(2))) == h.value) + val uncancelled = h.value.exit(h.path(scope, scope, N, N)) + assert(uncancelled != h.value) + assert(uncancelled.exit(h.exits(scope, h.sites(2))) == h.value.exit(h.exits(scope, N))) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala index ac7eb01387..e43f39fd1e 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeFormulaTest.scala @@ -28,3 +28,27 @@ class TypeFormulaTest extends AnyFunSuite: assert(a.union(b).orderedAtoms == Vector(0, 1)) assert(b.union(a).orderedAtoms == Vector(1, 0)) assert(a.union(a.intersection(b)).orderedAtoms == Vector(0)) + + test("all three-atom formulas retain their meaning when substitution identifies atoms"): + // Enumerate every DNF over three atoms, including the empty clause (top) + // and no clauses (bottom). This checks absorption after a non-injective map, + // which recursive substitutions can cause even if the source was reduced. + val clauses = (0 until 8).map: bits => + (0 until 3).filter(atom => (bits & (1 << atom)) != 0).foldLeft(TypeFormula.top[Int]): (clause, atom) => + clause.intersection(TypeFormula.atom(atom)) + val formulas = (0 until 256).map: bits => + clauses.zipWithIndex.filter((_, index) => (bits & (1 << index)) != 0).foldLeft(TypeFormula.bottom[Int]): + case (formula, (clause, _)) => formula.union(clause) + .distinct + assert(formulas.size == 20) + for formula <- formulas; a <- 0 until 3; b <- 0 until 3; c <- 0 until 3 do + val substitution = Vector(a, b, c) + val mapped = formula.map(substitution) + assert(mapped.orderedAtoms.toSet == mapped.atoms) + assert(mapped.orderedAtoms.distinct == mapped.orderedAtoms) + val second = Vector(1, 1, 0) + assert(mapped.map(second) == formula.map(atom => second(substitution(atom)))) + for assignment <- 0 until 8 do + val expected = formula.clauses.exists(_.forall(atom => (assignment & (1 << substitution(atom))) != 0)) + val actual = mapped.clauses.exists(_.forall(atom => (assignment & (1 << atom)) != 0)) + assert(actual == expected) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 875aeababa..f5caca8733 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -58,7 +58,7 @@ object ResolutionBoundary: // of allowing repeated boundaries to accumulate in contextual reference keys. private val checkMarkPaths = true -/** A reduced lexical path: entries followed by exits, stored outermost first. +/** A reduced lexical path: entries followed by exits, most recent crossing first. * Exiting cancels the leading entry when their sites agree (an absent site is * a capture, compatible with any activation). Entering never cancels an exit: * that pair records an inner value's provenance until a consumer accesses it. diff --git a/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls b/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls new file mode 100644 index 0000000000..7ae1d26487 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls @@ -0,0 +1,39 @@ +:. + +// Control: an exact nominal match transfers the supplied argument to read's A. +:expect 1 +class Item(val value: Int) +class Parent[A](val item: A) +private fun read[A](parent: Parent[A]) = parent.item +read(new Parent[Item](Item(1))).value +//│ = 1 + +// FIXME: A constructed subclass must transfer its instantiated Parent argument. +// The field signature is complete, so this does not require inferring members. +:fixme +:expect 2 +class Item(val value: Int) +class Parent[A](val item: A) +class Child[A](item: A) extends Parent[A](item) +private fun read[A](parent: Parent[A]) = parent.item +read(new Child[Item](Item(2))).value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.19: read(new Child[Item](Item(2))).value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' + +// FIXME: A declared subclass interface must likewise follow its parent view. +:fixme +:expect 3 +class Item(val value: Int) +class Parent[A](val item: A) +class Child[A](item: A) extends Parent[A](item) +private fun read[A](parent: Parent[A]) = parent.item +private fun child(): Child[Item] = new Child[Item](Item(3)) +read(child()).value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.34: read(child()).value +//│ ╙── ^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls b/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls new file mode 100644 index 0000000000..de5cdd322e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls @@ -0,0 +1,34 @@ +:. + +// Control: an explicit type argument supplies the declared member interface. +class Item(val value: Int) +declare fun make: [A extends Item] -> () -> A +private fun read() = make[Item]().value + +// FIXME: Every result satisfies the upper bound, even without explicit arguments. +// Quantified bounds are currently retained as metadata but not used by inference. +:fixme +class Item(val value: Int) +declare fun make: [A extends Item] -> () -> A +private fun read() = make().value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.13: private fun read() = make().value +//│ ╙── ^^^^^^^^^^^^ + +// FIXME: A generic implementation should also be checked using its upper bound. +:fixme +class Item(val value: Int) +fun read: [A extends Item] -> A -> Int +fun read(x) = x.value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.22: fun read(x) = x.value +//│ ╙── ^^^^^^^ + +// A lower bound must supply an inference constraint before the result is used. +:fixme +class Item(val value: Int) +declare fun make: [A restricts Item] -> () -> A +private fun read() = make().value +//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target +//│ ║ l.31: private fun read() = make().value +//│ ╙── ^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls new file mode 100644 index 0000000000..960e8372ef --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls @@ -0,0 +1,28 @@ +:. + +// Control: an ordinary forwarding alias preserves the declared interface. +:expect 1 +class Item(val value: Int) +type Choice[A] = A +private fun read(x: Choice[Item]) = x.value +read(Item(1)) +//│ = 1 + +// FIXME: A productive recursive union adds no unknown alternative. This +// predates Boolean normalization: the alias recursion guard publishes an opaque +// candidate alongside Item, preventing lookup even though the unfolding is finite. +:fixme +:expect 2 +class Item(val value: Int) +type Choice[A] = A | Choice[A] +private fun read(x: Choice[Item]) = x.value +read(Item(2)) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.18: private fun read(x: Choice[Item]) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This abstract type does not specify a member interface. +//│ ║ l.18: private fun read(x: Choice[Item]) = x.value +//│ ╙── ^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphPartialAliases.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphPartialAliases.mls new file mode 100644 index 0000000000..4dfaead9d4 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphPartialAliases.mls @@ -0,0 +1,50 @@ +:. + +// Supplying the unused formal allows eager alias normalization to reach a fixed point. +type First[A, B] = A +type Chain[A] = {value: A, next: Chain[First[A, Int]]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + +// FIXME: Omitting that same unused formal must preserve the finite graph. +// Eager normalization currently retains First[A], nesting another binding on +// every recursive constraint until hashing the environment overflows the stack. +:fixme +type First[A, B] = A +type Chain[A] = {value: A, next: Chain[First[A]]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () +//│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// FIXME: A fully supplied forwarding alias still contains the partial application. +:fixme +type First[A, B] = A +type Forward[A] = First[A] +type Chain[A] = {value: A, next: Chain[Forward[A]]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () +//│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// FIXME: Partial projections must also permit a finite argument permutation. +:fixme +type First[A, B, C] = A +type Chain[A, B] = {value: A, next: Chain[First[B, A], First[A, B]]} +private fun walk[A, B](chain: Chain[A, B], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () +//│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// A fixed omitted argument is a reset with a stable source hole. +type Identity[A] = A +type Chain[A] = {value: A, next: Chain[Identity]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + +// Omitting an unused formal on the recursive alias itself already terminates. +type Chain[A, B] = {value: A, next: Chain[A]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + +// Nominal recursive fields need not unfold their changing arguments eagerly. +class Chain[A](val value: A, val next: Chain[Array[A]]) +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphProjectionSites.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphProjectionSites.mls new file mode 100644 index 0000000000..4d15f75a58 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphProjectionSites.mls @@ -0,0 +1,38 @@ +:. + +// The structural constraint closes a cycle through a by-name getter. +type Link = {next: Link} +class Node with + fun next = this +private fun take(x: Link) = () +take(new Node) + +// FIXME: An unused type binder must not turn this finite constraint into +// infinitely many instantiations. constrainRecord creates a fresh field flow +// on every visit, and by-name invocation uses that flow as its binder site. +:fixme +type Link = {next: Link} +class Node with + fun next[A] = this +private fun take(x: Link) = () +take(new Node) +//│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// FIXME: The declared-result path has the same site-allocation problem. +:fixme +type Link = {next: Link} +class Node with + fun next[A]: Node = this +private fun take(x: Link) = () +take(new Node) +//│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// FIXME: Separate quantified signatures must obey the same finite-site rule. +:fixme +type Link = {next: Link} +class Node with + fun next: [A] -> Node + fun next = this +private fun take(x: Link) = () +take(new Node) +//│ /!!!\ Uncaught error: java.lang.StackOverflowError diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index b5f59a5a55..a2df21e9c1 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -372,3 +372,23 @@ private fun walk[A, B](chain: Stable[A, B], n: Int) = //│ ╔══[COMPILATION ERROR] This selection of member 'next' has no resolved target //│ ║ l.362: if n > 0 then walk(chain.next, n - 1) else () //│ ╙── ^^^^^^^^^^ + +// Even a constructor-free output wildcard is rejected by the conservative check. +// All positive value components remain A; the deferred polarity analysis should +// distinguish an argument part from a growing nominal/tuple/function constructor. +:e +type Chain[A] = {value: A, next: Chain[out A]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.380: type Chain[A] = {value: A, next: Chain[out A]} +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. +//│ ║ l.380: type Chain[A] = {value: A, next: Chain[out A]} +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. +//│ ║ l.380: type Chain[A] = {value: A, next: Chain[out A]} +//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] This selection of member 'next' has no resolved target +//│ ║ l.382: if n > 0 then walk(chain.next, n - 1) else () +//│ ╙── ^^^^^^^^^^ From 4c4f8770364b9c8512d08ac082b1d80c11169727 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 14:56:01 +0800 Subject: [PATCH 125/228] Collapse nested tuple rest views to their parent A rest view kept its immediate parent, so taking a rest of a rest nested the views and made each candidate record the order in which fields were removed. Removing fields from both ends of an n-tuple thus produced 2^n candidates for O(n^2) distinct layouts, which made FingerTreeList time out under new resolution. A view of a view now refers to the original parent; views contribute no spreads, so containsSpread is unchanged. Co-Authored-By: Claude Opus 5.5 --- doc/new-resolution-suite-migration.md | 2 +- .../src/main/scala/hkmc2/semantics/NewResolver.scala | 4 ++-- .../src/main/scala/hkmc2/semantics/Shape.scala | 12 ++++++++++++ .../test/mlscript/newres/TuplePatternBindings.mls | 11 +++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 18d9511e8b..21b87bfeb1 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -76,7 +76,7 @@ The remaining option consumers have these blockers when compiled with | --- | --- | | `Block`, `Shape` | Missing nominal members and callback arity mismatches; `Block` also needs a public interface for `showArm`. | | `Iter`, `MutMap`, `ups/EvaluationContext` | Missing public parameter interfaces and unresolved member selections. | -| `FingerTreeList` | Repeated `reduce` lookup errors on rest tuples; compilation exceeds the 25-second test limit. Convergence needs investigation. | +| `FingerTreeList` | The prelude `Array` interface has no `reduce`, which `mk` calls on its rest parameter; the error is reported once per distinct `args` tuple candidate. Compilation converges but takes about 20 seconds (legacy: 1.5 seconds), close to the 25-second limit. Each combination of candidates for `concatMiddle`'s `[...ay1, ...middle, ...ax2]` produces a separate tuple candidate, and each one is matched again by `toNodes`. | | `parsing/Extension`, `ParseRule`, `Test`, `TreeHelpers` | Repeated-entry mark assertion during nominal field projection: `NominalInstanceView.getMemberImpl` calls `captureType` on an argument whose path already contains that scope. Determine the source of the duplicate transfer without weakening the marks algebra. | | `parsing/Lexer` | Opened binary `~` conflicts with the builtin; calls with trailing contextual parameters leave function values where tokens are expected. | | `parsing/Parser` | Pattern-field flow and unresolved nominal members. | diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index ac6e2dcf3d..65ea1a2d68 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1510,7 +1510,7 @@ class NewResolver: val suffix = drop(rest, count, false) if approximate then TupleShape.Unknown(tuple.source, Nil, UnknownValueShape.at(tuple.source)) :: suffix else suffix val remaining = drop(segments, expectedCount, true) - val rest = if expectedCount == 0 then tuple else TupleShape(tuple.source, TupleShape.Rest(tuple, remaining) :: Nil)(this) + val rest = if expectedCount == 0 then tuple else TupleShape.restView(tuple, remaining)(this) publish(expectedCount, rest.exit(marks).enter(mss)) checkArgumentArity(args, expectedCount, hasRest, src, funSh): case (tuple, marks) => matchSegments(tuple, marks) @@ -1758,7 +1758,7 @@ class NewResolver: // Reusing an unchanged view also bounds recursive tail matching on // arrays with unknown length; wrapping it again would grow forever. val rest = if middle == segments then tuple - else TupleShape(tuple.source, TupleShape.Rest(tuple, middle) :: Nil)(this) + else TupleShape.restView(tuple, middle)(this) rest.exit(marks) match case value: TermShape => matchShapePat(value, p)(_ => ()) case NoShape => () diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 875aeababa..8c4affee43 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -694,6 +694,18 @@ object TupleShape: * flattening away the parent would hide recursive producer dependencies from * containsSpread, allowing recursion through rest slicing to evade widening. */ final case class Rest(shape: TupleShape, segments: Ls[Segment]) extends Element + /** A view of `tuple` retaining only `segments`, which must be taken from + * `tuple.segments`. A view of another view refers to that view's parent: + * a view contributes no spreads of its own, so containsSpread gives the same + * answer for the parent as for the intermediate view. Nesting views instead + * would make the candidate's identity record the order in which fields were + * removed. For `if xs is [x, ...rest] then f(rest); [...rest, x] then f(rest)` + * on an n-tuple, that yields 2^n candidates for only O(n^2) distinct layouts. */ + def restView(tuple: TupleShape, segments: Ls[Segment])(resolver: NewResolver): TupleShape = + val parent = tuple.elements match + case Rest(parent, _) :: Nil => parent + case _ => tuple + TupleShape(tuple.source, Rest(parent, segments) :: Nil)(resolver) /** A field found by record member lookup. Spreading r into `mut {...r}` reuses diff --git a/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls b/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls index 28805f42bf..e7cf4212a5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls @@ -39,3 +39,14 @@ opaque([{name: "hidden"}, 1]) //│ ║ l.29: fun opaque[A](pair: [A, Int]) = if pair is [item, _] then item.name //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Removing fields from either end in different orders reaches the same residual +// layouts. Each remainder must be one candidate, not one per removal order, +// or this 24-field tuple produces 2^24 candidates. +:expect 24 +private fun peel(values) = if values is + [] then 0 + [first, ...rest] and first > 100 then peel(rest) + [...rest, last] then 1 + peel(rest) +peel([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]) +//│ = 24 From 91cf93cf8203e67a6e9861f9439f9c8a58833122 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 15:03:27 +0800 Subject: [PATCH 126/228] Declare Array.reduce with an initial accumulator The accumulator receives both the initial value and the callback results through the method's type parameter. Declared methods cannot be overloaded by arity, and without an initial value the first element becomes the accumulator, so only the form with an initial value is declared. Declaring reduce exposes a mark-path assertion failure in FingerTreeList (repeated exit from the same lexical scope). The same failure occurs with map when a callback element reaches the receiver through a recursive call; newres/Arrays.mls records it as a fixme, and FingerTreeList stays on legacy resolution. Co-Authored-By: Claude Opus 5.5 --- doc/new-resolution-suite-migration.md | 8 ++-- doc/reference.md | 5 ++- .../src/test/mlscript/decls/Prelude.mls | 4 ++ .../src/test/mlscript/newres/Arrays.mls | 41 +++++++++++++++++++ .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 21b87bfeb1..1561b20e8f 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -76,7 +76,7 @@ The remaining option consumers have these blockers when compiled with | --- | --- | | `Block`, `Shape` | Missing nominal members and callback arity mismatches; `Block` also needs a public interface for `showArm`. | | `Iter`, `MutMap`, `ups/EvaluationContext` | Missing public parameter interfaces and unresolved member selections. | -| `FingerTreeList` | The prelude `Array` interface has no `reduce`, which `mk` calls on its rest parameter; the error is reported once per distinct `args` tuple candidate. Compilation converges but takes about 20 seconds (legacy: 1.5 seconds), close to the 25-second limit. Each combination of candidates for `concatMiddle`'s `[...ay1, ...middle, ...ax2]` produces a separate tuple candidate, and each one is matched again by `toNodes`. | +| `FingerTreeList` | Assertion failure in the mark-path invariant ("Repeated exit from the same lexical resolution scope") when `mk` calls `args.reduce` with a callback that feeds the accumulator back into `mk`. The same failure occurs for `map` when a callback element reaches the receiver through a recursive call; `newres/Arrays.mls` records it as a `:fixme`. | | `parsing/Extension`, `ParseRule`, `Test`, `TreeHelpers` | Repeated-entry mark assertion during nominal field projection: `NominalInstanceView.getMemberImpl` calls `captureType` on an argument whose path already contains that scope. Determine the source of the duplicate transfer without weakening the marks algebra. | | `parsing/Lexer` | Opened binary `~` conflicts with the builtin; calls with trailing contextual parameters leave function values where tokens are expected. | | `parsing/Parser` | Pattern-field flow and unresolved nominal members. | @@ -160,8 +160,10 @@ recursive UPS matchers. Preserve both runtime results and matcher structure. member lookup (`codegen/ParamClasses`, `basics/DynamicInstantiation`), and module/call checks. Decide nominal module-forwarding compatibility for `basics/CyclicModuleForwarders` before changing its expected outcomes. -- Extend host interfaces where needed: `Array.reduce` accumulator/callback - contracts, keyword-named `Map.set` and `Reflect.set`, and WebAssembly exports. +- Extend host interfaces where needed: keyword-named `Map.set` and `Reflect.set`, + and WebAssembly exports. `Array.reduce` requires an initial value because + declared methods cannot be overloaded by arity; the form without one would need + an accumulator type that also includes the element type. `Array.concat` currently returns `Array[Any]`; improving precision needs a declared element constraint on its rest arguments. `Array.splice` must separate its optional deletion count from inserted elements before those elements can diff --git a/doc/reference.md b/doc/reference.md index a4078ef28d..b10b6f9030 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -740,7 +740,10 @@ to `Child`-only members through that annotation. Array callbacks such as `map` receive the element, index, and array; their declared function interfaces must account for these arguments, for example with -unused or rest parameters. +unused or rest parameters. `reduce` also passes the accumulator first, and must be +given an initial value: `xs.reduce((acc, x, ...) => acc + x, 0)`. Without one, +JavaScript uses the first element as the initial accumulator, which the declared +signature does not describe. --- diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index c3e08ad902..e4d7ca53bb 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -165,6 +165,10 @@ declare class Array[T](val length: Int): Array[T] with fun filter(f: (T, Int, Array[T]) -> Bool): Array[T] fun every(f: (T, Int, Array[T]) -> Bool): Bool fun some(f: (T, Int, Array[T]) -> Bool): Bool + // Without an initial value, the first element becomes the accumulator, so the + // accumulator type would also have to include T. Methods cannot be overloaded + // by arity, so only the form with an initial value is declared. + fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U fun indexOf(value: T, ...from): Int fun lastIndexOf(value: T, ...from): Int fun includes(value: T, ...from): Bool diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 5406004a28..c924d08dbc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -191,3 +191,44 @@ let removed = xs.splice(1, 1, 9).join() //│ filled = "7,7,3" //│ removed = "7" //│ xs = [7, 9, 3] + +// reduce's accumulator receives both the initial value and the callback results. +:expect [3, 5] +class Item(val value: Int) +class Total(val total: Int) +[[Item(1), Item(2)].reduce((acc, item, ...) => Total(acc.total + item.value), Total(0)).total, + [Item(5)].reduce((acc, item, ...) => acc + item.value, 0)] +//│ = [3, 5] + +// An accumulator shape that comes only from the initial value is still checked. +:e +class Item(val value: Int) +class Total(val total: Int) +[Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.207: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) +//│ ║ ^^^^^^^^^^^ +//│ ╟── Instance of class 'Total' does not contain member 'missing' +//│ ║ l.207: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Without an initial value, the first element would be the accumulator, +// which the declared signature does not describe. +:e +[1, 2].reduce((acc, x, ...) => acc + x) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.219: [1, 2].reduce((acc, x, ...) => acc + x) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'reduce' expected 2 arguments, but got 1 +//│ ║ in ../decls/Prelude.mls +//│ ║ l.170: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ = 3 + +// A callback element fed back into the receiver through a recursive call. +// FIXME: marks transport crosses the same scope twice (assertion failure). +:fixme +private fun mk(xs) = xs.map((x, ...) => if x is Int then x else mk([x])) +mk([1]) +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Repeated exit from the same lexical resolution scope diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index f1e79752a4..3a03f52a1c 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.318: fun +//│ ║ l.322: fun //│ ║ ^^^ -//│ ║ l.319: const(c: Int): Int32 +//│ ║ l.323: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 3c8449f8e812eb86062967802bc72289013c733c Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 15:24:45 +0800 Subject: [PATCH 127/228] Fix resolution graph convergence and generic constraints --- doc/new-resolution-future-work.md | 113 +++++-------- doc/new-resolution-regular-types.md | 41 +++-- doc/new-resolution-suite-migration.md | 7 +- doc/new-resolution-type-value-flow.md | 34 ++-- .../scala/hkmc2/TypeInstantiationTest.scala | 21 +++ .../test/scala/hkmc2/TypeRelationTest.scala | 34 +++- .../scala/hkmc2/semantics/NewResolver.scala | 148 ++++++++++++------ .../hkmc2/semantics/NewResolverState.scala | 13 +- .../newres/InheritedTypeArguments.mls | 56 +++++-- .../test/mlscript/newres/QuantifiedBounds.mls | 109 ++++++++++++- .../newres/RecursiveBooleanInterfaces.mls | 35 ++++- .../newres/TypeGraphPartialAliases.mls | 21 ++- .../newres/TypeGraphProjectionSites.mls | 35 +++-- .../mlscript/newres/TypeGraphTermination.mls | 28 ++-- 14 files changed, 484 insertions(+), 211 deletions(-) diff --git a/doc/new-resolution-future-work.md b/doc/new-resolution-future-work.md index 3c4c770c46..feeb0b7f68 100644 --- a/doc/new-resolution-future-work.md +++ b/doc/new-resolution-future-work.md @@ -1,83 +1,54 @@ # Deferred resolution design work This reference records confirmed implementation gaps and deferred design work. -The convergence failures below must be corrected before claiming that accepted -type graphs are finite. Other design refinements require review before implementation. Current -contracts are in [instance types and parameter constraints](new-resolution-type-value-flow.md); +Current contracts are in [instance types and parameter constraints](new-resolution-type-value-flow.md); remaining suite ports are in the [migration worklist](new-resolution-suite-migration.md). -## Confirmed convergence failures - -### Partially supplied forwarding aliases - -```mlscript -type First[A, B] = A -type Chain[A] = {value: A, next: Chain[First[A]]} -private fun walk[A](chain: Chain[A], n: Int) = - if n > 0 then walk(chain.next, n - 1) else () -``` - -This has the same finite unfolding as `Chain[A] = {value: A, next: Chain[A]}`. -`declaredType` reduces an applied alias only when every formal is supplied, whereas -deferred interpretation fills omissions with source holes. Keeping `First[A]` as -an argument retains another binding environment on each recursive constraint; -eventually hashing `DeclaredType` overflows. Supplying the unused `B` makes the -example terminate. The same failure occurs through a forwarding alias and with -argument permutations. Unify the argument-binding rules while preserving omission -identities, variance, captures, and the alias expansion guard. -Regressions: `newres/TypeGraphPartialAliases.mls`. - -### Fresh sites during structural member constraints - -```mlscript -type Link = {next: Link} -class Node with - fun next[A] = this -private fun take(x: Link) = () -take(new Node) -``` - -`constrainRecord` allocates a fresh `FlowSymbol.memSym` for the expected field on -every visit. Selecting `next` invokes the by-name definition and uses this new -symbol as its instantiation site. The resulting substitution distinguishes another -receiver view, which repeats the structural constraint and allocates again. -The definition/site cache cannot bound allocation when the sites themselves grow. -An unused binder is sufficient; removing it makes the example terminate. Declared -results and separate quantified signatures also reproduce the overflow. -Synthetic projections need stable identities tied to the finite source graph, -with ordinary marks retaining activation separation. -Regressions: `newres/TypeGraphProjectionSites.mls`. - ## Quantified bounds -`TypeQuantifier` and `DeclaredTypeParameter` store lower and upper bounds, but -`instantiateCallable` substitutes only the parameter/result types and supplied -arguments. It never installs the bounds as relations on the new instances. -For example, a result of `[A extends Item] -> () -> A` does not expose `Item`'s -members when the type argument is inferred. A separately signed implementation -also cannot use its parameter's declared upper-bound interface. This is distinct -from postponing concrete mismatch diagnostics: the constraint edges and checking -interfaces themselves are missing. Regressions: `newres/QuantifiedBounds.mls`. - -## Inherited type-argument constraints - -Matching an actual subclass against `Parent[A]` currently leaves `A` unconstrained. -The nominal branch of `inferTypeArguments` compares exact class identities and -does not follow the instantiated parent view. Thus `read[A](p: Parent[A]) = p.item` -loses its result interface for both constructed and declared `Child[Item]` inputs, -while `Parent[Item]` works. The field has a complete signature, so this is separate -from inferred member signatures. The exact-class restriction predates the current -type-relation implementation. Follow the parent's bindings and scope transfers -when installing both variance directions. Regressions: `newres/InheritedTypeArguments.mls`. - -## Productive Boolean alias cycles +Instantiation installs `lower <: instance <: upper` with the complete binder +substitution, after recording explicit arguments. This covers callables and by-name +invocations; captured enclosing binders also specialize the bounds. Lower bounds +contribute inference candidates, and upper relations propagate obligations through +later candidates. Dependent and recursive bounds share the existing relation graph. + +Checking and observation gaps remain. An otherwise unconstrained result of +`[A extends Item] -> () -> A` does not expose `Item`'s members. A separately signed +implementation of `[A extends Item] -> A -> Int` cannot use that guaranteed +interface either. Installing an upper constraint does not give the parameter's +candidate host an observable shape. A recursive by-name getter with a lower bound +on a class parameter can also leak that parameter's abstract checking candidate +through inferred body constraints into a declared receiver. The same declared +receiver works when the body contributes no recursive checking edge. + +Do not fix this by publishing the upper bound as an additional lower candidate. +For `[A extends Base] -> A -> A`, inference from a `Child` input must still permit +`Child`-only operations on the result; a second `Base` candidate would incorrectly +forbid them. Conversely, a generic implementation must work for every permitted +`A`, and cannot use a lower bound's members as its checking interface. + +A design must distinguish a parameter's inferred candidates, its guaranteed upper +interface, and its rigid checking witness. It must specify how observation uses +those guarantees with delayed bounds and explicit arguments, without mixing the +checking witness into a real call or losing scope marks. Regression and precision +controls: `newres/QuantifiedBounds.mls`. + +## Unguarded Boolean alias cycles The alias observation guard publishes an opaque candidate when revisiting an alias. -For `Choice[A] = A | Choice[A]`, this adds an unknown alternative alongside `A`, -preventing member lookup even though the productive unfolding exposes only `A`. -This behavior predates the current Boolean normalization. A correction must also -specify unproductive cycles and recursive intersections; simply removing every -opaque candidate is not a general solution. Regression: `newres/RecursiveBooleanInterfaces.mls`. +For `Choice[A] = A | Choice[A]`, this prevents member lookup on `Choice[Item]`. +The existing regression requests the least-fixed-point interpretation, where this +alias is `Item`, but the equation alone does not determine that interpretation: +`Any` is also a solution. Conversely, `X = Item & X` has least solution `Nothing` +and greatest solution `Item`. A direct `X = X` supplies no choice at all. + +These cycles are unguarded: reaching the recursive reference crosses no nominal, +record, tuple, or function constructor. Finite Boolean normalization is not a +contract for their fixed points. Choose whether to reject unguarded recursive +aliases or define their fixed-point semantics before changing the guard. Simply +suppressing every repeated candidate would silently choose semantics and leave +pure cycles without an interface or an explanatory diagnostic. Regressions and +current diagnostic controls: `newres/RecursiveBooleanInterfaces.mls`. ## Receiver reconstruction diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index 9fca291f85..89e4b20d11 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -22,9 +22,9 @@ The first four have finite unfoldings. `Growing[Int]` exposes `Int`, `Array[Int] `Array[Array[Int]]`, and so on. It receives a regularity diagnostic, without widening the type or imposing an expansion-depth limit. -Canonical references reduce fully supplied aliases and normalize Boolean -combinations. The implemented check uses one dependency node per original formal -parameter and rejects constructor-bearing dependency cycles before observing the +Canonical references reduce applied aliases, including omitted arguments, and +normalize Boolean combinations and wildcard parts. The implemented check uses one +dependency node per original formal parameter and rejects constructor-bearing dependency cycles before observing the structural interface. This is an intentionally conservative restriction: it can reject regular types whose finite unfolding requires distinguishing argument parts or correlations between successive substitutions. The diagnostic says that the @@ -59,7 +59,7 @@ normalization does not freeze its current bounds. Combined nodes are interned in the consuming resolution state, using the same inherited-cache discipline as other type references. -Fully supplied aliases reduce before they become another type's argument. Arguments +Applied aliases reduce before they become another type's argument. Arguments are interpreted in their caller environment first, then declaration variance is applied and the alias's formals are substituted. A guard on source alias symbols stops unproductive recursive expansion. Nested arguments are reduced before adding @@ -94,14 +94,19 @@ This does **not** by itself bound the atoms. Constructor growth, retained argume parts that never become observable, and the existing requirements on finite marked contexts remain separate obligations. -In particular, eager reduction currently excludes partially supplied aliases. -With `type First[A, B] = A`, the recursive argument `First[A]` is semantically -identical to `A`, but retains a new environment on each recursive constraint. -`TypeGraphPartialAliases.mls` records the resulting stack overflow alongside the -terminating `First[A, Int]` control. Passing the conservative regularity check -therefore does not currently imply that the implementation reaches a fixed point. -Omitted positions must keep their source-hole identities when normalization is -extended; allocating holes during each expansion would create another source of growth. +Eager reduction and deferred interpretation share the same argument-binding rule. +Omitted positions reuse source-owned holes and follow the same scope transfers as +supplied arguments. For `type First[A, B] = A`, the recursive argument `First[A]` +therefore reduces to `A`; it does not retain a fresh environment on each recursive +constraint. A relevant omission, such as `Second[A]` for `type Second[A, B] = B`, +resets to its fixed source hole. Regressions: `TypeGraphPartialAliases.mls`. + +Written wildcard parts are normalized at their lexical polarities before the pair +is interned. Repeated `out A`, `in A`, or two-part substitutions then select existing +endpoints instead of retaining nested wildcard environments. The pair keeps its +written source so it still overrides declaration variance, and missing parts keep +that source for diagnostics. This does not discard unobserved parts: constructors +in either part still participate in the conservative growth check. ## Conservative check @@ -117,7 +122,7 @@ an interface is observed, so an unused annotation cannot avoid the check. Source visitation and alias expansion use finite source-node and alias-symbol guards; reachability uses the finite original-binder graph. Accepted and rejected results are cached by source root in the consuming resolver state. A rejected root does -not reject an unrelated regular child. No inference variables are allocated and +not reject an unrelated regular child. Omitted arguments reuse their source holes; no ordinary mark operation is changed. That handles ordinary permutations and resets, including: @@ -144,10 +149,12 @@ The constructor inside the input part is not an observable growing component of this structural unfolding. A binder-only edge from `A` to `A` loses that fact. The worksheet records the rejection explicitly as a current precision limitation. -The implementation also labels written wildcard dependencies as constructor-bearing, -so even `next: Chain[out A]` is rejected although it contains no growing constructor -and every positive `value` component remains `A`. The same worksheet records this -simpler false positive; the diagnostic's constructor explanation is conservative too. +Constructor-free wildcard dependencies do not count as growing constructors. +For example, `next: Chain[out A]` is accepted and every positive `value` component +remains `A`. Both parts are still analyzed, so `Chain[in Array[A]]` remains +conservatively rejected. `TypeGraphTermination.mls` covers these boundaries, and +`TypeRelationTest` checks repeated wildcard substitution, marked early/late bounds, +and listener saturation. The representation needs the same distinction. Current free-binder projection retains the whole bound argument whenever a formal is relevant, including parts diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 880b92b7b4..f021383939 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -103,9 +103,10 @@ headers identify the remaining legacy fixtures. scope. [Type-flow design improvements](new-resolution-future-work.md) are deferred: receiver reconstruction, inferred missing member types through nominal views, omitted-argument caller separation, and a more precise regularity check. These - do not block the current implementation batch. The whole-graph convergence audit - has found [concrete termination failures](new-resolution-future-work.md#confirmed-convergence-failures); - local allocation and replay bounds are documented in the + do not block the current implementation batch. The reproduced alias/projection + termination failures now have fixes and regressions; the remaining + [whole-graph convergence audit](new-resolution-future-work.md#whole-graph-convergence-audit) + and local allocation/replay bounds are documented in the [type-flow reference](new-resolution-type-value-flow.md#canonical-references-and-termination-obligations). For broader mutation migration, decide how reassignment changes storage's inferred interface, including across worksheet blocks. `newres/MutationFlow.mls` diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index f36b36c858..f8b4815ae6 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -74,16 +74,21 @@ subscribing to constraints so reentrant observation finds it. Recursion reuses the group; marks distinguish enclosing activations of a shared inner site. No other observation, projection, or recursive traversal allocates fresh binders. `App.resSym`, `New.resSym`, and reference/selection sites provide stable identities; -`typeApplicationSite` memoizes one identity per original `TyApp`. Class and +`typeApplicationSite` memoizes one identity per original `TyApp`. Structural +field constraints likewise reuse one projection site per source field across +recursive and activated views (`TypeGraphProjectionSites.mls`). Class and constructor views use the same original owner. An anonymous polymorphic annotation uses its source scheme, and each alternative of an overload has its own owner. `TypeShape.Polymorphic` and `DeclaredTypeParameter` retain the original binders and their bounds. Inputs, results, and nested callbacks receive the site's substitution -before interface expansion. Bounds are currently retained only as metadata: -`instantiateCallable` does not install their constraints. Applying the same -substitution to bounds and connecting them to the instances remains a -[required correction](new-resolution-future-work.md#quantified-bounds). +before interface expansion. The same substitution is applied to bounds, including +captured enclosing binders. Instantiation connects `lower <: instance <: upper` +after recording explicit supplied arguments, so a lower bound cannot widen a +supplied interface. By-name invocations install these relations when their scheme +is consumed. Recursive and dependent bounds share the existing relation graph. +Using an upper guarantee as an interface for an unconstrained result or a generic +checking body remains a [design issue](new-resolution-future-work.md#quantified-bounds). Enclosing binders remain lexical captures, not binders of the nested definition being instantiated. @@ -120,6 +125,12 @@ supplies its own parts and overrides declaration variance. Comparing actual `a` with expected `b` installs `a.output <: b.output` and `b.input <: a.input`. Missing wildcard parts are actual top/bottom types, not inference holes. +A subclass contributes the arguments of the requested nominal ancestor. Each +parent step preserves the child's binder substitution and the parent's scope +marks before installing both variance directions. This applies to constructed +receivers and declared nominal views, including multiple inheritance steps and +captured enclosing binders (`InheritedTypeArguments.mls`). + ### Substitute at the occurrence before applying argument variance First interpret an argument expression at its lexical occurrence polarity. A @@ -273,12 +284,13 @@ when their site labels also range over a finite set. Formula normalization is finite for a fixed atom set. These bounds do not alone establish finiteness of nested binding environments or the atom set. -There are now [concrete counterexamples](new-resolution-future-work.md#confirmed-convergence-failures): -structural member constraints manufacture fresh invocation sites, and partially -supplied forwarding aliases retain growing environments. Both can overflow on -semantically finite recursive types. A whole-graph termination argument must cover -these paths as well as listener convergence. Cache keys must never contain growing -substitution histories; depth limits and dropped marks do not establish a fixed point. +Structural field constraints now reuse source projection sites, and partial +forwarding aliases share deferred interpretation's source-hole binding rules. +Wildcard normalization similarly retains canonical argument parts rather than +nested substitution histories. Their regressions establish these local bounds; +a [whole-graph termination argument](new-resolution-future-work.md#whole-graph-convergence-audit) +must still cover all accepted contextual references, formula atoms, and listener +convergence. Depth limits and dropped marks do not establish a fixed point. These representations are internal to resolution. Lowering consumes completed targets and value shapes; runtime values acquire no type-argument objects. diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala index aa72b13f67..a8834ff4bd 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala @@ -47,6 +47,27 @@ class TypeInstantiationTest extends AnyFunSuite: intercept[IllegalArgumentException]: state.instantiateTypeParameters(scheme, FlowSymbol.app(), List(instance)) + test("structural projections bound allocation across activation and importer views"): + // Diff tests check recursive getter behavior; this checks that repeated + // projections and imported activations allocate only one binder group. + given owner: Elaborator.State = new Elaborator.State + val source = owner.newResolverState + val consumer = new Elaborator.State().newResolverState + val field = BlockMemberSymbol("next", Nil) + val site = source.fieldProjectionSite(field) + assert(consumer.inGraph(source).fieldProjectionSite(field) eq site) + val scheme = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val parameter = new VarSymbol(new syntax.Tree.Ident("A")) + val instances = consumer.instantiateTypeParameters(scheme, site, List(parameter)) + val views = List(consumer, consumer.withInstances(instances), consumer.inGraph(source), + consumer.withInstances(instances).inGraph(source)) + (1 to 1000).foreach: _ => + views.foreach: view => + assert(view.fieldProjectionSite(field) eq site) + assert(view.instantiateTypeParameters(scheme, view.fieldProjectionSite(field), List(parameter)) == instances) + assert(consumer.allocatedTypeInstanceCount == 1) + assert(source.allocatedTypeInstanceCount == 0) + test("consumer instantiation leaves another consumer and the source untouched"): given owner: Elaborator.State = new Elaborator.State val source = owner.newResolverState diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 075bd78f4b..55ee9598b8 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -114,13 +114,14 @@ class TypeRelationTest extends AnyFunSuite: assert(target.inferenceHost.listeners.size == before) assert(h.state.allocatedTypeInstanceCount == 0) - test("captured alias reduction uses ordinary exit and entry including caller identity loss"): + test("partial captured alias reduction uses ordinary exit and entry including caller identity loss"): val h = new Harness import h.given val (formal, ft) = h.parameter("X") + val (unused, _) = h.parameter("Unused") val symbol = TypeAliasSymbol(Tree.Ident("Identity")) val member = BlockMemberSymbol("Identity", Nil) - symbol.defn = S(TypeDef(symbol, member, List(TyParam(FldFlags.empty, N, formal)), + symbol.defn = S(TypeDef(symbol, member, List(TyParam(FldFlags.empty, N, formal), TyParam(FldFlags.empty, N, unused)), S(ft.resolution.source), N, Nil)) val alias = h.tpe(TypeShape.Alias(symbol, S(ft.resolution))) val scope = TermSymbol(Fun, N, Tree.Ident("owner")) @@ -147,6 +148,35 @@ class TypeRelationTest extends AnyFunSuite: assert(target.inferenceHost.listeners.size == count) assert(h.state.allocatedTypeInstanceCount == 0) + test("wildcard normalization bounds recursive environments while retaining late marked bounds"): + // Unlike a finite number of source projections, this measures saturation of + // the saved argument graph and its listeners under repeated substitution. + for input <- List(false, true); output <- List(false, true) do + val h = new Harness + import h.given + val (formal, ft) = h.parameter("A") + val (target, tt) = h.parameter("Target") + val scope = ResolutionBoundary(TermSymbol(Fun, N, Tree.Ident("owner"))) + val site = S(FlowSymbol.app()) + val original = h.resolver.transportType(tt, EntryMark(scope, site, NoMarks) :: Nil) + val wildcard = h.tpe(TypeShape.Wildcard(Option.when(input)(ft.resolution), Option.when(output)(ft.resolution))) + val first = h.resolver.declaredType(wildcard.resolution, Map(formal -> original)) + val observed = h.observe(ContextualType(first, Nil)) + val inputObserved = h.observe(ContextualType(h.resolver.selectArgument(first, false), Nil)) + val before = target.inferenceHost.listeners.size + var current = first + (1 to 1000).foreach: _ => + current = h.resolver.declaredType(wildcard.resolution, Map(formal -> current)) + assert(current eq first) + val value = DynShape() + h.resolver.publishParameter(target, value) + if output then assert(observed.toList == List(MarkedShape.enter(value, scope, site))) + else assert(!observed.exists(_.applicationHead._1.isInstanceOf[DynShape])) + if input then assert(inputObserved.toList == List(MarkedShape.enter(value, scope, site))) + else assert(inputObserved.isEmpty) + assert(target.inferenceHost.listeners.size == before) + assert(h.state.allocatedTypeInstanceCount == 0) + test("recursive Boolean arguments share a fixed point and preserve each atom's marks and late bounds"): val h = new Harness import h.given diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index ac6e2dcf3d..584c23d7bf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -336,6 +336,9 @@ class NewResolver: case TypeShape.Parameter(symbol, _) :: Nil => bound(symbol, wrapped) case TypeShape.Combined(formula) :: Nil => formula.atoms.flatMap(uses(_, wrapped, next, aliases)) case TypeShape.Contextual(reference) :: Nil => uses(reference.tpe, wrapped, next, aliases) + case TypeShape.Wildcard(input, output) :: Nil => + input.toSet.flatMap(part => uses(declaredType(part, tpe.copy(positive = !tpe.positive)), wrapped, next, aliases)) ++ + output.toSet.flatMap(part => uses(declaredType(part, tpe), wrapped, next, aliases)) case TypeShape.Argument(parts) :: Nil => uses(parts.input, wrapped, next, aliases) ++ uses(parts.output, wrapped, next, aliases) case TypeShape.SelectedArgument(argument, _) :: Nil => uses(argument, wrapped, next, aliases) case TypeShape.Applied(base, args) :: Nil => alias(base, Set.empty) match @@ -403,7 +406,7 @@ class NewResolver: private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], positive: Bool) (using NewResolverState): DeclaredType = - def loop(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], aliases: Set[TypeAliasSymbol]): DeclaredType = + def loop(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], aliases: Set[TypeAliasSymbol], positive: Bool): DeclaredType = def retain: DeclaredType = val relevant = if bindings.isEmpty then bindings else typeDependencies(resolution) match case R(binders) => bindings.filter((symbol, _) => binders(symbol)) @@ -411,35 +414,47 @@ class NewResolver: DeclaredType(resolution, relevant, Map.empty, positive) resolution.currentShapes.toList match case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) - case TypeShape.Captured(base, thru) :: Nil => captureType(loop(base, bindings, aliases), thru) + case TypeShape.Captured(base, thru) :: Nil => captureType(loop(base, bindings, aliases, positive), thru) case TypeShape.Union(left, right) :: Nil => - combinedType(resolution, typeFormula(loop(left, bindings, aliases)).union(typeFormula(loop(right, bindings, aliases)))) + combinedType(resolution, typeFormula(loop(left, bindings, aliases, positive)).union(typeFormula(loop(right, bindings, aliases, positive)))) case TypeShape.Intersection(left, right) :: Nil => - combinedType(resolution, typeFormula(loop(left, bindings, aliases)).intersection(typeFormula(loop(right, bindings, aliases)))) + combinedType(resolution, typeFormula(loop(left, bindings, aliases, positive)).intersection(typeFormula(loop(right, bindings, aliases, positive)))) + case TypeShape.Wildcard(input, output) :: Nil => + // Normalize both parts before retaining the argument. Otherwise even + // out A keeps another A-to-wildcard environment at each recursive + // step. Preserve the written source: it overrides declaration variance. + val parts = TypeArgument(input.fold(extremeType(false, S(resolution)))(loop(_, bindings, aliases, !positive)), + output.fold(extremeType(true, S(resolution)))(loop(_, bindings, aliases, positive))) + rstate.wildcardTypes.getOrElseUpdate((resolution, parts), { + val normalized = new TypeResolution(resolution.source, resolution.fail) + normalized.publish(TypeShape.Argument(parts)) + DeclaredType(normalized, Map.empty, Map.empty, true) + }) case TypeShape.Applied(base, arguments) :: Nil => - def applyAlias(base: DeclaredType, supplied: Ls[DeclaredType], seen: Set[TypeResolution]): Opt[DeclaredType] = + def applyAlias(base: DeclaredType, supplied: TypeApplication, seen: Set[TypeResolution]): Opt[DeclaredType] = if seen(base.resolution) then N else base.resolution.currentShapes.toList match case TypeShape.Alias(symbol, S(rhs)) :: Nil if !aliases(symbol) => val parameters = symbol.defn.get.tparams - if parameters.length != supplied.length then N else - val substitutions = parameters.zip(supplied).map: (parameter, argument) => - parameter.sym -> argumentType(argument, parameter.vce) - S(loop(rhs, effectiveBindings(base) ++ substitutions, aliases + symbol)) + if supplied.arguments.length > parameters.length then N else + // Omitted formals use the same source holes as deferred + // interpretation. Retaining a partial forwarding application + // would nest its environment at each recursive projection. + S(loop(rhs, typeBindings(base, supplied, parameters), aliases + symbol, positive)) case TypeShape.Contextual(reference) :: Nil => // Use exactly the interpreter's application rule: move caller // arguments to the template endpoint, substitute there, then // transport the result back. A wildcard exit followed by entry // is not cancelled; transportType retains its ordinary marks. applyAlias(reference.tpe.instantiate(base.instances), - supplied.map(transportType(_, inverseMarks(reference.marks))), seen + base.resolution) + supplied.move(inverseMarks(reference.marks)), seen + base.resolution) .map(transportType(_, reference.marks)) case _ => N // Reduce arguments in their caller environment before binding formals. // The alias guard grows only on body expansion, so nested Identity // applications reduce while unproductive recursive aliases stop. - applyAlias(loop(base, bindings, aliases), arguments.map(loop(_, bindings, aliases)), Set.empty).getOrElse(retain) + applyAlias(loop(base, bindings, aliases, positive), TypeApplication(arguments.map(loop(_, bindings, aliases, positive)), Nil), Set.empty).getOrElse(retain) case _ => retain - loop(resolution, bindings, Set.empty) + loop(resolution, bindings, Set.empty, positive) /** Normalize Boolean combinations without observing an atom's candidates. * Transport distributes over components using the ordinary transportType @@ -511,8 +526,12 @@ class NewResolver: }) private def extremeType(top: Bool)(using NewResolverState): DeclaredType = - rstate.extremeTypes.getOrElseUpdate(top, { - val resolution = new TypeResolution(Term.Missing, _ => lastWords("A synthesized extreme type cannot be invalid")) + extremeType(top, N) + + private def extremeType(top: Bool, source: Opt[TypeResolution])(using NewResolverState): DeclaredType = + rstate.extremeTypes.getOrElseUpdate((top, source), { + val resolution = new TypeResolution(source.fold[Term](Term.Missing)(_.source), + _ => lastWords("A synthesized extreme type cannot be invalid")) resolution.publish(if top then TypeShape.Top else TypeShape.Bottom) declaredType(resolution, Map.empty) }) @@ -715,9 +734,13 @@ class NewResolver: arguments = specialized.arguments.map(_.instantiate(instances)), instances = instances ++ specialized.instances) case callable: CallableTypeShape => val captured = instances -- callable.tparams.map(_.parameter.symbol) + def instantiate(tpe: DeclaredType): DeclaredType = tpe.instantiate(captured) + val scheme = callable.scheme.map: scheme => + scheme.copy(parameters = scheme.parameters.map: parameter => + parameter.copy(lower = parameter.lower.map(instantiate), upper = parameter.upper.map(instantiate))) callable.copy(paramLists = callable.paramLists.map: params => - DeclaredParams(params.params.map(_.map(_.instantiate(captured))), params.hasRest, params.rest.map(_.instantiate(captured))) - , result = callable.result.map(_.instantiate(captured))) + DeclaredParams(params.params.map(_.map(instantiate)), params.hasRest, params.rest.map(instantiate)) + , result = callable.result.map(instantiate), scheme = scheme) case literal: IntroShape if !literal.trm.isInstanceOf[Lam] => literal case _: (UnknownValueShape | DynShape | OpaqueTypeShape | ErrShape) => source case _ => ContextualShape(source, instances) @@ -940,12 +963,12 @@ class NewResolver: val td = symbol.defn.get val crossesValueScope = !(td.k is syntax.RecordField) && !td.tsym.decl.exists(_.isInstanceOf[Param]) val callerInstances = rstate.instances - val instances = if isByName(td) then instantiateByName(td, flow, - ExitMark(ResolutionBoundary(td.tsym), S(flow), NoMarks) :: receiverMarks, specialization) else callerInstances // A selected generic method binds its own parameters anew. Receiver // bindings can mention an earlier call of that same source method in // their values, but cannot bind the new method's local names. val capturedBindings = bindings -- definitionBinders(td) + val instances = if isByName(td) then instantiateByName(td, flow, + ExitMark(ResolutionBoundary(td.tsym), S(flow), NoMarks) :: receiverMarks, specialization, capturedBindings) else callerInstances def publish(shape: TermShape)(using NewResolverState): Unit = val Marked(interface, context) = shape val generic = interface match @@ -1038,6 +1061,18 @@ class NewResolver: parameters.zip(arguments).foreach: (parameter, argument) => publishParameter(parameter.host, InstanceShape(argument).enter(marks)) + /** Bounds constrain the instantiated references, never the shared checking + * binders. Install them after suppliedness is recorded, so a lower bound is + * checked against an explicit argument rather than widening its interface. + * Recursive and mutually dependent bounds reuse the ordinary relation graph. + */ + private def constrainQuantifiedBounds(parameters: Ls[DeclaredTypeParameter], substitution: Map[VarSymbol, TypeParameterInstance]) + (using NewResolverState): Unit = + parameters.foreach: parameter => + val instance = ContextualType(parameterType(parameter.parameter).instantiate(substitution), Nil) + parameter.lower.foreach(bound => constrainTypes(ContextualType(bound.instantiate(substitution), Nil), instance)) + parameter.upper.foreach(bound => constrainTypes(instance, ContextualType(bound.instantiate(substitution), Nil))) + /** Consume the scheme at its authoritative site. Specialized values and curried * tails carry instantiated references and no scheme, so later calls reuse it. */ @@ -1063,6 +1098,7 @@ class NewResolver: val instance = substitution(parameter.parameter.symbol) TypeShape.Parameter(instance, instance.inferenceHost) bindTypeArguments(parameters, marks, arguments) + constrainQuantifiedBounds(scheme.parameters, substitution) instantiated /** The body must satisfy its annotated result even if no value escapes or no @@ -1156,27 +1192,40 @@ class NewResolver: follow(if positive then parts.output else parts.input, args, captures, next) case TypeShape.Nominal(cls) => listenInstanceViews(value): value => val arguments = typeArguments(tpe, args, cls.tparams).map(transportType(_, captures)) - val (actual, instances) = contextualParts(value) - val (head, context) = actual.applicationHead - def constrain(param: TyParam, bound: DeclaredType, pattern: DeclaredType)(using NewResolverState): Unit = - listenArgumentParts(bound.instantiate(instances)): actual => - listenArgumentParts(argumentType(pattern, param.vce)): expected => - constrainTypes(ContextualType(actual.output, context), ContextualType(expected.output, Nil)) - constrainTypes(ContextualType(expected.input, Nil), ContextualType(actual.input, context)) - def constrainNominal(nominal: NominalInstanceView)(using NewResolverState): Unit = - if nominal.defn is cls then cls.tparams.zip(arguments).foreach: (param, pattern) => - nominal.bindings.get(param.sym).foreach(constrain(param, _, pattern)) - head match - case _: UnknownValueShape => - cls.tparams.zip(arguments).foreach: (param, argument) => - listenArgumentParts(argumentType(argument, param.vce)): parts => - inferTypeArguments(parts.output, value, marks) - case ds: DefnShape if ds.clsDef.contains(cls) => cls.tparams.zip(arguments).foreach: (param, pattern) => + def constrainNominal(value: TermShape)(using NewResolverState): Unit = + val (actual, instances) = contextualParts(value) + val (head, context) = actual.applicationHead + def constrain(param: TyParam, bound: DeclaredType, pattern: DeclaredType)(using NewResolverState): Unit = + listenArgumentParts(bound.instantiate(instances)): actual => + listenArgumentParts(argumentType(pattern, param.vce)): expected => + constrainTypes(ContextualType(actual.output, context), ContextualType(expected.output, Nil)) + constrainTypes(ContextualType(expected.input, Nil), ContextualType(actual.input, context)) + def originalParameters()(using NewResolverState): Unit = cls.tparams.zip(arguments).foreach: (param, pattern) => val bound = parameterType(TypeShape.Parameter(param.sym, param.sym.inferenceHost)) constrain(param, argumentType(bound, param.vce), pattern) - case nominal: NominalInstanceView => constrainNominal(nominal) - case tuple: TupleShape => constrainNominal(tuple.arrayParent) - case _ => () + def parent(ext: TermShape)(using NewResolverState): Unit = + // Parent arguments live at the parent's endpoint, which can cross + // both class and enclosing scopes. Preserve those marks together + // with the child's substitution on every step of the ancestry. + transportShape(instantiateShape(ext, instances), context) match + case value: TermShape => constrainNominal(value) + case NoShape => () + head match + case _: UnknownValueShape => + cls.tparams.zip(arguments).foreach: (param, argument) => + listenArgumentParts(argumentType(argument, param.vce)): parts => + inferTypeArguments(parts.output, value, marks) + case ds: DefnShape => + if ds.clsDef.contains(cls) then originalParameters() else ds.ext.foreach(parent) + case base: BaseShape => + if base.defn is cls then originalParameters() else base.ext.foreach(parent) + case nominal: NominalInstanceView => + if nominal.defn is cls then cls.tparams.zip(arguments).foreach: (param, pattern) => + nominal.bindings.get(param.sym).foreach(constrain(param, _, pattern)) + else nominal.parent.foreach(parent) + case tuple: TupleShape => parent(tuple.arrayParent) + case _ => () + constrainNominal(value) case TypeShape.Top => () case _ => value match // A concrete target stays fixed, but must receive bounds that arrive @@ -1196,7 +1245,7 @@ class NewResolver: actual: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = fields.foreach: (field, sign) => val expected = declaredType(sign, bindings, positive) - val flow = FlowSymbol.memSym(field.sym)(using rstate.owner) + val flow = rstate.fieldProjectionSite(field.sym) def receive(value: TermShape)(using NewResolverState): Unit = inferTypeArguments(expected, value, marks) def member(info: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match case MemberLookup.Contextual(source, substitution) => member(source, instances ++ substitution) @@ -2302,16 +2351,25 @@ class NewResolver: * annotation instead belongs to the returned value and remains unconsumed. */ private def definitionBinders(td: TermDefinition): Ls[VarSymbol] = - def signatureBinders(sign: Term): Ls[VarSymbol] = sign match - case Forall(parameters, _, body) => parameters.map(_.sym) ::: signatureBinders(body) + td.tparams.toList.flatten.map(_.sym) ::: definitionQuantifiers(td).map(_.sym) + + private def definitionQuantifiers(td: TermDefinition): Ls[QuantVar] = + def signatureQuantifiers(sign: Term): Ls[QuantVar] = sign match + case Forall(parameters, _, body) => parameters ::: signatureQuantifiers(body) case _ => Nil - td.tparams.toList.flatten.map(_.sym) ::: - (if td.flags.hasResultAnnotation then Nil else td.sign.toList.flatMap(signatureBinders)) + if td.flags.hasResultAnnotation then Nil else td.sign.toList.flatMap(signatureQuantifiers) private def instantiateByName(td: TermDefinition, site: FlowSymbol, marks: Ls[Marks], - specialization: Opt[(FlowSymbol, Ls[DeclaredType])])(using NewResolverState): Map[VarSymbol, TypeParameterInstance] = - instantiateParameters(td.tsym, definitionBinders(td), specialization.fold(site)(_._1), + specialization: Opt[(FlowSymbol, Ls[DeclaredType])], bindings: Map[VarSymbol, DeclaredType]) + (using NewResolverState): Map[VarSymbol, TypeParameterInstance] = + val instances = instantiateParameters(td.tsym, definitionBinders(td), specialization.fold(site)(_._1), marks, rstate.instances, specialization.map(_._2)) + val parameters = definitionQuantifiers(td).map: parameter => + DeclaredTypeParameter(TypeShape.Parameter(parameter.sym, parameter.sym.inferenceHost), + parameter.lb.map(bound => declaredType(typeResolution(bound), bindings)), + parameter.ub.map(bound => declaredType(typeResolution(bound), bindings))) + constrainQuantifiedBounds(parameters, instances) + instances private def fromBMSAt(bms: BlockMemberSymbol, resSym: FlowSymbol, markss: Ls[Marks], listener: Listener, trm: Term, selected: ShapeListener[DefinitionSymbol[?]], receiver: Bool, @@ -2349,7 +2407,7 @@ class NewResolver: log(s"listenTerm: td.body = ${td.body.fold("N")(_.showDbg)}") val callerInstances = rstate.instances val instances = if isByName(td) then instantiateByName(td, resSym, - ExitMark(ResolutionBoundary(td.tsym), S(resSym), NoMarks) :: markss, specialization) + ExitMark(ResolutionBoundary(td.tsym), S(resSym), NoMarks) :: markss, specialization, Map.empty) else callerInstances -- td.tparams.toList.flatten.map(_.sym) def receive(shape: TermShape)(using NewResolverState): Unit = wrappedListener(instantiateShape(shape, instances))(using rstate.withInstances(callerInstances)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 93d95231c8..332df4a19c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -216,6 +216,8 @@ final class NewResolverState private ( new Cache(inherited.map(_.regularTypes), identity) val combinedTypes: Cache[TypeFormula[DeclaredType], DeclaredType] = new Cache(inherited.map(_.combinedTypes), identity) + val wildcardTypes: Cache[(TypeResolution, TypeArgument), DeclaredType] = + new Cache(inherited.map(_.wildcardTypes), identity) val pendingTypeDependencies: Cache[TypeResolution, TypeDependencyHost] = new Cache(inherited.map(_.pendingTypeDependencies), identity) val dependencySubscriptions: Seen[(TypeResolution, TypeResolution)] = @@ -247,6 +249,15 @@ final class NewResolverState private ( val key = new Identity(application) root.typeApplicationSites.getOrElseUpdate(key, typeApplicationSites.get(key).getOrElse( FlowSymbol("type application")(using owner))) + private val fieldProjectionSites: Cache[BlockMemberSymbol, FlowSymbol] = + new Cache(inherited.map(_.fieldProjectionSites), identity) + /** A structural field constraint is a static projection site. Recursive + * observations share it across substitution views; ordinary receiver marks + * distinguish activations, as they do for a written member selection. + */ + def fieldProjectionSite(field: BlockMemberSymbol): FlowSymbol = + root.fieldProjectionSites.getOrElseUpdate(field, fieldProjectionSites.get(field).getOrElse( + FlowSymbol.memSym(field)(using owner))) private[hkmc2] def instantiateTypeParameters(scheme: AnyDefinitionSymbol | TypeResolution, site: FlowSymbol, parameters: Ls[VarSymbol]): Map[VarSymbol, TypeParameterInstance] = require(parameters.distinct.length == parameters.length, "A scheme cannot bind a parameter twice") @@ -273,7 +284,7 @@ final class NewResolverState private ( new Cache(inherited.map(_.patternTypes), identity) val primitiveTypes: Cache[ClassSymbol, DeclaredType] = new Cache(inherited.map(_.primitiveTypes), identity) - val extremeTypes: Cache[Bool, DeclaredType] = + val extremeTypes: Cache[(Bool, Opt[TypeResolution]), DeclaredType] = new Cache(inherited.map(_.extremeTypes), identity) val variantTypes: Cache[(DeclaredType, Bool), DeclaredType] = new Cache(inherited.map(_.variantTypes), identity) diff --git a/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls b/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls index 7ae1d26487..8631c13aa8 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls @@ -8,23 +8,17 @@ private fun read[A](parent: Parent[A]) = parent.item read(new Parent[Item](Item(1))).value //│ = 1 -// FIXME: A constructed subclass must transfer its instantiated Parent argument. +// A constructed subclass must transfer its instantiated Parent argument. // The field signature is complete, so this does not require inferring members. -:fixme :expect 2 class Item(val value: Int) class Parent[A](val item: A) class Child[A](item: A) extends Parent[A](item) private fun read[A](parent: Parent[A]) = parent.item read(new Child[Item](Item(2))).value -//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.19: read(new Child[Item](Item(2))).value -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' +//│ = 2 -// FIXME: A declared subclass interface must likewise follow its parent view. -:fixme +// A declared subclass interface must likewise follow its parent view. :expect 3 class Item(val value: Int) class Parent[A](val item: A) @@ -32,8 +26,42 @@ class Child[A](item: A) extends Parent[A](item) private fun read[A](parent: Parent[A]) = parent.item private fun child(): Child[Item] = new Child[Item](Item(3)) read(child()).value -//│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.34: read(child()).value -//│ ╙── ^^^^^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' +//│ = 3 + +// Multiple parent steps compose substitutions, including permuted arguments. +:expect [4, 5] +class First(val first: Int) +class Second(val second: Int) +class Parent[A, B](val left: A, val right: B) +class Middle[X, Y](x: X, y: Y) extends Parent[Y, X](y, x) +class Child[P, Q](p: P, q: Q) extends Middle[P, Q](p, q) +private fun read[A, B](parent: Parent[A, B]) = [parent.left, parent.right] +let result = read(new Child[Second, First](Second(5), First(4))) +[result.0.first, result.1.second] +//│ = [4, 5] +//│ result = [First(4), Second(5)] + +// A declared parent chain retains the same permutation and both call contexts. +:expect [6, 7] +class First(val first: Int) +class Second(val second: Int) +class Parent[A](val item: A) +class Middle[X](x: X) extends Parent[X](x) +class Child[Y](y: Y) extends Middle[Y](y) +private fun read[A](parent: Parent[A]) = parent.item +private fun first(): Child[First] = new Child[First](First(6)) +private fun second(): Child[Second] = new Child[Second](Second(7)) +[read(first()).first, read(second()).second] +//│ = [6, 7] + +// An enclosing function's parameter remains captured while following a parent. +:expect [8, 9] +class First(val first: Int) +class Second(val second: Int) +class Parent[A](val item: A) +private fun read[A](parent: Parent[A]) = parent.item +private fun make[T](item: T) = + class Child extends Parent[T](item) + read(new Child) +[make(First(8)).first, make(Second(9)).second] +//│ = [8, 9] diff --git a/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls b/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls index de5cdd322e..d663736240 100644 --- a/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls +++ b/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls @@ -6,13 +6,14 @@ declare fun make: [A extends Item] -> () -> A private fun read() = make[Item]().value // FIXME: Every result satisfies the upper bound, even without explicit arguments. -// Quantified bounds are currently retained as metadata but not used by inference. +// The upper constraint is installed, but observing an unconstrained instance +// still has no interface. Its guarantee must be kept apart from lower candidates. :fixme class Item(val value: Int) declare fun make: [A extends Item] -> () -> A private fun read() = make().value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.13: private fun read() = make().value +//│ ║ l.14: private fun read() = make().value //│ ╙── ^^^^^^^^^^^^ // FIXME: A generic implementation should also be checked using its upper bound. @@ -21,14 +22,110 @@ class Item(val value: Int) fun read: [A extends Item] -> A -> Int fun read(x) = x.value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.22: fun read(x) = x.value +//│ ║ l.23: fun read(x) = x.value //│ ╙── ^^^^^^^ // A lower bound must supply an inference constraint before the result is used. -:fixme class Item(val value: Int) declare fun make: [A restricts Item] -> () -> A private fun read() = make().value + +// Dependent lower bounds share the entire instance group, including binders +// whose arguments arrive after the bound relation is installed. +class Item(val value: Int) +declare fun make: [A, B restricts A] -> A -> B +private fun read() = make(Item(1)).value + +class Item(val value: Int) +declare fun make: [A restricts B, B restricts Item] -> () -> A +private fun read() = make().value + +// An upper-bound relation must infer the nested argument from a later A input. +class Item(val value: Int) +class Box[T](val item: T) +declare fun make: [A extends Box[B], B] -> A -> B +private fun read() = make(new Box[Item](Item(2))).value + +// An explicit A supplies the same upper-bound obligation without a value input. +class Item(val value: Int) +class Box[T](val item: T) +declare fun make: [A extends Box[B], B] -> () -> B +private fun read() = make[Box[Item], Item]().value + +// Lower bounds constrain a supplied type, but do not enlarge its interface. +:e +class Base +class Child(val extra: Int) extends Base +declare fun make: [A restricts Child] -> () -> A +private fun read() = make[Base]().extra +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.60: private fun read() = make[Base]().extra +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── Value of type 'Base' does not contain member 'extra' + +// A by-name invocation consumes the quantified scheme at the selection itself. +// Its recursive body supplies no concrete candidate. +class Item(val value: Int) +fun make: [A restricts Item] -> A +fun make = make +private fun read() = make.value + +// A recursive lower bound connects a finite graph instead of unfolding a tree. +declare fun make: [A restricts {next: A}] -> () -> A +private fun walk(n: Int) = + let x = make() + if n > 0 then x.next.next.next else x + +// Upper bounds are obligations, not additional inferred lower candidates. +// Seeding Base into A's candidate host would incorrectly hide Child.extra. +class Base +class Child(val extra: Int) extends Base +declare fun identity: [A extends Base] -> A -> A +private fun read() = identity(Child(4)).extra + +// A generic body cannot assume its lower bound's members: A may be a supertype. +:e +class Item(val value: Int) +fun read: [A restricts Item] -> A -> Int +fun read(x) = x.value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.31: private fun read() = make().value -//│ ╙── ^^^^^^^^^^^^ +//│ ║ l.90: fun read(x) = x.value +//│ ╙── ^^^^^^^ + +// Capturing an enclosing binder specializes the bounds as well as the result. +:expect [5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun make[T](item: T) = + fun nested: [A restricts T] -> () -> A + fun nested() = item + nested +[make(First(5))().first, make(Second(6))().second] +//│ = [5, 6] + +// FIXME: The recursive body leaks a checking-only T candidate through its +// inferred constraints, even though the declared receiver fixes T to Item. +:fixme +class Item(val value: Int) +class Provider[T] with + fun make: [A restricts T] -> A + fun make = make +private fun read(provider: Provider[Item]) = provider.make.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.113: private fun read(provider: Provider[Item]) = provider.make.value +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.111: fun make: [A restricts T] -> A +//│ ║ ^ +//│ ╟── Type parameter 'T' does not specify a member interface. +//│ ║ l.110: class Provider[T] with +//│ ╙── ^ + +// Without that recursive checking edge, the declared receiver supplies its +// lexical class bindings to the by-name bound. The body supplies no candidates. +class Item(val value: Int) +class Provider[T] with + fun make: [A restricts T] -> A + fun make = throw 0 +private fun read(provider: Provider[Item]) = provider.make.value diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls index 960e8372ef..6477f04f9f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls @@ -8,9 +8,9 @@ private fun read(x: Choice[Item]) = x.value read(Item(1)) //│ = 1 -// FIXME: A productive recursive union adds no unknown alternative. This -// predates Boolean normalization: the alias recursion guard publishes an opaque -// candidate alongside Item, preventing lookup even though the unfolding is finite. +// FIXME: This expected result assumes the least fixed point of the unguarded +// recursive union. The alias guard currently publishes an opaque alternative. +// Choosing fixed-point semantics or rejecting unguarded cycles needs a design decision. :fixme :expect 2 class Item(val value: Int) @@ -26,3 +26,32 @@ read(Item(2)) //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' + +// An unguarded self-alias has no concrete interface. Dropping the recursion +// candidate must not make an arbitrary member operation silently acceptable. +:e +type Loop = Loop +private fun read(x: Loop) = x.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.34: private fun read(x: Loop) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This abstract type does not specify a member interface. +//│ ║ l.34: private fun read(x: Loop) = x.value +//│ ╙── ^^^^ + +// The equation X = Item | X has many solutions; Item is the least one, Any +// the greatest. Conversely, X = Item & X has Nothing as its least solution +// and Item as its greatest. Preserve the current diagnostic until the recursive +// alias contract specifies a fixed point or rejects these unguarded equations. +:e +class Item(val value: Int) +type Choice = Item & Choice +private fun read(x: Choice) = x.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.50: private fun read(x: Choice) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This abstract type does not specify a member interface. +//│ ║ l.50: private fun read(x: Choice) = x.value +//│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphPartialAliases.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphPartialAliases.mls index 4dfaead9d4..57e94d8075 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphPartialAliases.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphPartialAliases.mls @@ -6,32 +6,24 @@ type Chain[A] = {value: A, next: Chain[First[A, Int]]} private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () -// FIXME: Omitting that same unused formal must preserve the finite graph. -// Eager normalization currently retains First[A], nesting another binding on -// every recursive constraint until hashing the environment overflows the stack. -:fixme +// Omitting that same unused formal must preserve the finite graph. type First[A, B] = A type Chain[A] = {value: A, next: Chain[First[A]]} private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () -//│ /!!!\ Uncaught error: java.lang.StackOverflowError -// FIXME: A fully supplied forwarding alias still contains the partial application. -:fixme +// A fully supplied forwarding alias may itself contain a partial application. type First[A, B] = A type Forward[A] = First[A] type Chain[A] = {value: A, next: Chain[Forward[A]]} private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () -//│ /!!!\ Uncaught error: java.lang.StackOverflowError -// FIXME: Partial projections must also permit a finite argument permutation. -:fixme +// Partial projections must also permit a finite argument permutation. type First[A, B, C] = A type Chain[A, B] = {value: A, next: Chain[First[B, A], First[A, B]]} private fun walk[A, B](chain: Chain[A, B], n: Int) = if n > 0 then walk(chain.next, n - 1) else () -//│ /!!!\ Uncaught error: java.lang.StackOverflowError // A fixed omitted argument is a reset with a stable source hole. type Identity[A] = A @@ -48,3 +40,10 @@ private fun walk[A](chain: Chain[A], n: Int) = class Chain[A](val value: A, val next: Chain[Array[A]]) private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () + +// A relevant omitted formal resets to its source hole, even though another +// argument changes on the recursive edge. Normalization must reuse that hole. +type Second[A, B] = B +type Chain[A] = {value: A, next: Chain[Second[A]]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphProjectionSites.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphProjectionSites.mls index 4d15f75a58..29c3e76021 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphProjectionSites.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphProjectionSites.mls @@ -7,32 +7,45 @@ class Node with private fun take(x: Link) = () take(new Node) -// FIXME: An unused type binder must not turn this finite constraint into -// infinitely many instantiations. constrainRecord creates a fresh field flow -// on every visit, and by-name invocation uses that flow as its binder site. -:fixme +// An unused type binder must not turn this finite constraint into +// infinitely many instantiations. The source field is a stable projection site. type Link = {next: Link} class Node with fun next[A] = this private fun take(x: Link) = () take(new Node) -//│ /!!!\ Uncaught error: java.lang.StackOverflowError -// FIXME: The declared-result path has the same site-allocation problem. -:fixme +// Declared results obey the same finite-site rule. type Link = {next: Link} class Node with fun next[A]: Node = this private fun take(x: Link) = () take(new Node) -//│ /!!!\ Uncaught error: java.lang.StackOverflowError -// FIXME: Separate quantified signatures must obey the same finite-site rule. -:fixme +// Separate quantified signatures obey the same finite-site rule. type Link = {next: Link} class Node with fun next: [A] -> Node fun next = this private fun take(x: Link) = () take(new Node) -//│ /!!!\ Uncaught error: java.lang.StackOverflowError + +// Sharing the static projection site must preserve the receivers' ordinary +// activation marks and their distinct class arguments through the recursive edge. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +type Link[A] = {value: A, next: Link[A]} +class Node[T](val value: T) with + fun next[Unused] = this +private fun read[A](link: Link[A]) = link.next.value +[read(new Node[First](First(1))).first, read(new Node[Second](Second(2))).second] +//│ = [1, 2] + +// A quantified lower bound can itself close the projection cycle. +type Link = {next: Link} +class Node with + fun next: [A restricts Node] -> A + fun next = this +private fun take(x: Link) = () +take(new Node) diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index a2df21e9c1..d8e690098f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -373,22 +373,18 @@ private fun walk[A, B](chain: Stable[A, B], n: Int) = //│ ║ l.362: if n > 0 then walk(chain.next, n - 1) else () //│ ╙── ^^^^^^^^^^ -// Even a constructor-free output wildcard is rejected by the conservative check. -// All positive value components remain A; the deferred polarity analysis should -// distinguish an argument part from a growing nominal/tuple/function constructor. -:e +// A constructor-free output wildcard preserves A and reaches a finite graph. +// Normalizing both argument parts must not retain nested wildcard environments. type Chain[A] = {value: A, next: Chain[out A]} private fun walk[A](chain: Chain[A], n: Int) = if n > 0 then walk(chain.next, n - 1) else () -//│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.380: type Chain[A] = {value: A, next: Chain[out A]} -//│ ║ ^^^^^^^^^^^^ -//│ ╟── Recursive type 'Chain' is not supported by the current regularity check. -//│ ║ l.380: type Chain[A] = {value: A, next: Chain[out A]} -//│ ║ ^^^^^^^^^^^^ -//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.380: type Chain[A] = {value: A, next: Chain[out A]} -//│ ╙── ^ -//│ ╔══[COMPILATION ERROR] This selection of member 'next' has no resolved target -//│ ║ l.382: if n > 0 then walk(chain.next, n - 1) else () -//│ ╙── ^^^^^^^^^^ + +// Input-only and two-part wildcards are finite too. Neither part may retain +// the preceding wildcard as an unevaluated binding layer. +type Chain[A] = {value: A, next: Chain[in A]} +private fun walk[A](chain: Chain[A], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () + +type Chain[A, B] = {value: A, next: Chain[in B out A, in A out B]} +private fun walk[A, B](chain: Chain[A, B], n: Int) = + if n > 0 then walk(chain.next, n - 1) else () From cbc78eb64a25cfeb5b4567569d9e86aecf5b13e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 15:28:09 +0800 Subject: [PATCH 128/228] Locate declared members at their class's definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A declared member's signature is interpreted at its class's definition, but member projection appended the whole receiver path to the member, so the member's scope moved with each receiver's origin. In `fun mk(xs) = xs.map((x, ...) => mk([x]))`, `[1]` reaches `map` with the path `↘mk` and `[x]` with `↘mk ↗mk`. Both share the type-parameter instance of the same `map` selection, so a candidate published through one receiver exited `mk` twice when read through the other, failing the mark-path assertion. Declaring Array.reduce exposed this in FingerTreeList. Receiver exits from scopes that do not enclose the class's definition are now treated as provenance: they transport the class's argument bindings, not the member's scope. Elaboration records each type definition's enclosing resolution scopes for this split. Inherited declared members also failed to exit the class through which they were selected, although the parent reference is captured into it: `fun one() = 1; one().toString()` crossed Int's entry with the exit from `one`. They now exit that class like its own members. Co-Authored-By: Claude Opus 5.5 --- doc/new-resolution-suite-migration.md | 4 +- doc/new-resolution-type-value-flow.md | 16 +++- .../scala/hkmc2/semantics/Elaborator.scala | 5 ++ .../scala/hkmc2/semantics/NewResolver.scala | 83 +++++++++++++++++++ .../hkmc2/semantics/NewResolverState.scala | 4 + .../main/scala/hkmc2/semantics/Shape.scala | 39 ++++----- .../src/test/mlscript/newres/Arrays.mls | 17 +++- .../test/mlscript/newres/GenericMethods.mls | 22 +++++ .../test/mlscript/newres/PrimitiveMembers.mls | 8 ++ 9 files changed, 172 insertions(+), 26 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 1561b20e8f..2179ae643f 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -76,8 +76,8 @@ The remaining option consumers have these blockers when compiled with | --- | --- | | `Block`, `Shape` | Missing nominal members and callback arity mismatches; `Block` also needs a public interface for `showArm`. | | `Iter`, `MutMap`, `ups/EvaluationContext` | Missing public parameter interfaces and unresolved member selections. | -| `FingerTreeList` | Assertion failure in the mark-path invariant ("Repeated exit from the same lexical resolution scope") when `mk` calls `args.reduce` with a callback that feeds the accumulator back into `mk`. The same failure occurs for `map` when a callback element reaches the receiver through a recursive call; `newres/Arrays.mls` records it as a `:fixme`. | -| `parsing/Extension`, `ParseRule`, `Test`, `TreeHelpers` | Repeated-entry mark assertion during nominal field projection: `NominalInstanceView.getMemberImpl` calls `captureType` on an argument whose path already contains that scope. Determine the source of the duplicate transfer without weakening the marks algebra. | +| `FingerTreeList` | Compilation exceeds the 25-second test limit. Tuple literals with several spreads, such as `concatMiddle`'s `[...ay1, ...middle, ...ax2]`, produce one candidate per combination of operand candidates, and each is matched again by `toNodes`. Candidate sets compare shapes structurally, which rehashes these deep shapes; identity-based candidate storage is the next step. | +| `parsing/Extension`, `ParseRule`, `Test` | Selections on values imported from legacy-resolution modules, such as `Parser.tracer`, have no resolved target. `TreeHelpers` compiles standalone; its consumers remain to be checked. | | `parsing/Lexer` | Opened binary `~` conflicts with the builtin; calls with trailing contextual parameters leave function values where tokens are expected. | | `parsing/Parser` | Pattern-field flow and unresolved nominal members. | | `parsing/ParseRuleVisualizer`, `Rules`, `parsing-web-demo/main` | Missing host/public interfaces and unresolved selections. | diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 97bd342a93..c9e3282c31 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -190,7 +190,9 @@ The relevant scope crossings are: - Function/method entry and exit, and lexical captures of enclosing definitions. - Nominal member projection: capture argument references into the class and exit - the class when publishing its selected member view. + the class when publishing its selected member view. Inherited members also exit + the class through which they are selected, since the parent reference is + captured into that class. - Constructor invocation: use the same instance boundary for its class and constructor, including later parameter lists. @@ -202,6 +204,18 @@ composes paths on deferred instance references before delivering them at project and result boundaries. Leaving a wildcard entry outside a wrapper could consume a call exit before the wrapper's own path was considered. +A declared member is located at its class's definition, where its signature's +`Capture` nodes start. A receiver path starts where the value was created or +annotated instead. Its innermost exits from scopes that do not enclose the class's +definition are the receiver's provenance: they transport the class's argument +bindings, but not the member's own scope. Otherwise a member's scope would move +with each receiver's origin. Receivers created at different depths then disagree +about the scopes that enclose shared nodes such as a method's type-parameter +instance at one call site, and a candidate published through one receiver crosses +a scope twice when read through another. `NewResolver.nominalMember` performs this +split using the enclosing scopes that elaboration records for each type definition. +`newres/Arrays.mls` and `GenericMethods.mls` cover recursive and nested receivers. + New-resolution syntax records lexical `Capture` nodes, including in the prelude. Imported type interpretations retain these source references in the consuming graph; they do not reconstruct capture paths from selected member parameters. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index de32081983..5fc349c651 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -124,6 +124,10 @@ object Elaborator: env.valuesIterator.flatMap(_.symbol).collect: case symbol: VarSymbol if symbol.decl.exists(_.isInstanceOf[TyParam]) => symbol + /** The resolution scopes that references from this context capture through. */ + lazy val resolutionBoundaries: Set[ResolutionBoundary] = + parent.fold(Set.empty[ResolutionBoundary])(_.resolutionBoundaries) ++ outer.resolutionBoundary.map(ResolutionBoundary(_)) + def +(local: Str -> Symbol): Ctx = copy(env = env + local.mapSecond(Ctx.RefElem(_))) def ++(locals: IterableOnce[Str -> Symbol]): Ctx = @@ -2287,6 +2291,7 @@ extends Importer: val outerCtx = ctx rstate.lexicalTypeBinders(td.symbol) = ctx.typeBinders + rstate.lexicalBoundaries(td.symbol) = ctx.resolutionBoundaries var newCtx = S(td.symbol).collectFirst: case s: InnerSymbol => s diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 65ea1a2d68..e650ba51e8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1361,6 +1361,89 @@ class NewResolver: publishParameter(param, element.enter(context :: Nil)) shape + /** Member lookup on a declared nominal interface reached through `receiver`. + * + * A member's signature is interpreted at its class's definition: its Capture + * nodes cross from the member into the class's enclosing scopes. The receiver + * path, however, starts where the value was created or annotated, which can be + * nested more deeply. For example, in + * `fun mk(xs) = xs.map((x, ...) => mk([x]))`, the tuple `[x]` is created in + * one activation of `mk` and passed to another, so its path is `↘mk ↗mk`, + * whereas `mk([1])` passes a tuple with path `↘mk`. The innermost exits of + * scopes that do not enclose the class's definition (here, the first `↗mk`, + * since `Array` is global) are the value's provenance. They transport the + * class's type-argument bindings, but do not move the member's scope. + * Appending the whole path to the member instead places the scope of `map` + * inside `mk` for one receiver and outside it for the other. Both receivers + * share the `U` instance of the same `map` selection. Candidates published + * through one path then cross the same scope twice when they are read + * through the other path. + */ + private[semantics] def nominalMember(view: NominalInstanceView, name: Str, receiver: Marks)(using NewResolverState): MemberLookup = + val defn = view.defn + // Module/object references introduce no value invocation boundary. + val scope = defn.sym match + case _: ModuleOrObjectSymbol => N + case symbol => S(ResolutionBoundary(symbol)) + defn.body.members.get(name) match + case S(member) => + val (location, provenance) = splitReceiverPath(defn, receiver) + // Supplied arguments are outside the class; its parameter annotations + // are inside. Enclosing binders keep their own lexical contexts and + // enter the class through the annotation's explicit Capture nodes. + val parameters = defn.tparams.map(_.sym).toSet + val local = view.bindings.map: (symbol, bound) => + val transported = provenance match + case NoMarks => bound + case provenance: ExitMark => transportType(bound, provenance :: Nil) + symbol -> (if parameters(symbol) && scope.nonEmpty then captureType(transported, defn.sym) else transported) + MemberLookup.Declared(member, local, scope.toList.map(ExitMark(_, N, NoMarks)), view.annotation, true) + .withMarks(location match + case NoMarks => Nil + case location: SomeMarks => location :: Nil) + case N => + (name.toIntOption, arrayElementType(view)) match + // An element's value keeps its whole path, including its provenance. + case (S(index), S(element)) if index >= 0 => + MemberLookup.Indexed(TupleShape.TypedField(element, Nil), Nil).withMarks(receiver :: Nil) + case _ => + // The parent is referenced inside the class, like its own members' + // annotations: `class Int extends Num` captures `Num` into Int's + // scope. An inherited member exits that scope before the receiver + // path applies, just like an own member. + val exit = scope.fold[Marks](NoMarks)(ExitMark(_, N, NoMarks)) + val inherited = view.parent.fold[MemberLookup](MemberLookup.Missing): parent => + val Marked(parentView, inner) = parent + InstanceShape(extremeType(false)).exit(inner).exit(exit).exit(receiver) match + case Marked(_, path) => parentView.getMemberThrough(name, path) + // The parent interface is not reachable through this receiver; + // applying these marks to its members yields no candidates. + case NoShape => parentView.getMember(name).withMarks(inner :: exit :: receiver :: Nil) + inherited.withAnnotation(view.annotation) + + /** Split a receiver path into the path from `defn`'s definition to the consumer, + * and the innermost exits of scopes not enclosing that definition. Exits are + * applied innermost first, so once a path leaves an enclosing scope of `defn`, + * it cannot leave a scope that does not enclose `defn`. + */ + private def splitReceiverPath(defn: ClassLikeDef, receiver: Marks)(using NewResolverState): (Marks, ExitMarks) = + val enclosing = defn.sym.getState.newResolverState.lexicalBoundaries.get(defn.sym).getOrElse( + lastWords(s"Nominal declaration ${defn.sym.nme} must record its enclosing resolution scopes")) + def exits(path: ExitMarks): (ExitMarks, ExitMarks) = path match + case ExitMark(boundary, id, rest) if enclosing(boundary) => + val (location, provenance) = exits(rest) + (ExitMark(boundary, id, location), provenance) + case provenance => + softAssert(!enclosing.exists(provenance.hasExit), + "A receiver path cannot leave an enclosing scope of its class before a nested scope") + (NoMarks, provenance) + def go(path: Marks): (Marks, ExitMarks) = path match + case EntryMark(boundary, id, rest) => + val (location, provenance) = go(rest) + (EntryMark(boundary, id, location), provenance) + case path: ExitMarks => exits(path) + go(receiver) + private[semantics] def arrayElementType(array: NominalInstanceView): Opt[DeclaredType] = val cls = prelude.builtins.Array.defn.get array.ancestor(cls).flatMap(_.bindings.get(cls.tparams.head.sym)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 93d95231c8..da428560ec 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -210,6 +210,10 @@ final class NewResolverState private ( // exporters. Nominal interfaces may use any enclosing explicit type binder. val lexicalTypeBinders: Cache[AnyDefinitionSymbol, Set[VarSymbol]] = new Cache(inherited.map(_.lexicalTypeBinders), identity) + // Recorded with lexicalTypeBinders: the resolution scopes enclosing a type + // definition. Declared members are located at their class's definition. + val lexicalBoundaries: Cache[AnyDefinitionSymbol, Set[ResolutionBoundary]] = + new Cache(inherited.map(_.lexicalBoundaries), identity) val typeDependencies: Cache[TypeResolution, Set[VarSymbol]] = new Cache(inherited.map(_.typeDependencies), identity) val regularTypes: Cache[TypeResolution, Bool] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 8c4affee43..9e7a23e8f2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -118,7 +118,7 @@ end Marked case class MarkedShape(sh: NonMarkedShape, mark: SomeMarks) extends TermShape: protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - sh.getMember(name).withMarks(mark :: Nil) + sh.getMemberThrough(name, mark) override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = sh.isInstanceOfClass(cls) def describe: Str = sh.describe def toLoc: Opt[Loc] = sh.toLoc @@ -148,6 +148,14 @@ sealed trait TermShape extends Shape: final def getMember(name: Str)(using state: NewResolverState): MemberLookup = state.membersCache.getOrElseUpdate((new Identity[TermShape](this), name), getMemberImpl(name)) protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup + /** Lookup on this shape as reached through the receiver path `receiver`. + * Shapes whose members are declared by a nominal class override this to + * locate the member at the class's definition (see NewResolver.nominalMember); + * other members are transported through the whole path. + */ + def getMemberThrough(name: Str, receiver: Marks)(using NewResolverState): MemberLookup = receiver match + case NoMarks => getMember(name) + case receiver: SomeMarks => getMember(name).withMarks(receiver :: Nil) /** Whether this value is an instance of the nominal class. A saturated class * value (e.g. a class without parameters) is still not an instance. */ @@ -420,6 +428,8 @@ final case class ContextualShape(source: NonMarkedShape, instances: Map[VarSymbo def toLoc: Opt[Loc] = source.toLoc override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = source.isInstanceOfClass(cls) protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = source.getMember(name).instantiate(instances) + override def getMemberThrough(name: Str, receiver: Marks)(using NewResolverState): MemberLookup = + source.getMemberThrough(name, receiver).instantiate(instances) /** A type application has consumed the declaration's scheme. Subsequent term * applications retain this binder group and the supplied caller references. @@ -457,24 +467,9 @@ final case class NominalInstanceView(defn: ClassLikeDef, bindings: Map[VarSymbol case Marked(parent: NominalInstanceView, _) => parent.ancestor(cls) case _ => N protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - defn.body.members.get(name) match - case S(member) => - // Supplied arguments are outside the class; its parameter annotations - // are inside. Enclosing binders keep their own lexical contexts and - // enter the class through the annotation's explicit Capture nodes. - val parameters = defn.tparams.map(_.sym).toSet - // Module/object references introduce no value invocation boundary. - val scope = defn.sym match - case _: ModuleOrObjectSymbol => N - case symbol => S(ResolutionBoundary(symbol)) - val local = bindings.map: (symbol, bound) => - symbol -> (if parameters(symbol) && scope.nonEmpty then resolver.captureType(bound, defn.sym) else bound) - MemberLookup.Declared(member, local, - scope.toList.map(ExitMark(_, N, NoMarks)), annotation, true) - case N => - (name.toIntOption, resolver.arrayElementType(this)) match - case (S(index), S(element)) if index >= 0 => MemberLookup.Indexed(TupleShape.TypedField(element, Nil), Nil) - case _ => parent.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)).withAnnotation(annotation) + resolver.nominalMember(this, name, NoMarks) + override def getMemberThrough(name: Str, receiver: Marks)(using NewResolverState): MemberLookup = + resolver.nominalMember(this, name, receiver) /** Parameter types and arity exposed by one list in a declared calling interface. * A rest annotation describes the whole trailing array; hasRest also distinguishes @@ -602,6 +597,9 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element], case Nil => MemberLookup.Missing loop(segments, index) case _ => arrayParent.getMember(name) + override def getMemberThrough(name: Str, receiver: Marks)(using NewResolverState): MemberLookup = name.toIntOption match + case S(index) if index >= 0 => super.getMemberThrough(name, receiver) + case _ => arrayParent.getMemberThrough(name, receiver) /** Values whose members and call results are deliberately checked only at runtime. * Unlike UnknownValueShape, this authorizes dynamic operations; it is introduced @@ -771,6 +769,9 @@ class IntroShape(val trm: IntroTerm, val primitive: Opt[NominalInstanceView]) ex case lam: Term.Lam => MemberLookup.Missing // TODO: methods on lambdas // case newTerm: Term.New => // Map.empty // TODO + override def getMemberThrough(name: Str, receiver: Marks)(using NewResolverState): MemberLookup = trm match + case _: Term.Lit | _: Term.UnitVal => primitive.fold[MemberLookup](MemberLookup.Missing)(_.getMemberThrough(name, receiver)) + case _: Term.Lam => super.getMemberThrough(name, receiver) override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = primitive.exists(_.isInstanceOfClass(cls)) def toLoc: Opt[Loc] = trm.toLoc diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index c924d08dbc..aa3868851d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -226,9 +226,18 @@ class Total(val total: Int) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 -// A callback element fed back into the receiver through a recursive call. -// FIXME: marks transport crosses the same scope twice (assertion failure). -:fixme +// A tuple created in one activation of `mk` and passed to another has the path +// `↘mk ↗mk`, whereas `[1]` has the path `↘mk`. Both receivers share the result +// type parameter of the same `map` selection. The exit from the first activation +// is provenance: Array's members remain at its global definition, so both paths +// read that parameter from the same scope. +:expect [1] private fun mk(xs) = xs.map((x, ...) => if x is Int then x else mk([x])) mk([1]) -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Repeated exit from the same lexical resolution scope +//│ = [1] + +// The accumulator of reduce is fed back into the receiver in the same way. +:expect 0 +private fun mk(...args) = args.reduce((acc, x, ...) => if acc is Int then acc else mk(...acc), 0) +mk(1, 2) +//│ = 0 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 3aab9f12c2..6c2a51250b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -136,3 +136,25 @@ module Local with val mapped = [Known(4)].map((item, ...) => Known(item.known + 1)) Local.mapped.map((item, ...) => item.known) //│ = [5] + +// A local class's members are located inside its enclosing activation. Receivers +// annotated in a nested function add an exit from that function, which is only +// the receiver's provenance: it must not move the members of the local class. +:expect [1, 2, 3] +class First(val first: Int) +class Second(val second: Int) +class Item(val value: Int) +private fun wrap[A](value: A) = + class Local(val item: A) with + fun get[U](f: A -> U): U = f(item) + fun inner() = (new Local(value) as Local) + [inner().item, inner().get(x => x)] +private fun wrapped[A](value: A) = + class Local(val item: A) with + fun get[U](f: A -> U): U = f(item) + (new Local(value) as Local) +private fun viaNested(x) = + fun nested() = wrapped[Item](x) + nested().get(i => i.value) +[wrap[First](First(1)).0.first, wrap[Second](Second(2)).1.second, viaNested(Item(3))] +//│ = [1, 2, 3] diff --git a/hkmc2/shared/src/test/mlscript/newres/PrimitiveMembers.mls b/hkmc2/shared/src/test/mlscript/newres/PrimitiveMembers.mls index f0e89980d7..d6e8b02525 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PrimitiveMembers.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PrimitiveMembers.mls @@ -55,3 +55,11 @@ let xs = Array(3) [xs is Array, xs.length, String(42), (new Error("new")) is Error] //│ = [true, 3, "42", true] //│ xs = [<3 empty items>] + +// Int inherits toString from Num. The parent reference is captured into Int's +// scope, so the inherited member must leave that scope before the receiver's +// path, here the exit from `one`, is applied. +:expect "1" +fun one() = 1 +one().toString() +//│ = "1" From c28d59b64200419b7676f6a08075e2910406024f Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 15:48:18 +0800 Subject: [PATCH 129/228] Reject unguarded recursive type aliases --- doc/new-resolution-future-work.md | 21 +- doc/new-resolution-regular-types.md | 44 +- doc/new-resolution-type-value-flow.md | 7 +- doc/reference.md | 13 +- .../scala/hkmc2/semantics/NewResolver.scala | 144 +++++-- .../hkmc2/semantics/NewResolverState.scala | 2 + .../scala/hkmc2/semantics/TypeShape.scala | 4 + .../test/mlscript/newres/DeclaredTypes.mls | 125 +++--- .../newres/RecursiveBooleanInterfaces.mls | 388 ++++++++++++++++-- .../mlscript/newres/TypeGraphTermination.mls | 104 +++-- 10 files changed, 646 insertions(+), 206 deletions(-) diff --git a/doc/new-resolution-future-work.md b/doc/new-resolution-future-work.md index feeb0b7f68..27017a6e26 100644 --- a/doc/new-resolution-future-work.md +++ b/doc/new-resolution-future-work.md @@ -4,7 +4,9 @@ This reference records confirmed implementation gaps and deferred design work. Current contracts are in [instance types and parameter constraints](new-resolution-type-value-flow.md); remaining suite ports are in the [migration worklist](new-resolution-suite-migration.md). -## Quantified bounds +## Upper-bound interfaces + +This interface and checking-witness redesign is deferred future work. Instantiation installs `lower <: instance <: upper` with the complete binder substitution, after recording explicit arguments. This covers callables and by-name @@ -33,23 +35,6 @@ those guarantees with delayed bounds and explicit arguments, without mixing the checking witness into a real call or losing scope marks. Regression and precision controls: `newres/QuantifiedBounds.mls`. -## Unguarded Boolean alias cycles - -The alias observation guard publishes an opaque candidate when revisiting an alias. -For `Choice[A] = A | Choice[A]`, this prevents member lookup on `Choice[Item]`. -The existing regression requests the least-fixed-point interpretation, where this -alias is `Item`, but the equation alone does not determine that interpretation: -`Any` is also a solution. Conversely, `X = Item & X` has least solution `Nothing` -and greatest solution `Item`. A direct `X = X` supplies no choice at all. - -These cycles are unguarded: reaching the recursive reference crosses no nominal, -record, tuple, or function constructor. Finite Boolean normalization is not a -contract for their fixed points. Choose whether to reject unguarded recursive -aliases or define their fixed-point semantics before changing the guard. Simply -suppressing every repeated candidate would silently choose semantics and leave -pure cycles without an interface or an explanatory diagnostic. Regressions and -current diagnostic controls: `newres/RecursiveBooleanInterfaces.mls`. - ## Receiver reconstruction ```mlscript diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index 89e4b20d11..d312b0ee27 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -4,6 +4,45 @@ This is an internal reference for structural recursion in new resolution. The instance-wrapper and variance semantics are specified in [instance types and parameter constraints](new-resolution-type-value-flow.md). +## Guarded alias recursion + +Every recursive alias cycle must pass through a record, tuple, function arrow, +or nominal type. Aliases, type applications, unions, intersections, negation, +quantifiers, and wildcard bounds do not themselves supply a guard. An abstract +constructor is not assumed to guard its arguments. Reject unguarded cycles at +their definitions, including unused aliases and cycles through forward or mutually +recursive references. For example: + +```mlscript +type Loop = Loop // rejected +type Choice[A] = A | Choice[A] // rejected +type Left = Right // rejected with Right below +type Right = Left + +type Chain = {next: Chain} // guarded +type Wrap[A] = {next: A} +type Indirect = Wrap[Indirect] // guarded after forwarding A +``` + +Guardedness follows alias substitutions: `Identity[Loop]` forwards an unguarded +occurrence, whereas `Wrap[Loop]` above guards its argument. An unused argument +cannot create a cycle in the expanded alias. Repeated finite applications such as +`Identity[Identity[Int]]` are not recursive aliases. + +The check operates on the completed source graph before Boolean normalization; +absorption does not excuse a written unguarded cycle. The source dependency solver +also computes which formals are used without a structural guard. Alias applications +follow those actual arguments when checking for cycles, rather than treating every +argument as unguarded or every application as a guard. These summaries use the same +finite-set fixed point as free-binder discovery, but structural constructors stop +dependency propagation. The cycle check starts a fresh path inside each constructor +so a guarded use cannot hide an invalid alias's own definition. + +Diagnostics name the alias and point to the recursive reference and declaration. +The resolver does not choose least or greatest solutions for unguarded Boolean +equations. This rule is separate from the finite-representation check below: +a guarded alias can still grow its type arguments without bound. + ## Regularity requirement A structural type must unfold into a finite graph of distinct type components. @@ -270,8 +309,9 @@ requirements on the marks algebra remain in force throughout. `newres/TypeGraphTermination.mls` covers regular recursion, permutations, mutual resets, unused arguments, transparent and captured forwarding aliases, forward -references, union/intersection saturation, Boolean-only recursion, and the variance -counterexample. Rejected constructor cycles and the two documented precision +references, union/intersection saturation, rejected unguarded recursion, and the +variance counterexample. `newres/RecursiveBooleanInterfaces.mls` covers direct, +mutual, captured, and forwarding cycles alongside guarded wrapper controls. Rejected constructor cycles and the two documented precision limitations use ordinary `:e` expectations. `TypeFormulaTest` checks normalization against Boolean truth tables. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index f8b4815ae6..4571e58162 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -88,7 +88,7 @@ after recording explicit supplied arguments, so a lower bound cannot widen a supplied interface. By-name invocations install these relations when their scheme is consumed. Recursive and dependent bounds share the existing relation graph. Using an upper guarantee as an interface for an unconstrained result or a generic -checking body remains a [design issue](new-resolution-future-work.md#quantified-bounds). +checking body remains a [design issue](new-resolution-future-work.md#upper-bound-interfaces). Enclosing binders remain lexical captures, not binders of the nested definition being instantiated. @@ -269,8 +269,9 @@ own saved environments. Synthetic formula/argument/selection nodes follow their saved references instead of reading an ambient binding map. This removes irrelevant bindings without freezing inference candidates or treating forward references as closed. -[Regular structural types](new-resolution-regular-types.md) specifies alias reduction, -Boolean normalization, and the conservative constructor-cycle rejection check. +[Regular structural types](new-resolution-regular-types.md) specifies guarded alias +recursion, alias reduction, Boolean normalization, and the conservative +constructor-cycle rejection check. Accepted recursive references must share graph edges rather than grow substituted environments. Structural recursion and recursive generic function constraints are different: a call can add an edge to a reusable parameter instance without eagerly diff --git a/doc/reference.md b/doc/reference.md index a4078ef28d..6e6f54ff9c 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -1404,9 +1404,16 @@ new-resolution migration. type Point = { x: Num, y: Num } ``` -Under new resolution, recursive structural types must have a finite graph -representation of their unfolding. The current check conservatively rejects constructor-bearing cycles between used -type parameters, after alias and union/intersection normalization. For example: +Under new resolution, recursive type aliases must be guarded: every recursive +cycle must pass through a record, tuple, function arrow, or nominal type. +`type Loop = Loop` and `type Choice[A] = A | Choice[A]` are rejected, even when +unused. Alias applications follow the guards in the alias body: a wrapper defined +as `type Wrap[A] = {next: A}` permits `type Loop = Wrap[Loop]`. + +Recursive structural types must also have a finite graph representation of their +unfolding. The current check conservatively rejects constructor-bearing cycles +between used type parameters, after alias and union/intersection normalization. +For example: ```mlscript type Chain[A] = {value: A, next: Chain[A]} diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 584c23d7bf..63a24c7fa4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -123,6 +123,7 @@ class NewResolver: val l = typeResolution(left) val r = typeResolution(right) result.publish(if union then TypeShape.Union(l, r) else TypeShape.Intersection(l, r)) + case Neg(base) => result.publish(TypeShape.Negation(typeResolution(base))) case DynTy() => result.publish(TypeShape.Dynamic) case FunTy(params, ret, _) => result.publish(TypeShape.Function(params, typeResolution(ret))) case UnitVal() => result.publish(TypeShape.Unit) @@ -148,7 +149,7 @@ class NewResolver: case App(Ref(op: BuiltinSymbol), Tup(Fld(_, Lit(_: Tree.IntLit | _: Tree.DecLit), _) :: Nil)) if op.nme === "-" || op.nme === "+" => result.publish(TypeShape.Abstract) - case _: Neg | _: Tup | _: Lit | Missing | Error() => + case _: Tup | _: Lit | Missing | Error() => result.publish(TypeShape.Abstract) case _ => listen(term, discardMarks = true): case shape: SymShape => shape.sym.onComplete: () => @@ -178,23 +179,41 @@ class NewResolver: registerSignature(rhs) case _ => () + // Guard and dependency analysis inspect the declaration behind a capture; + // its value-scope marks do not change whether it supplies a type constructor. + private def typeApplicationTarget(base: TypeResolution)(using NewResolverState): Opt[TypeShape] = + @tailrec + def loop(base: TypeResolution, seen: Set[TypeResolution]): Opt[TypeShape] = + if seen(base) then N else base.currentShapes.toList match + case TypeShape.Captured(inner, _) :: Nil => loop(inner, seen + base) + case shape :: Nil => S(shape) + case _ => N + loop(base, Set.empty) + /** Compute free binders on the finite source graph, not on substituted types. * Each edge can hide binders introduced by an alias or quantifier. Iterating * finite binder sets reaches a fixed point even for mutually recursive types. * An unresolved target is a dependency still to discover, never a closed type. * Valid written types have one target (validation rejects ambiguity). The * synthetic nodes that can receive more candidates use no ambient bindings. + * The unguarded variant uses the same equations but stops dependencies at + * structural constructors, to distinguish forwarding and guarding aliases. */ private def typeDependencies(resolution: TypeResolution)(using NewResolverState) - : Either[Set[TypeResolution], Set[VarSymbol]] = rstate.typeDependencies.get(resolution) match + : Either[Set[TypeResolution], Set[VarSymbol]] = typeDependencies(resolution, false) + + private def typeDependencies(resolution: TypeResolution, unguardedOnly: Bool)(using NewResolverState) + : Either[Set[TypeResolution], Set[VarSymbol]] = + (if unguardedOnly then rstate.unguardedTypeDependencies else rstate.typeDependencies).get(resolution) match case S(binders) => R(binders) case N => + val cache = if unguardedOnly then rstate.unguardedTypeDependencies else rstate.typeDependencies val graph = mutable.LinkedHashMap.empty[TypeResolution, (Set[VarSymbol], Ls[(TypeResolution, Set[VarSymbol])])] val argumentUses = mutable.Map.empty[TypeResolution, Ls[(TypeResolution, VarSymbol, TypeResolution)]] val pending = mutable.Set.empty[TypeResolution] def visit(res: TypeResolution): Unit = if !graph.contains(res) then graph(res) = (Set.empty, Nil) - rstate.typeDependencies.get(res) match + cache.get(res) match case S(binders) => graph(res) = (binders, Nil) case N => val shapes = res.currentShapes.toList @@ -205,6 +224,9 @@ class NewResolver: edges ::= child -> hidden visit(child) def child(res: TypeResolution): Unit = edge(res, Set.empty) + // Both analyses discover the whole source graph. Only free-binder + // projection propagates dependencies across a structural guard. + def guardedChild(res: TypeResolution): Unit = if unguardedOnly then visit(res) else child(res) def reference(tpe: DeclaredType): Unit = visit(tpe.resolution) tpe.bindings.values.foreach(reference) @@ -213,8 +235,9 @@ class NewResolver: case TypeShape.Nominal(defn) => // This immutable declaration metadata belongs to the original // graph, even when the type was reached through an imported AST. - direct ++= defn.sym.getState.newResolverState.lexicalTypeBinders.get(defn.sym).getOrElse( - lastWords(s"Nominal declaration ${defn.sym.nme} must record its enclosing type binders")) + if !unguardedOnly then + direct ++= defn.sym.getState.newResolverState.lexicalTypeBinders.get(defn.sym).getOrElse( + lastWords(s"Nominal declaration ${defn.sym.nme} must record its enclosing type binders")) case TypeShape.Alias(symbol, rhs) => val bound = symbol.defn.get.tparams.map(_.sym).toSet rhs.foreach(edge(_, bound)) @@ -224,23 +247,22 @@ class NewResolver: // An alias argument contributes dependencies only if the body // uses its formal. Solve this with the other equations: a cycle // such as Loop[A] = {next: Loop[Array[A]]} has no use of A. - def arguments(base: TypeResolution, seen: Set[TypeResolution]): Unit = - if seen(base) then args.foreach(child) else base.currentShapes.toList match - case TypeShape.Captured(inner, _) :: Nil => arguments(inner, seen + base) - case TypeShape.Alias(symbol, S(rhs)) :: Nil => - visit(rhs) - val formals = symbol.defn.get.tparams - args.zipWithIndex.foreach: (arg, index) => - formals.lift(index) match - case S(formal) => - visit(arg) - argumentUses(res) = (rhs, formal.sym, arg) :: argumentUses.getOrElse(res, Nil) - case N => child(arg) - case _ => args.foreach(child) - arguments(base, Set.empty) - case TypeShape.Tuple(fields) => fields.foreach(child) - case TypeShape.Record(_, fields) => fields.foreach((_, field) => child(field)) - case TypeShape.Function(params, result) => child(typeResolution(params)); child(result) + typeApplicationTarget(base) match + case S(TypeShape.Alias(symbol, S(rhs))) => + visit(rhs) + val formals = symbol.defn.get.tparams + args.zipWithIndex.foreach: (arg, index) => + formals.lift(index) match + case S(formal) => + visit(arg) + argumentUses(res) = (rhs, formal.sym, arg) :: argumentUses.getOrElse(res, Nil) + case N => child(arg) + case S(TypeShape.Nominal(_)) => args.foreach(guardedChild) + // An abstract constructor could forward an argument unchanged. + case _ => args.foreach(child) + case TypeShape.Tuple(fields) => fields.foreach(guardedChild) + case TypeShape.Record(_, fields) => fields.foreach((_, field) => guardedChild(field)) + case TypeShape.Function(params, result) => guardedChild(typeResolution(params)); guardedChild(result) case TypeShape.Polymorphic(params, _, body) => val bound = params.map(_.parameter.symbol).toSet params.foreach: param => @@ -249,6 +271,7 @@ class NewResolver: edge(body, bound) case TypeShape.Union(left, right) => child(left); child(right) case TypeShape.Intersection(left, right) => child(left); child(right) + case TypeShape.Negation(base) => child(base) case TypeShape.Wildcard(input, output) => input.foreach(child); output.foreach(child) // These synthesized nodes retain their own interpreted references // or inference hosts; none reads the surrounding binding map. @@ -273,10 +296,10 @@ class NewResolver: if next != dependencies(res) then dependencies(res) = next changed = true - dependencies.foreach((res, binders) => rstate.typeDependencies(res) = binders) + dependencies.foreach((res, binders) => cache(res) = binders) R(dependencies(resolution)) - /** Check recursive substitutions on the finite source graph, before observing + /** Check guarded recursion and substitutions on the finite source graph, before observing * structural children can generate an unbounded sequence of environments. * Edges connect original formal parameters, and record whether their argument * puts the source formal underneath a non-Boolean constructor. A growing edge @@ -290,19 +313,18 @@ class NewResolver: private def regularType(root: TypeResolution)(using NewResolverState): Bool = rstate.regularTypes.get(root) match case S(result) => result case N => - val visited = mutable.Set.empty[TypeResolution] + val visited = mutable.LinkedHashSet.empty[TypeResolution] val applications = mutable.ArrayBuffer.empty[(TypeResolution, TypeAliasSymbol, TypeResolution, Ls[TypeResolution])] - def alias(base: TypeResolution, seen: Set[TypeResolution]): Opt[(TypeAliasSymbol, TypeResolution)] = - if seen(base) then N else base.currentShapes.toList match - case TypeShape.Alias(symbol, S(rhs)) :: Nil => S(symbol -> rhs) - case TypeShape.Captured(inner, _) :: Nil => alias(inner, seen + base) + def alias(base: TypeResolution): Opt[(TypeAliasSymbol, TypeResolution)] = + typeApplicationTarget(base) match + case S(TypeShape.Alias(symbol, S(rhs))) => S(symbol -> rhs) case _ => N def visit(res: TypeResolution): Unit = if visited.add(res) then res.currentShapes.foreach: case TypeShape.Alias(_, rhs) => rhs.foreach(visit) case TypeShape.Captured(base, _) => visit(base) case TypeShape.Applied(base, args) => - alias(base, Set.empty).foreach: (symbol, rhs) => + alias(base).foreach: (symbol, rhs) => applications += ((res, symbol, rhs, args)) visit(base) args.foreach(visit) @@ -316,6 +338,7 @@ class NewResolver: visit(body) case TypeShape.Union(left, right) => visit(left); visit(right) case TypeShape.Intersection(left, right) => visit(left); visit(right) + case TypeShape.Negation(base) => visit(base) case TypeShape.Wildcard(input, output) => input.foreach(visit); output.foreach(visit) case TypeShape.Combined(formula) => formula.orderedAtoms.foreach(atom => visit(atom.resolution)) case TypeShape.Argument(parts) => visit(parts.input.resolution); visit(parts.output.resolution) @@ -323,6 +346,58 @@ class NewResolver: case TypeShape.Contextual(reference) => visit(reference.tpe.resolution) case _ => () visit(root) + // Check source cycles before substitution or Boolean normalization can + // hide an unguarded occurrence. Alias applications are transparent, but + // their arguments are followed only through unguarded formal uses: + // Id[Loop] is unguarded, whereas RecordWrapper[Loop] can be guarded. + val checked = mutable.Set.empty[TypeResolution] + var guarded = true + def checkGuarded(res: TypeResolution, path: Ls[TypeResolution]): Unit = + if path.contains(res) then + val cycle = res :: path.takeWhile(_ != res) + cycle.iterator.flatMap(node => node.currentShapes.collect: + case TypeShape.Alias(symbol, S(rhs)) => (node, symbol, rhs) + ).nextOption().foreach: (reference, symbol, rhs) => + guarded = false + rhs.fail(msg"Unguarded recursive type alias '${symbol.nme}'." -> reference.source.toLoc :: + (msg"Recursive references must pass through a record, tuple, function, or nominal type." -> symbol.toLoc) :: Nil) + else if checked.add(res) then + val next = res :: path + def follow(child: TypeResolution): Unit = checkGuarded(child, next) + res.currentShapes.foreach: + case TypeShape.Alias(_, rhs) => rhs.foreach(follow) + case TypeShape.Captured(base, _) => follow(base) + case TypeShape.Applied(base, args) => + follow(base) + typeApplicationTarget(base) match + case S(TypeShape.Alias(symbol, S(rhs))) => + val uses = typeDependencies(rhs, true) match + case R(binders) => binders + case L(_) => lastWords("Guardedness requires a completed source dependency graph") + symbol.defn.get.tparams.zip(args).foreach: (formal, argument) => + if uses(formal.sym) then follow(argument) + case S(TypeShape.Nominal(_)) => () + case _ => args.foreach(follow) + case TypeShape.Union(left, right) => follow(left); follow(right) + case TypeShape.Intersection(left, right) => follow(left); follow(right) + case TypeShape.Negation(base) => follow(base) + case TypeShape.Wildcard(input, output) => input.foreach(follow); output.foreach(follow) + case TypeShape.Polymorphic(params, _, body) => + params.foreach: param => + param.lower.foreach(follow) + param.upper.foreach(follow) + follow(body) + case TypeShape.Combined(formula) => formula.orderedAtoms.foreach(atom => follow(atom.resolution)) + case TypeShape.Argument(parts) => follow(parts.input.resolution); follow(parts.output.resolution) + case TypeShape.SelectedArgument(argument, _) => follow(argument.resolution) + case TypeShape.Contextual(reference) => follow(reference.tpe.resolution) + case _ => () + // Structural children start fresh paths, so every cycle is checked even + // when an enclosing interface guards the use of an invalid alias. + visited.foreach(checkGuarded(_, Nil)) + if !guarded then + rstate.regularTypes(root) = false + return false def dependencies(res: TypeResolution): Set[VarSymbol] = typeDependencies(res) match case R(binders) => binders case L(_) => lastWords("Regularity requires a completed source dependency graph") @@ -341,7 +416,7 @@ class NewResolver: output.toSet.flatMap(part => uses(declaredType(part, tpe), wrapped, next, aliases)) case TypeShape.Argument(parts) :: Nil => uses(parts.input, wrapped, next, aliases) ++ uses(parts.output, wrapped, next, aliases) case TypeShape.SelectedArgument(argument, _) :: Nil => uses(argument, wrapped, next, aliases) - case TypeShape.Applied(base, args) :: Nil => alias(base, Set.empty) match + case TypeShape.Applied(base, args) :: Nil => alias(base) match case S((symbol, rhs)) if !aliases(symbol) => // Constructor analysis ignores scope crossings, but still follows // the alias's substitution. The guard bounds body expansion; @@ -809,10 +884,9 @@ class NewResolver: listenTypeViews(declaredType(typeResolution(parent.cls), bindings)): ext => publish(NominalInstanceView(defn, bindings, S(ext))(S(tpe.resolution.source))(this)) case TypeShape.Alias(symbol, rhs) => - // Revisiting the same alias reference without reaching an outer - // nominal/function shape supplies no additional interface. Track + // Revisiting the same alias reference without reaching a structural + // shape supplies no additional interface. Track // references, not symbols: Id[Id[T]] has two distinct references. - // This also terminates aliases that recursively grow their arguments. if aliases(current.resolution) then publish(OpaqueTypeShape(tpe.resolution.source)) else rhs match case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams), current.positive), noTypeArguments, @@ -878,7 +952,7 @@ class NewResolver: case TypeShape.Intersection(left, right) => next(left); next(right) case TypeShape.Combined(formula) => formula.orderedAtoms.foreach: atom => follow(atom.instantiate(current.instances), noTypeArguments, aliases, publish) - case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)) + case TypeShape.Unit | TypeShape.Abstract | TypeShape.Negation(_) => publish(OpaqueTypeShape(tpe.resolution.source)) withCanonicalType(tpe)(follow(_, noTypeArguments, Set.empty, host.publish)) // A separate full signature also annotates the implementation's parameters. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 332df4a19c..c9ade21d80 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -212,6 +212,8 @@ final class NewResolverState private ( new Cache(inherited.map(_.lexicalTypeBinders), identity) val typeDependencies: Cache[TypeResolution, Set[VarSymbol]] = new Cache(inherited.map(_.typeDependencies), identity) + val unguardedTypeDependencies: Cache[TypeResolution, Set[VarSymbol]] = + new Cache(inherited.map(_.unguardedTypeDependencies), identity) val regularTypes: Cache[TypeResolution, Bool] = new Cache(inherited.map(_.regularTypes), identity) val combinedTypes: Cache[TypeFormula[DeclaredType], DeclaredType] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 9c55dd7202..2ca529cbfd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -12,6 +12,9 @@ enum TypeShape: case Alias(symbol: TypeAliasSymbol, rhs: Opt[TypeResolution]) case Union(left: TypeResolution, right: TypeResolution) case Intersection(left: TypeResolution, right: TypeResolution) + // Negated interfaces remain opaque, but their dependencies must participate + // in source validation, including the rejection of unguarded alias cycles. + case Negation(base: TypeResolution) // Substituted unions/intersections retain whole interpreted endpoints. Keeping // their lattice normal form prevents repeated Boolean substitutions from // nesting environments, without expanding bounds or changing endpoint marks. @@ -109,6 +112,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case Record(_, fields) => fields.foreach(_._2.validate(next)) case Union(left, right) => left.validate(next); right.validate(next) case Intersection(left, right) => left.validate(next); right.validate(next) + case Negation(base) => base.validate(next) case Combined(formula) => formula.orderedAtoms.foreach(_.resolution.validate(next)) case _ => () diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index c62761f133..50f2f247ee 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -214,29 +214,34 @@ fun unused(node: Link) = node.next.next class Box[T] type Loop[T] = Loop[Box[T]] fun unused(value: Loop[Int]) = value.field -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.216: fun unused(value: Loop[Int]) = value.field -//│ ║ ^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'field' of a value with unknown shape. -//│ ╟── This abstract type does not specify a member interface. +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.215: type Loop[T] = Loop[Box[T]] +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.215: type Loop[T] = Loop[Box[T]] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.215: type Loop[T] = Loop[Box[T]] +//│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'field' has no resolved target //│ ║ l.216: fun unused(value: Loop[Int]) = value.field -//│ ╙── ^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^ // A concrete instantiation cannot authorize calls through an opaque type parameter. :e private fun invoke[T](value: T) = value() invoke[() -> Int](() => 42) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.227: private fun invoke[T](value: T) = value() +//│ ║ l.232: private fun invoke[T](value: T) = value() //│ ║ ^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.227: private fun invoke[T](value: T) = value() +//│ ║ l.232: private fun invoke[T](value: T) = value() //│ ║ ^ //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.227: private fun invoke[T](value: T) = value() +//│ ║ l.232: private fun invoke[T](value: T) = value() //│ ║ ^ //│ ╟── Type parameter 'T' does not specify a member interface. -//│ ║ l.227: private fun invoke[T](value: T) = value() +//│ ║ l.232: private fun invoke[T](value: T) = value() //│ ╙── ^ //│ = 42 @@ -249,14 +254,14 @@ class Box(val item) fun read(box: Box) = box.Box#item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.249: fun read(box: Box) = box.Box#item.extra +//│ ║ l.254: fun read(box: Box) = box.Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.248: class Box(val item) +//│ ║ l.253: class Box(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.249: fun read(box: Box) = box.Box#item.extra +//│ ║ l.254: fun read(box: Box) = box.Box#item.extra //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -287,7 +292,7 @@ class Child extends Base with fun identity[T](x: T): T = x identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.288: identity[Base](new Child).extra +//│ ║ l.293: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -329,11 +334,11 @@ class Box[T](val value: T) (new Box[Base](new Child)).value.extra Box[Base](new Child).value.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.329: (new Box[Base](new Child)).value.extra +//│ ║ l.334: (new Box[Base](new Child)).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.330: Box[Base](new Child).value.extra +//│ ║ l.335: Box[Base](new Child).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -377,11 +382,11 @@ class Box with val item = new Item (new Box as Box).Box#item.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.378: (new Box as Box).Box#item.extra +//│ ║ l.383: (new Box as Box).Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.378: (new Box as Box).Box#item.extra +//│ ║ l.383: (new Box as Box).Box#item.extra //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -397,11 +402,11 @@ module Types with fun hidden(module types: Types) = types.Item.extra hidden(Types) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.397: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.402: fun hidden(module types: Types) = types.Item.extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.397: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.402: fun hidden(module types: Types) = types.Item.extra //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -421,7 +426,7 @@ class Child extends Base with fun apply[A](f: A -> A, x: A): A = f(x) apply[Base](arg => (arg.extra; arg), new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.422: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ l.427: apply[Base](arg => (arg.extra; arg), new Child) //│ ║ ^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -444,7 +449,7 @@ class Child extends Base with fun read(xs: [Base, Int]) = xs.0.extra read([new Child, 0]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.444: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ l.449: fun read(xs: [Base, Int]) = xs.0.extra //│ ║ ^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -465,10 +470,10 @@ let xs = mut [First(1)] set xs.0 = Second(2) [...xs].0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.466: [...xs].0.first +//│ ║ l.471: [...xs].0.first //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.465: set xs.0 = Second(2) +//│ ║ l.470: set xs.0 = Second(2) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -477,10 +482,10 @@ set xs.0 = Second(2) :ge [1, 2].2 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.478: [1, 2].2 +//│ ║ l.483: [1, 2].2 //│ ║ ^^^^^^^^ //│ ╟── Tuple literal does not contain member '2' -//│ ║ l.478: [1, 2].2 +//│ ║ l.483: [1, 2].2 //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -517,14 +522,14 @@ private fun identity(value) = value fun read(box: Annotated) = identity(box.item).extra read(Child(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.517: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.522: fun read(box: Annotated) = identity(box.item).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.513: class Parent(val item) +//│ ║ l.518: class Parent(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.517: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.522: fun read(box: Annotated) = identity(box.item).extra //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -536,16 +541,16 @@ class Box(val callback) fun invoke(box: Box) = box.callback() invoke(Box(() => 42)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.536: fun invoke(box: Box) = box.callback() +//│ ║ l.541: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.536: fun invoke(box: Box) = box.callback() +//│ ║ l.541: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^ //│ ╟── Constructor parameter 'callback' has no type annotation. -//│ ║ l.535: class Box(val callback) +//│ ║ l.540: class Box(val callback) //│ ║ ^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'callback'. -//│ ║ l.536: fun invoke(box: Box) = box.callback() +//│ ║ l.541: fun invoke(box: Box) = box.callback() //│ ╙── ^^^ //│ = 42 @@ -554,14 +559,14 @@ invoke(Box(() => 42)) class Box[X](val value: [X, X]) fun foo[A](x: Box[A]) = x.value.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.555: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.560: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.555: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.560: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.555: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.560: fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ // Concrete callers cannot hide the annotation responsible for an abstract tuple element. @@ -572,14 +577,14 @@ class Box[X](val value: [X, X]) private fun foo[A](x: Box[A]) = x.value.0.oops foo[Item](Box[Item]([new Item, new Item])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.572: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.577: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.572: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.577: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.572: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.577: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -589,28 +594,28 @@ class Box[X](val value: [X, X]) type Wrapped[T] = Box[T] fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.590: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.595: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.590: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.595: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.590: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.595: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╙── ^ // Tuple annotations do not need a nominal receiver to produce an abstract element. :ge fun foo[A](x: [A, A]) = x.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.604: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.609: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.604: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.609: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.604: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.609: fun foo[A](x: [A, A]) = x.0.oops //│ ╙── ^ // An unannotated field can be reached through a function result annotation and an alias. @@ -619,14 +624,14 @@ class Box(val value) type Wrapped = Box fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.620: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.625: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.618: class Box(val value) +//│ ║ l.623: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.620: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.625: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╙── ^^^^^^^ // TODO: ascription preserves receiver flow for the selected field's missing type. @@ -637,14 +642,14 @@ class Item with class Box(val value) (Box(new Item) as Box).value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.638: (Box(new Item) as Box).value.oops +//│ ║ l.643: (Box(new Item) as Box).value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.637: class Box(val value) +//│ ║ l.642: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.638: (Box(new Item) as Box).value.oops +//│ ║ l.643: (Box(new Item) as Box).value.oops //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -653,14 +658,14 @@ class Box(val value) :ge fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.654: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.659: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.654: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.659: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.654: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.659: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╙── ^ // A concrete annotation supplies the nested member interface without losing tuple precision. @@ -677,14 +682,14 @@ foo(Box[Item]([new Item, new Item])) class Pair[A, B](val value: [A, B]) fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.678: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.683: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.678: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.683: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^ //│ ╟── 'omittedSecond' is exposed by this compilation unit. -//│ ║ l.678: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.683: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // An unfilled omitted alias argument likewise cannot justify an arbitrary member. @@ -692,12 +697,12 @@ fun omittedSecond(x: Pair[Int]) = x.value.1.oops type HalfPair[T] = Pair[Int, T] fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.693: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.698: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.693: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.698: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^ //│ ╟── 'omittedAlias' is exposed by this compilation unit. -//│ ║ l.693: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.698: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls index 6477f04f9f..183a08b204 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls @@ -1,6 +1,6 @@ :. -// Control: an ordinary forwarding alias preserves the declared interface. +// An ordinary forwarding alias preserves the declared interface. :expect 1 class Item(val value: Int) type Choice[A] = A @@ -8,50 +8,352 @@ private fun read(x: Choice[Item]) = x.value read(Item(1)) //│ = 1 -// FIXME: This expected result assumes the least fixed point of the unguarded -// recursive union. The alias guard currently publishes an opaque alternative. -// Choosing fixed-point semantics or rejecting unguarded cycles needs a design decision. -:fixme -:expect 2 -class Item(val value: Int) +// Reject the definition itself, even when no value observes its interface. +:e +type Loop = Loop +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.13: type Loop = Loop +//│ ║ ^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.13: type Loop = Loop +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.13: type Loop = Loop +//│ ╙── ^^^^ + +:e type Choice[A] = A | Choice[A] -private fun read(x: Choice[Item]) = x.value -read(Item(2)) -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.18: private fun read(x: Choice[Item]) = x.value -//│ ║ ^^^^^^^ -//│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This abstract type does not specify a member interface. -//│ ║ l.18: private fun read(x: Choice[Item]) = x.value -//│ ╙── ^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' - -// An unguarded self-alias has no concrete interface. Dropping the recursion -// candidate must not make an arbitrary member operation silently acceptable. +//│ ╔══[COMPILATION ERROR] Resolution error in alternation +//│ ║ l.25: type Choice[A] = A | Choice[A] +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Choice'. +//│ ║ l.25: type Choice[A] = A | Choice[A] +//│ ║ ^^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.25: type Choice[A] = A | Choice[A] +//│ ╙── ^^^^^^ + +:e +type Choice[A] = A & Choice[A] +//│ ╔══[COMPILATION ERROR] Resolution error in composition +//│ ║ l.37: type Choice[A] = A & Choice[A] +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Choice'. +//│ ║ l.37: type Choice[A] = A & Choice[A] +//│ ║ ^^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.37: type Choice[A] = A & Choice[A] +//│ ╙── ^^^^^^ + +// Mutual recursion may cross several transparent aliases before cycling. +:e +type First = Second +type Second = Third +type Third = First +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.51: type Second = Third +//│ ║ ^^^^^ +//│ ╟── Unguarded recursive type alias 'Second'. +//│ ║ l.50: type First = Second +//│ ║ ^^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.51: type Second = Third +//│ ╙── ^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.52: type Third = First +//│ ║ ^^^^^ +//│ ╟── Unguarded recursive type alias 'Third'. +//│ ║ l.51: type Second = Third +//│ ║ ^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.52: type Third = First +//│ ╙── ^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.50: type First = Second +//│ ║ ^^^^^^ +//│ ╟── Unguarded recursive type alias 'First'. +//│ ║ l.52: type Third = First +//│ ║ ^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.50: type First = Second +//│ ╙── ^^^^^ + +// A guarded branch does not guard a separate recursive alternative. +:e +type Mixed = ({next: Mixed} | Mixed) +//│ ╔══[COMPILATION ERROR] Resolution error in alternation +//│ ║ l.83: type Mixed = ({next: Mixed} | Mixed) +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Mixed'. +//│ ║ l.83: type Mixed = ({next: Mixed} | Mixed) +//│ ║ ^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.83: type Mixed = ({next: Mixed} | Mixed) +//│ ╙── ^^^^^ + +// Boolean absorption must not conceal a written unguarded cycle. :e +type Hidden[A] = A | (A & Hidden[A]) +//│ ╔══[COMPILATION ERROR] Resolution error in alternation +//│ ║ l.96: type Hidden[A] = A | (A & Hidden[A]) +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Hidden'. +//│ ║ l.96: type Hidden[A] = A | (A & Hidden[A]) +//│ ║ ^^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.96: type Hidden[A] = A | (A & Hidden[A]) +//│ ╙── ^^^^^^ + +// An enclosing constructor does not make an invalid alias definition valid. +:e +type Wrapper = {value: Loop} type Loop = Loop -private fun read(x: Loop) = x.value -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.34: private fun read(x: Loop) = x.value -//│ ║ ^^^^^^^ -//│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This abstract type does not specify a member interface. -//│ ║ l.34: private fun read(x: Loop) = x.value -//│ ╙── ^^^^ - -// The equation X = Item | X has many solutions; Item is the least one, Any -// the greatest. Conversely, X = Item & X has Nothing as its least solution -// and Item as its greatest. Preserve the current diagnostic until the recursive -// alias contract specifies a fixed point or rejects these unguarded equations. +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.110: type Loop = Loop +//│ ║ ^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.110: type Loop = Loop +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.110: type Loop = Loop +//│ ╙── ^^^^ + +// Forwarding formals and captures remain transparent to the guardedness check. +:e +type Identity[A] = A +type Loop = Identity[Loop] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.124: type Loop = Identity[Loop] +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.124: type Loop = Identity[Loop] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.124: type Loop = Identity[Loop] +//│ ╙── ^^^^ + +:e +private fun local() = + type Identity[A] = A + type Loop = Identity[Loop] + () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.138: type Loop = Identity[Loop] +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.138: type Loop = Identity[Loop] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.138: type Loop = Identity[Loop] +//│ ╙── ^^^^ + +// A forward reference must resolve before formal-use summaries are finalized. +:e +type Loop = Forward[Loop] +type Forward[A] = Identity[A] +type Identity[A] = A +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.152: type Loop = Forward[Loop] +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.152: type Loop = Forward[Loop] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.152: type Loop = Forward[Loop] +//│ ╙── ^^^^ + +// Growing arguments do not guard a recursive alias application. :e +type Loop[A] = Loop[Array[A]] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.167: type Loop[A] = Loop[Array[A]] +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.167: type Loop[A] = Loop[Array[A]] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.167: type Loop[A] = Loop[Array[A]] +//│ ╙── ^^^^ + +// Guards may appear in a wrapper alias, including one declared later. +type Loop = Wrap[Loop] +type Wrap[A] = {next: A} +private fun walk(x: Loop) = x.next.next + +type Wrap[A] = [A] +type Loop = Wrap[Loop] +private fun walk(x: Loop) = x.0.0 + +type Wrap[A] = () -> A +type Loop = Wrap[Loop] +private fun walk(x: Loop) = x()() + +class Box[A](val value: A) +type Loop = Box[Loop] +private fun walk(x: Loop) = x.value.value + +// A mutual cycle with a structural guard is valid. +type Left = Right +type Right = {next: Left} +private fun walk(x: Left) = x.next.next + +// Reusing an identity alias in a finite expression is not recursive. +:expect 2 +class Item(val value: Int) +type Identity[A] = A +type Twice[A] = Identity[Identity[A]] +private fun read(x: Twice[Item]) = x.value +read(Item(2)) +//│ = 2 + +// An unused actual argument cannot introduce a cycle into the expanded alias. +:expect 3 class Item(val value: Int) -type Choice = Item & Choice -private fun read(x: Choice) = x.value -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.50: private fun read(x: Choice) = x.value -//│ ║ ^^^^^^^ -//│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This abstract type does not specify a member interface. -//│ ║ l.50: private fun read(x: Choice) = x.value -//│ ╙── ^^^^^^ +type Ignore[A] = Item +type Loop = Ignore[Loop] +private fun read(x: Loop) = x.value +read(Item(3)) +//│ = 3 + +// Forwarded parameter uses must be composed across multiple alias applications. +type Pair[A, B] = [B, A] +type Wrap[A] = Pair[Int, A] +type Loop = Wrap[Loop] +private fun walk(x: Loop) = x.0.0 + +// Unrelated guarded aliases in the same source graph remain usable. +:e +type Bad = Bad | Good +type Good = {next: Good} +private fun walk(x: Good) = x.next.next +//│ ╔══[COMPILATION ERROR] Resolution error in alternation +//│ ║ l.226: type Bad = Bad | Good +//│ ║ ^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Bad'. +//│ ║ l.226: type Bad = Bad | Good +//│ ║ ^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.226: type Bad = Bad | Good +//│ ╙── ^^^ + +// Quantification alone is not a structural guard. +:e +type Loop = [A] -> Loop +//│ ╔══[COMPILATION ERROR] Resolution error in universal quantification +//│ ║ l.241: type Loop = [A] -> Loop +//│ ║ ^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.241: type Loop = [A] -> Loop +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.241: type Loop = [A] -> Loop +//│ ╙── ^^^^ + +// Function domains, as well as results, guard recursive references. +type Loop = Loop -> Int +private fun apply(f: Loop, x: Loop): Int = f(x) + +// Guarded and unguarded uses of the same formal must both be considered. +:e +type MixedUse[A] = A | [A] +type Loop = MixedUse[Loop] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.259: type Loop = MixedUse[Loop] +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.259: type Loop = MixedUse[Loop] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.259: type Loop = MixedUse[Loop] +//│ ╙── ^^^^ + +// A forwarding alias may omit an unused parameter without introducing a guard. +:e +type First[A, B] = A +type Loop = First[Loop] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.273: type Loop = First[Loop] +//│ ║ ^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.273: type Loop = First[Loop] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.273: type Loop = First[Loop] +//│ ╙── ^^^^ + +// Negation is a Boolean operation, not a structural guard, even though the +// current resolver otherwise treats negated interfaces as opaque. +:e +type Loop = ~Loop +//│ ╔══[COMPILATION ERROR] Resolution error in negation type +//│ ║ l.287: type Loop = ~Loop +//│ ║ ^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.287: type Loop = ~Loop +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.287: type Loop = ~Loop +//│ ╙── ^^^^ + +:e +type Negated[A] = ~A +type Loop = Negated[Loop] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.300: type Loop = Negated[Loop] +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.300: type Loop = Negated[Loop] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.300: type Loop = Negated[Loop] +//│ ╙── ^^^^ + +// Keeping negation's source dependencies does not reject nonrecursive types. +type NotInt = ~Int +private fun unused(x: NotInt) = () + +// A constructor underneath negation still guards its recursive reference. +type Loop = ~[Loop] +private fun unused(x: Loop) = () + +// A bound on a quantified parameter is not a structural guard. +:e +type Loop = [A extends Loop] -> A +//│ ╔══[COMPILATION ERROR] Resolution error in universal quantification +//│ ║ l.321: type Loop = [A extends Loop] -> A +//│ ║ ^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.321: type Loop = [A extends Loop] -> A +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.321: type Loop = [A extends Loop] -> A +//│ ╙── ^^^^ + +// Wildcard bounds are transparent, including through a forwarding alias. +:e +type Identity[A] = A +type Loop = Identity[out Loop] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.335: type Loop = Identity[out Loop] +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.335: type Loop = Identity[out Loop] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.335: type Loop = Identity[out Loop] +//│ ╙── ^^^^ + +// Applying an abstract type parameter supplies no known structural guard. +:e +type Apply[F, A] = F[A] +type Identity[A] = A +type Loop = Apply[Identity, Loop] +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.350: type Loop = Apply[Identity, Loop] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.350: type Loop = Apply[Identity, Loop] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.350: type Loop = Apply[Identity, Loop] +//│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls index d8e690098f..9ee2019aa9 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeGraphTermination.mls @@ -161,9 +161,19 @@ class Second(val second: Int) type Alternating[A, B] = {value: A, next: Alternating[SecondOf[A, B], FirstOf[A, B]]} fun read(chain: Alternating[First, Second]): Int = chain.next.value.second -// Unproductive alias cycles must stop without eagerly unfolding growing arguments. +// An unguarded alias is rejected before unfolding its growing arguments. +:e type Loop[A] = Loop[Array[A]] private fun unused(value: Loop[Int]) = () +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.166: type Loop[A] = Loop[Array[A]] +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Loop'. +//│ ║ l.166: type Loop[A] = Loop[Array[A]] +//│ ║ ^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.166: type Loop[A] = Loop[Array[A]] +//│ ╙── ^^^^ // An unresolved alias body must keep its argument binding until the target is known. type Forward[A] = Identity[A] @@ -205,13 +215,13 @@ private fun walk[A](chain: Chain[A], n: Int) = type Chain[A] = {value: A, next: Chain[A | Array[A]]} private fun unused[A](chain: Chain[A]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.205: type Chain[A] = {value: A, next: Chain[A | Array[A]]} +//│ ║ l.215: type Chain[A] = {value: A, next: Chain[A | Array[A]]} //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Recursive type 'Chain' is not supported by the current regularity check. -//│ ║ l.205: type Chain[A] = {value: A, next: Chain[A | Array[A]]} +//│ ║ l.215: type Chain[A] = {value: A, next: Chain[A | Array[A]]} //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.205: type Chain[A] = {value: A, next: Chain[A | Array[A]]} +//│ ║ l.215: type Chain[A] = {value: A, next: Chain[A | Array[A]]} //│ ╙── ^ // Growth through a forwarding alias and a mutual cycle is still non-regular. @@ -221,13 +231,13 @@ type Left[A] = {value: A, next: Right[Wrap[A]]} type Right[B] = {value: B, next: Left[B]} private fun unused[A](chain: Left[A]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.220: type Left[A] = {value: A, next: Right[Wrap[A]]} +//│ ║ l.230: type Left[A] = {value: A, next: Right[Wrap[A]]} //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Recursive type 'Right' is not supported by the current regularity check. -//│ ║ l.220: type Left[A] = {value: A, next: Right[Wrap[A]]} +//│ ║ l.230: type Left[A] = {value: A, next: Right[Wrap[A]]} //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Type parameter 'B' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.221: type Right[B] = {value: B, next: Left[B]} +//│ ║ l.231: type Right[B] = {value: B, next: Left[B]} //│ ╙── ^ // Tuple and function constructors also count as growth. @@ -235,26 +245,26 @@ private fun unused[A](chain: Left[A]) = () type Chain[A] = {value: A, next: Chain[[A, A]]} private fun unused[A](chain: Chain[A]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.235: type Chain[A] = {value: A, next: Chain[[A, A]]} +//│ ║ l.245: type Chain[A] = {value: A, next: Chain[[A, A]]} //│ ║ ^^^^^^^^^^^^^ //│ ╟── Recursive type 'Chain' is not supported by the current regularity check. -//│ ║ l.235: type Chain[A] = {value: A, next: Chain[[A, A]]} +//│ ║ l.245: type Chain[A] = {value: A, next: Chain[[A, A]]} //│ ║ ^^^^^^^^^^^^^ //│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.235: type Chain[A] = {value: A, next: Chain[[A, A]]} +//│ ║ l.245: type Chain[A] = {value: A, next: Chain[[A, A]]} //│ ╙── ^ :e type Chain[A] = {value: A, next: Chain[Int -> A]} private fun unused[A](chain: Chain[A]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.248: type Chain[A] = {value: A, next: Chain[Int -> A]} +//│ ║ l.258: type Chain[A] = {value: A, next: Chain[Int -> A]} //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Recursive type 'Chain' is not supported by the current regularity check. -//│ ║ l.248: type Chain[A] = {value: A, next: Chain[Int -> A]} +//│ ║ l.258: type Chain[A] = {value: A, next: Chain[Int -> A]} //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.248: type Chain[A] = {value: A, next: Chain[Int -> A]} +//│ ║ l.258: type Chain[A] = {value: A, next: Chain[Int -> A]} //│ ╙── ^ // A forward alias must be resolved before deciding whether an argument grows. @@ -263,13 +273,13 @@ type Chain[A] = {value: A, next: Chain[Later[A]]} private fun unused[A](chain: Chain[A]) = () type Later[A] = Array[A] //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.262: type Chain[A] = {value: A, next: Chain[Later[A]]} +//│ ║ l.272: type Chain[A] = {value: A, next: Chain[Later[A]]} //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Recursive type 'Chain' is not supported by the current regularity check. -//│ ║ l.262: type Chain[A] = {value: A, next: Chain[Later[A]]} +//│ ║ l.272: type Chain[A] = {value: A, next: Chain[Later[A]]} //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.262: type Chain[A] = {value: A, next: Chain[Later[A]]} +//│ ║ l.272: type Chain[A] = {value: A, next: Chain[Later[A]]} //│ ╙── ^ // Boolean combinations can include an omitted argument, which remains a source hole. @@ -282,13 +292,13 @@ private fun walk[A](chain: Chain[A], n: Int) = type Chain[A] = {value: A, next: Chain[{item: A | Int}]} private fun unused[A](chain: Chain[A]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.282: type Chain[A] = {value: A, next: Chain[{item: A | Int}]} +//│ ║ l.292: type Chain[A] = {value: A, next: Chain[{item: A | Int}]} //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Recursive type 'Chain' is not supported by the current regularity check. -//│ ║ l.282: type Chain[A] = {value: A, next: Chain[{item: A | Int}]} +//│ ║ l.292: type Chain[A] = {value: A, next: Chain[{item: A | Int}]} //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.282: type Chain[A] = {value: A, next: Chain[{item: A | Int}]} +//│ ║ l.292: type Chain[A] = {value: A, next: Chain[{item: A | Int}]} //│ ╙── ^ // Captured forwarding aliases inside a Boolean argument must also saturate. @@ -307,32 +317,42 @@ type Wrap[A] = Array[A] type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} private fun unused[A](chain: Chain[A]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.307: type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} +//│ ║ l.317: type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Recursive type 'Chain' is not supported by the current regularity check. -//│ ║ l.307: type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} +//│ ║ l.317: type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.307: type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} +//│ ║ l.317: type Chain[A] = {value: A, next: Chain[Wrap[Wrap[A]]]} //│ ╙── ^ -// Boolean-only recursion also needs stable references, without a record boundary. +// Boolean-only recursion has no structural guard and is rejected. +:e type Choice[A] = A | Choice[A | Int] fun choose(value: Choice[Str]): Choice[Str] = value - -// Constructor growth is observable through a Boolean-only recursive alias too. +//│ ╔══[COMPILATION ERROR] Resolution error in alternation +//│ ║ l.331: type Choice[A] = A | Choice[A | Int] +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Choice'. +//│ ║ l.331: type Choice[A] = A | Choice[A | Int] +//│ ║ ^^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.331: type Choice[A] = A | Choice[A | Int] +//│ ╙── ^^^^^^ + +// Growing arguments do not guard Boolean-only alias recursion either. :e type Choice[A] = A | Choice[Array[A]] private fun unused(value: Choice[Int]) = () -//│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.325: type Choice[A] = A | Choice[Array[A]] -//│ ║ ^^^^^^^^^^^^^^^^ -//│ ╟── Recursive type 'Choice' is not supported by the current regularity check. -//│ ║ l.325: type Choice[A] = A | Choice[Array[A]] -//│ ║ ^^^^^^^^^^^^^^^^ -//│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.325: type Choice[A] = A | Choice[Array[A]] -//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in alternation +//│ ║ l.345: type Choice[A] = A | Choice[Array[A]] +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Unguarded recursive type alias 'Choice'. +//│ ║ l.345: type Choice[A] = A | Choice[Array[A]] +//│ ║ ^^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.345: type Choice[A] = A | Choice[Array[A]] +//│ ╙── ^^^^^^ // Selecting an absent output resets the observable argument to Any. // The current conservative check rejects this regular case; retain the example @@ -341,16 +361,16 @@ private fun unused(value: Choice[Int]) = () type Chain[A] = {value: A, next: Chain[in Array[A]]} private fun read[A](chain: Chain[A]) = chain.next.value //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.341: type Chain[A] = {value: A, next: Chain[in Array[A]]} +//│ ║ l.361: type Chain[A] = {value: A, next: Chain[in Array[A]]} //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Recursive type 'Chain' is not supported by the current regularity check. -//│ ║ l.341: type Chain[A] = {value: A, next: Chain[in Array[A]]} +//│ ║ l.361: type Chain[A] = {value: A, next: Chain[in Array[A]]} //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.341: type Chain[A] = {value: A, next: Chain[in Array[A]]} +//│ ║ l.361: type Chain[A] = {value: A, next: Chain[in Array[A]]} //│ ╙── ^ //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.342: private fun read[A](chain: Chain[A]) = chain.next.value +//│ ║ l.362: private fun read[A](chain: Chain[A]) = chain.next.value //│ ╙── ^^^^^^^^^^^^^^^^ // Absorption across recursive steps can depend on the relationship between arguments. @@ -361,16 +381,16 @@ type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} private fun walk[A, B](chain: Stable[A, B], n: Int) = if n > 0 then walk(chain.next, n - 1) else () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.360: type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} +//│ ║ l.380: type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Recursive type 'Stable' is not supported by the current regularity check. -//│ ║ l.360: type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} +//│ ║ l.380: type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Type parameter 'A' occurs under a type constructor on a recursive dependency cycle. -//│ ║ l.360: type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} +//│ ║ l.380: type Stable[A, B] = {value: A, next: Stable[A | (B & Array[A]), A]} //│ ╙── ^ //│ ╔══[COMPILATION ERROR] This selection of member 'next' has no resolved target -//│ ║ l.362: if n > 0 then walk(chain.next, n - 1) else () +//│ ║ l.382: if n > 0 then walk(chain.next, n - 1) else () //│ ╙── ^^^^^^^^^^ // A constructor-free output wildcard preserves A and reaches a finite graph. From 57d733ac14bb1e8f134f23c8ea7fef748997f1f7 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 16:12:47 +0800 Subject: [PATCH 130/228] Cover inherited nominal paths and array accumulator flow --- .../mlscript/newres/NominalMemberPaths.mls | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/newres/NominalMemberPaths.mls diff --git a/hkmc2/shared/src/test/mlscript/newres/NominalMemberPaths.mls b/hkmc2/shared/src/test/mlscript/newres/NominalMemberPaths.mls new file mode 100644 index 0000000000..8f4e1c6852 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/NominalMemberPaths.mls @@ -0,0 +1,65 @@ +:. + +// Inherited fields and generic methods retain the two callers' arguments after +// an annotated receiver leaves its allocation scope and crosses another call. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +class Parent[A](val item: A) with + fun map[B](f: A -> B): B = f(item) +class Child[A](item: A) extends Parent[A](item) +private fun make[A](value: A): Child[A] = new Child[A](value) +private fun forward(x) = x +[forward(make[First](First(1))).item.first, + forward(make[Second](Second(2))).map(x => x).second] +//│ = [1, 2] + +// The superclass and subclass are both local to a generic activation. Their +// inherited signatures must keep that activation when receivers escape. +:expect [3, 4] +class First(val first: Int) +class Second(val second: Int) +private fun make[A](value: A) = + class Parent(val item: A) with + fun map[B](f: A -> B): B = f(item) + class Child(item: A) extends Parent(item) + fun nested() = new Child(value) as Child + nested() +private fun forward(x) = x +[forward(make[First](First(3))).item.first, + forward(make[Second](Second(4))).map(x => x).second] +//│ = [3, 4] + +// Rest slicing through a generic body preserves the field substitutions even +// when both ends are removed in successive views. +:expect [5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun trim[A](value: A) = + let values = [0, value, 9] + if values is [_, ...tail] and tail is [...middle, _] then middle +[trim[First](First(5)).0.first, trim[Second](Second(6)).0.second] +//│ = [5, 6] + +// reduce feeds the callback result into the next callback invocation, even +// when it has a different shape from the initial accumulator. +:e +class Initial(val start: Int) +class Later(val result: Int) +[1, 2].reduce((acc, value, ...) => Later(acc.start + value), Initial(0)) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.49: [1, 2].reduce((acc, value, ...) => Later(acc.start + value), Initial(0)) +//│ ║ ^^^^^^^^^ +//│ ╟── Instance of class 'Later' does not contain member 'start' +//│ ║ l.49: [1, 2].reduce((acc, value, ...) => Later(acc.start + value), Initial(0)) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// The remaining callback arguments retain their declared positions and types. +:expect [7, 3] +class Total(val total: Int) +val empty: Array[Int] = mut [] +[empty.reduce((acc, value, ...) => Total(acc.total + value), Total(7)).total, + [4, 5].reduce((acc, value, index, array) => acc + index + array.length, -2)] +//│ = [7, 3] +//│ empty = [] From 8a8b37ebfe464303e3833867ade1e7c8b5349f0a Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 16:47:28 +0800 Subject: [PATCH 131/228] Clarify value views, specialization, and activation shape roles --- doc/new-resolution-design.md | 5 +- doc/new-resolution-type-value-flow.md | 113 ++++++++++++++++-- .../scala/hkmc2/semantics/NewResolver.scala | 19 ++- .../main/scala/hkmc2/semantics/Shape.scala | 48 ++++++-- 4 files changed, 160 insertions(+), 25 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 4ce2667c5e..08624dcba4 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -34,7 +34,10 @@ exported mapped arrays and imported generic functions and methods. [Instance types and parameter constraints](new-resolution-type-value-flow.md) specifies instance wrappers, input/output constraints, variance, canonical binder -instances, shared body views, and partial-signature inference. +instances, shared body views, and partial-signature inference. Its +[shape roles](new-resolution-type-value-flow.md#value-views-consumed-schemes-and-activation-events) +section distinguishes captured value substitutions, consumed callable schemes, +and inference-event activations, with examples and their representation invariants. [Regular structural types](new-resolution-regular-types.md) specifies normalization and the conservative recursion restriction. Do not duplicate those algorithms in member lookup or value-flow handling. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 2cd4485867..d96cd6e30c 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -170,14 +170,111 @@ to instance-symbol substitutions through deferred tuples, records, callbacks, an closures. They do not copy expanded argument candidates into a new body graph. Shape substitution is memoized so repeated observations reuse aggregate identities. -`ActivatedShape` records the body activation that produced an event. -`ContextualShape` carries the value's caller-side view. These substitutions must -remain separate: recursion can bind the same original parameter differently in -the callee body and in a captured caller value. Composition keeps a fixed base -state and flat maps, not a chain of activation environments. Source listeners -accept all activations; contextual observations accept compatible ones. -`NewResolverState.withInstances` shares consumer hosts, and `inGraph` preserves -the incoming activation when invoking an imported listener. +### Value views, consumed schemes, and activation events + +The similarly named forms in +[`Shape.scala`](../hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala) +answer different questions. A **scheme** is a callable's explicitly quantified +binders and their bounds. Consuming it chooses the canonical parameter instances +for an authoritative instantiation site; it does not imply that a term argument +list has been applied. + +| Form | Meaning | How it is consumed | +| --- | --- | --- | +| `ContextualShape(source, instances)` | Observe a shared value using these bindings for references inside it. This does not consume the value's own generic scheme. | Member/body observation uses the substitution; `callableParts` extracts it before application. | +| `SpecializedShape(declaration, arguments, instances)` | This declaration's scheme has already been consumed by explicit type application. Retain its supplied type references and chosen instance group. | `callableParts` reports a consumed scheme, so `appShape` reuses the group. | +| `ActivatedShape(value, instances)` | Deliver an inference event to operations running in this body activation. The enclosed value retains its own, independent substitution. | `listen` checks compatibility, unwraps the envelope, and invokes the receiver in that activation. | + +All three `instances` fields map original binders to `TypeParameterInstance` +symbols, but the first two describe the value, whereas the third describes the +receiving operation. `ContextualSymShape` and `ActivatedSymShape` provide the +corresponding roles while an overload remains a `SymShape`, before selection +produces a term shape. + +For a schematic nested definition: + +```text +outer[A](a: A) defines inner[B](b: B) = (a, b), and returns inner. +``` + +The returned `inner` captures an instance of `A`; its own `B` remains quantified. +A contextual view retains the captured `A` without selecting an instance for `B`. +Two later calls can instantiate `B` at their respective sites. Treating every +contextual view as specialized would prevent that instantiation. + +Conversely, in `let g = f[Int]`, the type application has already consumed `f`'s +scheme, even if `g` has not received term arguments. `SpecializedShape` preserves +that fact for an inferred declaration. Subsequent calls through `g` or its aliases +reuse the chosen group. Treating this as only a contextual view would let +`appShape` instantiate the declaration again. A complete annotated +`CallableTypeShape` records the same transition differently: `instantiateCallable` +substitutes its parameter/result references and clears `scheme`. Remaining curried +lists retain those references; a separately quantified result has its own scheme. + +The activation envelope is independent of both cases. Suppose a recursive +`f[A]` passes a value mentioning its caller's `A` into another call of `f`. +Write `A@p` and `A@q` for the instances chosen at two static sites (these are +explanatory names, not additional runtime identities). The incoming value must +still refer to `A@p`, while operations on the callee's shared body run with +`A -> A@q`. Schematically, delivery can therefore carry: + +```text +ActivatedShape( + ContextualShape(value, {A -> A@p}), + {A -> A@q}) +``` + +Replacing either map with the other would confuse the incoming value's type +references with the callee's parameters. Marks still distinguish lexical +activations when recursion revisits the same static site; these substitutions do +not replace, cancel, or otherwise change mark operations. + +`publishViewed` first applies the value substitution with `instantiateShape`, +then `publishActivated` records the current `NewResolverState.instances` on the +event. `listen` accepts an event when its map and the requested activation agree +on every shared key; absent keys do not conflict. A source listener with an empty +requested map therefore accepts all activations. On delivery, the receiver runs +with the combined compatible activation maps, while the enclosed value keeps its +own references. `NewResolverState.withInstances` shares consumer hosts, and +`inGraph` preserves the incoming activation when invoking an imported listener. +An `ActivatedShape` is thus an event envelope despite extending `TermShape`; +member lookup on the envelope is invalid and must follow unwrapping. + +### Related interfaces and representation invariants + +These roles must also be distinguished from interpreting an annotated instance: + +| Form | Role | +| --- | --- | +| `InstanceShape` | Preserve a `DeclaredType` reference in a value constraint, including its input and output uses. `listenInstanceViews` interprets it when an operation requests an interface. | +| `NominalInstanceView` | Expose a nominal type's declared members and inherited interface with their argument bindings. | +| `RecordTypeShape` | Expose a structural annotation's declared fields and their argument bindings. | +| `CallableTypeShape` | Expose a declared calling interface. `scheme` records whether quantified binders remain available for instantiation. | + +`ContextualShape` is the general deferred value view, not a mandatory wrapper +around every substituted shape. Tuples and records store their substitutions +directly; `InstanceShape` stores one inside its `DeclaredType`; declared interfaces +substitute their references. `instantiateShape` centralizes these cases and +memoizes their results. Applying another substitution composes flat maps rather +than nesting contextual wrappers. Entries already captured by a value take +precedence over a later observation's map. For an open `CallableTypeShape`, its +own quantified binders are excluded from capture substitution. + +The semantic requirements are independent value and activation substitutions, +and an explicit distinction between open and consumed schemes. They do not +require exactly these three subclasses: a common value-view representation could +carry scheme status, and an event envelope could live outside `TermShape`. The +current representation expresses those distinctions through the forms above. + +Keeping both maps does not itself introduce a chain of environments. Their keys +are original binders and their values are canonical instance symbols, not further +substitutions. `withInstances` reuses a fixed base state; `listen` removes the event +envelope before handing its value to the operation. For a fixed finite set of +binders and instantiation sites, there are finitely many such maps and map pairs. +This is a local bound, not a proof of termination of the entire type graph; see +[canonical references and termination obligations](#canonical-references-and-termination-obligations). + +### Checking witnesses and constraint edges Generic bodies also receive a checking activation containing `RigidTypeShape` witnesses. Those witnesses enforce generic opacity even in private/non-strict diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index beeb981386..46c19e2f61 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -785,8 +785,12 @@ class NewResolver: private def listenTermViews(term: Term)(listener: Listener)(using NewResolverState): Unit = listenTerm(term)(shape => listenInstanceViews(shape)(listener)) - /** Substitution changes a deferred observation, not its source graph. Compound - * children inherit the same flat map when they are subsequently observed. + /** Apply a value's binder substitution without copying the source inference + * graph, allocating parameter instances, or consuming a callable's scheme. + * Deferred children inherit the flat map; existing captures take precedence + * over incoming entries. Each representation stores the view in its own form + * (DeclaredType, aggregate fields, or ContextualShape), preserving outer marks. + * Receiver activation is separate and is attached by publishActivated. */ private[semantics] def instantiateShape(value: TermShape, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): TermShape = if instances.isEmpty then value else rstate.shapeViews.getOrElseUpdate((value, instances), { @@ -2156,6 +2160,10 @@ class NewResolver: rstate.activatedSymbols.getOrElseUpdate((symbol, rstate.instances), ActivatedSymShape(symbol, rstate.instances)) if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) + /** `instances` interprets references in the value; rstate.instances identifies + * the receiving body activation. Preserve both: a recursive argument may still + * refer to the caller's binder instance when delivered into a callee activation. + */ private def publishViewed(host: ShapeHost, source: Shape, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = val shape = source match @@ -2717,9 +2725,10 @@ class NewResolver: if aggregate.currentShapes.add(shape) then aggregate.notifyShapeListeners(shape) def listen(trm: Term, discardMarks: Bool = false)(receive: ShapeListener[Shape])(using NewResolverState): Unit = - // Source listeners process all activations. A deferred observation with a - // chosen substitution accepts only compatible activations of that source; - // the delivered value still keeps its independent caller-side references. + // A request and event are compatible when their activation maps agree on + // shared keys; an empty source request accepts all activations. Unwrap the + // event and supply that activation to the receiving operation, without + // replacing the enclosed value's independent caller-side substitution. val requestedInstances = rstate.instances def compatible(instances: Map[VarSymbol, TypeParameterInstance]): Bool = requestedInstances.forall((parameter, instance) => instances.get(parameter).forall(_ eq instance)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 62f1d3ced1..4a7f1ba89a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -366,11 +366,16 @@ class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: L def enter(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = ??? def enter(marks: Marks)(using TL): TermShape | NoShape = ??? -/** Deferred overload selection retains the value's binder substitution. */ +/** The symbolic counterpart of ContextualShape: retain the value's substitution + * until overload selection produces a term shape. This does not consume a scheme. + */ final class ContextualSymShape(val source: SymShape, val instances: Map[VarSymbol, TypeParameterInstance]) extends SymShape(source.sym, source.resSym, source.markss) -/** Symbolic source-flow events carry the same body activation as value events. */ +/** The symbolic counterpart of ActivatedShape. `listen` unwraps this event and + * invokes the receiver in `instances`; `source` can independently retain the + * value's substitution in a ContextualSymShape. + */ final class ActivatedSymShape(val source: SymShape, val instances: Map[VarSymbol, TypeParameterInstance]) extends SymShape(source.sym, source.resSym, source.markss) @@ -419,8 +424,15 @@ final case class InstanceShape(tpe: DeclaredType) extends NonAppTermShape: protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = lastWords("Instance member lookup requires interpreting its type first") -/** A deferred value observation shares its source shape and keeps a flat binder - * substitution. Members and function bodies are observed in that same view. +/** A deferred value view: interpret references in the shared `source` using + * `instances`, a flat map from original binders to canonical parameter instances. + * This does not consume the source's own generic scheme. For example, a returned + * inner[B] can capture outer's A while leaving B available for a later call. + * `instantiateShape` composes views instead of nesting them; already captured + * entries take precedence. Aggregates and declared types can store this view + * directly rather than using a ContextualShape wrapper. + * Unlike ActivatedShape.instances, this map belongs to the value, not its receiver. + * See doc/new-resolution-type-value-flow.md, "Value views, consumed schemes, and activation events". */ final case class ContextualShape(source: NonMarkedShape, instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: require(!source.isInstanceOf[ContextualShape], "Compose shape views rather than nesting them") @@ -431,8 +443,13 @@ final case class ContextualShape(source: NonMarkedShape, instances: Map[VarSymbo override def getMemberThrough(name: Str, receiver: Marks)(using NewResolverState): MemberLookup = source.getMemberThrough(name, receiver).instantiate(instances) -/** A type application has consumed the declaration's scheme. Subsequent term - * applications retain this binder group and the supplied caller references. +/** Explicit type application, such as f[Int], has consumed this declaration's + * scheme before any term argument list need be applied. `arguments` retains the + * supplied caller type references; `instances` retains the chosen binder group + * and lexical captures. `callableParts` tells `appShape` to reuse that group even + * through stored aliases, rather than instantiate the declaration again. + * A ContextualShape alone does not record scheme consumption. Complete annotated + * CallableTypeShape views record consumption by substituting and clearing `scheme`. */ final case class SpecializedShape(declaration: DefnShape, arguments: Ls[DeclaredType], instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: @@ -440,9 +457,15 @@ final case class SpecializedShape(declaration: DefnShape, arguments: Ls[Declared def toLoc: Opt[Loc] = declaration.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = declaration.getMember(name).instantiate(instances) -/** A source-flow event identifies the activation in which its consumer executes. - * The value separately retains the caller's type references; these maps cannot - * be merged when a recursive callee rebinds the same source parameter. +/** An inference-event envelope, not a value interface. `publishActivated` saves + * the current body's original-binder-to-instance map; `listen` checks agreement + * with the requested activation on shared keys, unwraps `value`, and invokes the + * receiver in the combined compatible activation. An empty request accepts all. + * The enclosed value separately retains its own substitution: in recursive f[A], + * it can refer to the caller's A@p while the receiving body uses A@q. Merging those + * maps would rebind the value or execute the operation in the wrong activation. + * Despite extending TermShape, this envelope must be unwrapped before lookup. + * Neither map changes the ordinary mark algebra used for lexical scope crossings. */ final case class ActivatedShape(value: TermShape, instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: def describe: Str = value.describe @@ -479,8 +502,11 @@ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool, re /** Calls through annotations expose only the declared result. Argument shapes * constrain type parameters in the interface; they do not recover its implementation. - * Named generic declarations retain their parameter symbols so explicit type - * applications constrain the same parameters as inferred arguments. + * `scheme` retains binders that are still available for instantiation; capture + * substitution leaves those binders alone. `instantiateCallable` consumes the + * scheme by substituting its references and clearing it, so stored values and + * remaining curried lists reuse the chosen instances. This is the annotated + * counterpart of SpecializedShape, not an inference-event activation. */ final case class CallableTypeShape(source: Term, paramLists: Ls[DeclaredParams], result: Opt[DeclaredType], scheme: Opt[TypeScheme], supplied: Opt[Ls[DeclaredType]], From 52317ff4eceeaf4bb20602f72b9cf454a4c5e5e5 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 16:56:29 +0800 Subject: [PATCH 132/228] Separate activation events from semantic value shapes --- doc/new-resolution-type-value-flow.md | 30 ++++++++++------- .../hkmc2/semantics/InterfaceExposure.scala | 1 - .../scala/hkmc2/semantics/NewResolver.scala | 22 +++++-------- .../hkmc2/semantics/NewResolverState.scala | 2 -- .../main/scala/hkmc2/semantics/Shape.scala | 32 +++++++++---------- .../src/main/scala/hkmc2/semantics/Term.scala | 2 +- .../scala/hkmc2/semantics/TypeShape.scala | 2 +- 7 files changed, 44 insertions(+), 47 deletions(-) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index d96cd6e30c..a8a3f51a53 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -183,13 +183,13 @@ list has been applied. | --- | --- | --- | | `ContextualShape(source, instances)` | Observe a shared value using these bindings for references inside it. This does not consume the value's own generic scheme. | Member/body observation uses the substitution; `callableParts` extracts it before application. | | `SpecializedShape(declaration, arguments, instances)` | This declaration's scheme has already been consumed by explicit type application. Retain its supplied type references and chosen instance group. | `callableParts` reports a consumed scheme, so `appShape` reuses the group. | -| `ActivatedShape(value, instances)` | Deliver an inference event to operations running in this body activation. The enclosed value retains its own, independent substitution. | `listen` checks compatibility, unwraps the envelope, and invokes the receiver in that activation. | +| `ActivatedShapeEvent(value, instances)` | Deliver an inference event to operations running in this body activation. The enclosed value retains its own, independent substitution. | `listen` checks compatibility, unwraps the envelope, and invokes the receiver in that activation. | All three `instances` fields map original binders to `TypeParameterInstance` symbols, but the first two describe the value, whereas the third describes the -receiving operation. `ContextualSymShape` and `ActivatedSymShape` provide the -corresponding roles while an overload remains a `SymShape`, before selection -produces a term shape. +receiving operation. `ContextualSymShape` retains a value's substitution while +an overload remains a `SymShape`, before selection produces a term shape. +`ActivatedShapeEvent` accepts either a term shape or a symbolic shape as its payload. For a schematic nested definition: @@ -219,7 +219,7 @@ still refer to `A@p`, while operations on the callee's shared body run with `A -> A@q`. Schematically, delivery can therefore carry: ```text -ActivatedShape( +ActivatedShapeEvent( ContextualShape(value, {A -> A@p}), {A -> A@q}) ``` @@ -237,8 +237,17 @@ requested map therefore accepts all activations. On delivery, the receiver runs with the combined compatible activation maps, while the enclosed value keeps its own references. `NewResolverState.withInstances` shares consumer hosts, and `inGraph` preserves the incoming activation when invoking an imported listener. -An `ActivatedShape` is thus an event envelope despite extending `TermShape`; -member lookup on the envelope is invalid and must follow unwrapping. +Publishers store `ShapeEvent`, a shared base with two alternatives: an ordinary +`Shape` or an `ActivatedShapeEvent`. The envelope is not a `Shape` or `TermShape`, +so it cannot enter member lookup, application, or mark transport. Its payload is +a `Shape`, preventing nested event envelopes. Semantic listeners receive shapes +only after dispatch. Ordinary shapes remain unwrapped when no activation is +attached; dispatch contextualizes those values using the observing state. + +Event equality includes both the payload and activation. Repeated publication of +one event is deduplicated, while the same shape in distinct activations remains +separate. Publisher replay and imported-host copying retain the complete events; +they do not replace saved activations with the state active during replay. ### Related interfaces and representation invariants @@ -261,10 +270,9 @@ precedence over a later observation's map. For an open `CallableTypeShape`, its own quantified binders are excluded from capture substitution. The semantic requirements are independent value and activation substitutions, -and an explicit distinction between open and consumed schemes. They do not -require exactly these three subclasses: a common value-view representation could -carry scheme status, and an event envelope could live outside `TermShape`. The -current representation expresses those distinctions through the forms above. +and an explicit distinction between open and consumed schemes. The first two +value forms express scheme status separately; the event envelope belongs to the +publisher protocol and shares no value operations with them. Keeping both maps does not itself introduce a chain of environments. Their keys are original binders and their values are canonical instance symbols, not further diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index d8ea41d8a3..a74099cc16 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -121,7 +121,6 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) listener => resolver.listenTerm(value)(listener)(using rstate.withInstances(substitution))): shape => emit(resolver.instantiateShape(shape, substitution).exit(context), path) head match - case _: ActivatedShape => lastWords("Exposure must observe values after activation dispatch") case contextual: ContextualShape => contextual.source.exit(marks) match case value: TermShape => val substitution = instances ++ contextual.instances diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 46c19e2f61..dc43484d75 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1114,17 +1114,17 @@ class NewResolver: private def listenTypeArgument(symbol: VarSymbol)(listener: Listener)(using NewResolverState): Unit = listenTypeArgument(symbol.inferenceHost)(listener) - private def listenTypeArgument(host: Publisher.Data[Shape])(listener: Listener)(using NewResolverState): Unit = + private def listenTypeArgument(host: Publisher.Data[ShapeEvent])(listener: Listener)(using NewResolverState): Unit = host.subscribe: case value: TermShape => listener(value) - case _ => softAssert(false, "A type parameter received a symbolic overload set") + case _ => softAssert(false, "A type parameter's bounds must contain values without activation envelopes") private[semantics] def publishParameter(symbol: VarSymbol, shape: TermShape | NoShape)(using NewResolverState): Unit = shape match case value: TermShape if !symbol.decl.exists(_.isInstanceOf[TyParam]) => publishActivated(symbol, value) case _ => publishParameter(symbol.inferenceHost, shape) - private[semantics] def publishParameter(host: Publisher.Data[Shape], shape: TermShape | NoShape)(using NewResolverState): Unit = shape match + private[semantics] def publishParameter(host: Publisher.Data[ShapeEvent], shape: TermShape | NoShape)(using NewResolverState): Unit = shape match case value: TermShape => host.publish(value) case _ => () @@ -2154,10 +2154,7 @@ class NewResolver: else register private def publishActivated(host: ShapeHost, source: Shape)(using NewResolverState): Unit = - val shape = if rstate.instances.isEmpty then source else source match - case value: TermShape => ActivatedShape(value, rstate.instances) - case symbol: SymShape => - rstate.activatedSymbols.getOrElseUpdate((symbol, rstate.instances), ActivatedSymShape(symbol, rstate.instances)) + val shape = if rstate.instances.isEmpty then source else ActivatedShapeEvent(source, rstate.instances) if host.currentShapes.add(shape) then host.notifyShapeListeners(shape) /** `instances` interprets references in the value; rstate.instances identifies @@ -2713,7 +2710,7 @@ class NewResolver: * subscriptions while the node is waiting for a forward definition. Deliver * cached shapes to each listener, which is already registered for future shapes. */ - private def listenAggregate(aggregate: Tup | Rcd, listener: ShapeListener[Shape]) + private def listenAggregate(aggregate: Tup | Rcd, listener: ShapeListener[ShapeEvent]) (start: (Listener) => Unit)(using NewResolverState): Unit = val first = aggregateProducers.add(new Identity(aggregate)) // An imported definition can already have shapes computed by its own elaborator. @@ -2732,14 +2729,11 @@ class NewResolver: val requestedInstances = rstate.instances def compatible(instances: Map[VarSymbol, TypeParameterInstance]): Bool = requestedInstances.forall((parameter, instance) => instances.get(parameter).forall(_ eq instance)) - val listener: ShapeListener[Shape] = shape => shape match - case ActivatedShape(value, instances) => + val listener: ShapeListener[ShapeEvent] = event => event match + case ActivatedShapeEvent(value, instances) => if compatible(instances) then receive(value)(using rstate.withInstances(requestedInstances ++ instances)) - case activated: ActivatedSymShape => - if compatible(activated.instances) then - receive(activated.source)(using rstate.withInstances(requestedInstances ++ activated.instances)) case value: TermShape => receive(instantiateShape(value, rstate.instances)) - case _ => receive(shape) + case symbol: SymShape => receive(symbol) log(s"listen: trm = ${trm.showDbg}") trm.addShapeListener(listener) trm match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 2ceadc9370..c927b6f6c8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -236,8 +236,6 @@ final class NewResolverState private ( new Cache(inherited.map(_.contextualSymbols), identity) val shapeViews: Cache[(TermShape, Map[VarSymbol, TypeParameterInstance]), TermShape] = new Cache(inherited.map(_.shapeViews), identity) - val activatedSymbols: Cache[(SymShape, Map[VarSymbol, TypeParameterInstance]), ActivatedSymShape] = - new Cache(inherited.map(_.activatedSymbols), identity) val inferredInstantiations: Seen[(AnyDefinitionSymbol, FlowSymbol, Map[VarSymbol, TypeParameterInstance], Ls[Marks])] = new Seen(inherited.map(_.inferredInstantiations)) // A scheme is owned by its source definition, or by the original interpretation diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 4a7f1ba89a..dc7cf42ee8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -9,7 +9,13 @@ import hkmc2.document.Document.* import scala.collection.mutable -sealed trait Shape extends ShapeLike: +/** Publisher payloads distinguish ordinary shapes from activation-tagged events. + * Only Shape supports value operations; an event must be dispatched first. + * Plain shapes need no allocation to participate in activation-independent flow. + */ +sealed trait ShapeEvent + +sealed trait Shape extends ShapeEvent, ShapeLike: def describe: Str /** Origin of the value or symbol described by this shape, independently of its use site. */ def toLoc: Opt[Loc] @@ -33,7 +39,6 @@ sealed trait Shape extends ShapeLike: case view: ContextualShape => s"ContextualShape(${view.source.shwDbg})" case specialized: SpecializedShape => s"SpecializedShape(${specialized.declaration.shwDbg})" case rigid: RigidTypeShape => s"RigidTypeShape(${rigid.parameter.showDbg})" - case flow: ActivatedShape => s"ActivatedShape(${flow.value.shwDbg})" case bs: BaseShape => s"BaseShape(${bs.defn.sym.showDbg})" case es: ErrShape => es.describe @@ -372,12 +377,6 @@ class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: L final class ContextualSymShape(val source: SymShape, val instances: Map[VarSymbol, TypeParameterInstance]) extends SymShape(source.sym, source.resSym, source.markss) -/** The symbolic counterpart of ActivatedShape. `listen` unwraps this event and - * invokes the receiver in `instances`; `source` can independently retain the - * value's substitution in a ContextualSymShape. - */ -final class ActivatedSymShape(val source: SymShape, val instances: Map[VarSymbol, TypeParameterInstance]) -extends SymShape(source.sym, source.resSym, source.markss) /** Keep a declared receiver's boundary while consumers choose between the term, * type, and constructor interpretations of the same overload set. @@ -431,7 +430,7 @@ final case class InstanceShape(tpe: DeclaredType) extends NonAppTermShape: * `instantiateShape` composes views instead of nesting them; already captured * entries take precedence. Aggregates and declared types can store this view * directly rather than using a ContextualShape wrapper. - * Unlike ActivatedShape.instances, this map belongs to the value, not its receiver. + * Unlike ActivatedShapeEvent.instances, this map belongs to the value, not its receiver. * See doc/new-resolution-type-value-flow.md, "Value views, consumed schemes, and activation events". */ final case class ContextualShape(source: NonMarkedShape, instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: @@ -464,14 +463,13 @@ final case class SpecializedShape(declaration: DefnShape, arguments: Ls[Declared * The enclosed value separately retains its own substitution: in recursive f[A], * it can refer to the caller's A@p while the receiving body uses A@q. Merging those * maps would rebind the value or execute the operation in the wrong activation. - * Despite extending TermShape, this envelope must be unwrapped before lookup. + * The payload may be a value or an unresolved symbol; it cannot itself be an + * event. This envelope supports neither value operations nor mark transport. + * Equality includes the activation so distinct deliveries are not deduplicated. * Neither map changes the ordinary mark algebra used for lexical scope crossings. */ -final case class ActivatedShape(value: TermShape, instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: - def describe: Str = value.describe - def toLoc: Opt[Loc] = value.toLoc - protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - lastWords("Activation events must be unpacked before observing their values") +final case class ActivatedShapeEvent(value: Shape, instances: Map[VarSymbol, TypeParameterInstance]) extends ShapeEvent: + require(instances.nonEmpty, "Publish an activation-independent shape without an envelope") /** A nominal annotation exposes only declarations, including inherited declarations. * In particular, selecting an unannotated field does not inspect its initializer. @@ -808,6 +806,6 @@ sealed trait LitShape extends NonAppTermShape: protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Missing // TODO: methods on literals, e.g. string methods -type ShapePublisher = Publisher[Shape] -type ShapeHost = Host[Shape] +type ShapePublisher = Publisher[ShapeEvent] +type ShapeHost = Host[ShapeEvent] diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 38a6371c90..414138de4e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -144,7 +144,7 @@ sealed trait ResolvableImpl extends ShapeHost, PossiblyErroneous: // private[semantics] val shapes: MutSet[Shape] = MutSet.empty - def getShapes: Ls[Shape] = shapes.toList + def getShapes: Ls[ShapeEvent] = shapes.toList /** * The expanded form of the term, if it exists. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 2ca529cbfd..d524a62530 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -35,7 +35,7 @@ enum TypeShape: case Contextual(reference: ContextualType) // The same third-party symbol can have different inference in two exporters. // Retain its originating host so importing a result needs no whole-state copy. - case Parameter(symbol: VarSymbol, host: Publisher.Data[Shape]) + case Parameter(symbol: VarSymbol, host: Publisher.Data[ShapeEvent]) // An omitted argument owns one source inference node. It is not a quantified // binder and is never instantiated at a call site; marks distinguish its flows. case Hole(host: Publisher.Data[TermShape]) From dac6dca5a19ac0a6742c02067ba032fa26d0e3a2 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 25 Sep 2026 17:02:08 +0800 Subject: [PATCH 133/228] Small debloating --- hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 55ee9598b8..c4036cf58f 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -510,16 +510,6 @@ class TypeRelationTest extends AnyFunSuite: assert(a.currentShapes.toSet == Set(InstanceShape(bt))) assert(b.currentShapes.toSet == Set(InstanceShape(at), InstanceShape(concrete))) - test("repeated deferred views reuse source identities for synthesized interfaces"): - val h = new Harness - import h.given - val (a, at) = h.parameter("A") - val substitution = h.state.instantiateTypeParameters(at.resolution, FlowSymbol.app(), List(a)) - val tuple = TupleShape(Term.UnitVal(), TupleShape.TypedField(at, Nil) :: Nil)(h.resolver) - val view = h.resolver.instantiateShape(tuple, substitution) - (1 to 1000).foreach: _ => - assert(h.resolver.instantiateShape(tuple, substitution) eq view) - test("importers extend a cyclic relation without mutating the exporter or one another"): val h = new Harness import h.given From 4937cf2c1bf0267d77bc8d12ccced9d38982506f Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 17:45:38 +0800 Subject: [PATCH 134/228] Record inherited-field dead-parameter elimination regression --- .../dead-param-elim/inherited-field.mls | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/dead-param-elim/inherited-field.mls diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/inherited-field.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/inherited-field.mls new file mode 100644 index 0000000000..32e92b769c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/inherited-field.mls @@ -0,0 +1,29 @@ +:.. +:js + +// FIXME: The producer is Child but the selected field belongs to Parent. +// FlowConstraintSolver drops that flow instead of keeping the constructor input +// live, so dead-parameter elimination replaces value with undefined. +// Disable inlining to keep read available for the dead-parameter analysis. +:fixme +:deadParamElim debug +:noInline +:expect 21 +class Parent(val value) +class Child(value) extends Parent(value) +private fun read(value) = (new Child(value)).Parent#value +read(21) +//│ dead-param-elim > >>> dead-param-elim results >>> +//│ dead-param-elim > prodfun read#0 @ -> eliminable: {0} +//│ dead-param-elim > <<< dead-param-elim results <<< +//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. +//│ ═══[RUNTIME ERROR] Expected: '21', got: 'undefined' + +:deadParamElim off +:noInline +:expect 21 +class Parent(val value) +class Child(value) extends Parent(value) +private fun read(value) = (new Child(value)).Parent#value +read(21) +//│ = 21 From c4246e47ebfce1e13a280b309f9b65fe25176b9c Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 17:45:56 +0800 Subject: [PATCH 135/228] Encode resolver shape and substitution invariants --- doc/new-resolution-type-value-flow.md | 37 ++- .../scala/hkmc2/TypeInstantiationTest.scala | 4 +- .../test/scala/hkmc2/TypeRelationTest.scala | 8 +- .../shared/src/main/scala/hkmc2/package.scala | 3 + .../hkmc2/semantics/InterfaceExposure.scala | 12 +- .../scala/hkmc2/semantics/NewResolver.scala | 217 +++++++++--------- .../hkmc2/semantics/NewResolverState.scala | 32 +-- .../main/scala/hkmc2/semantics/Shape.scala | 102 +++++--- .../scala/hkmc2/semantics/TypeShape.scala | 12 +- .../hkmc2/semantics/TypeSubstitution.scala | 21 ++ .../src/test/mlscript/newres/ShapeViews.mls | 156 +++++++++++++ 11 files changed, 415 insertions(+), 189 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/TypeSubstitution.scala create mode 100644 hkmc2/shared/src/test/mlscript/newres/ShapeViews.mls diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index a8a3f51a53..65a143ac25 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -181,8 +181,8 @@ list has been applied. | Form | Meaning | How it is consumed | | --- | --- | --- | -| `ContextualShape(source, instances)` | Observe a shared value using these bindings for references inside it. This does not consume the value's own generic scheme. | Member/body observation uses the substitution; `callableParts` extracts it before application. | -| `SpecializedShape(declaration, arguments, instances)` | This declaration's scheme has already been consumed by explicit type application. Retain its supplied type references and chosen instance group. | `callableParts` reports a consumed scheme, so `appShape` reuses the group. | +| `ContextualShape(source, instances)` | Observe a shared value using these bindings for references inside it. This does not consume the value's own generic scheme. | Member/body observation uses the substitution; `shapeParts` extracts it before application. | +| `SpecializedShape(declaration, arguments, instances)` | This declaration's scheme has already been consumed by explicit type application. Retain its supplied type references and chosen instance group. | `shapeParts` retains the supplied arguments, so `appShape` reuses the consumed group. | | `ActivatedShapeEvent(value, instances)` | Deliver an inference event to operations running in this body activation. The enclosed value retains its own, independent substitution. | `listen` checks compatibility, unwraps the envelope, and invokes the receiver in that activation. | All three `instances` fields map original binders to `TypeParameterInstance` @@ -251,6 +251,39 @@ they do not replace saved activations with the state active during replay. ### Related interfaces and representation invariants +The view normal form is enforced by the Scala types. `CoreShape` excludes +`ContextualShape` and `SpecializedShape`, and `MarkedShape[T]` retains its core's +type parameter. `AppShape.receiver` has type +`CoreTermShape = CoreShape | MarkedShape[CoreShape]`: an application cannot contain +a contextual or specialized receiver, even beneath marks or earlier applications. +`ContextualShape.source` accepts only the shapes that defer substitution through +a view (`AppShape`, `NewShape`, `DefnShape`, `BaseShape`, and `IntroShape`). It +cannot contain another view, a marked shape, or a shape that stores substitutions +directly. `SpecializedShape.declaration` is a `DefnShape`. + +The symbolic counterpart follows the same rule: `ContextualSymShape.source` is +a `CoreSymShape` (plain or declared), so it cannot wrap another contextual symbol. +`CallableTypeShape.paramLists` is a `NELs[DeclaredParams]`: every callable view +has a next argument list, even when that list itself accepts zero arguments. +Consuming the last list exposes the result instead of constructing an empty +callable view. Mapping parameter types preserves nonemptiness with `ne_map`. + +Value captures and body activations use the opaque `TypeSubstitution`, whose +underlying immutable map remains available for reads. Its constructor derives +each key from the instance's original binder. `withOverrides` combines two valid +substitutions with the right operand taking precedence, and `without` removes +shadowed binders. Both preserve the substitution type; arbitrary map updates do +not. This rules out mismatched binder/instance pairs without runtime validation +at every `DeclaredType` construction. + +Consequently, `shapeParts` can decode one outer marking and one view with an +exhaustive match. It returns a `CoreTermShape`, its captured instances, and any +consumed type arguments. It preserves the application chain: peeling applications +would forget consumed term lists and could mistake a constructed object for a +constructor. Application, callback checking, constructor lookup, and constructor +patterns share this decoder. `ShapeViews.mls` exercises nested captures, +specialized curried values, callback constraints, and constructor-pattern controls. + These roles must also be distinguished from interpreting an annotated instance: | Form | Role | diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala index a8834ff4bd..a8b6e8cb83 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala @@ -97,7 +97,7 @@ class TypeInstantiationTest extends AnyFunSuite: (1 to 1000).foreach: _ => assert(left.withInstances(second) eq right) assert(right.withInstances(first) eq left) - assert(left.withInstances(Map.empty) eq source) + assert(left.withInstances(TypeSubstitution.empty) eq source) assert(left.inGraph(right) eq left) assert(local.inferenceHost(using left) eq local.inferenceHost(using right)) assert(source.allocatedTypeInstanceCount == 2) @@ -116,7 +116,7 @@ class TypeInstantiationTest extends AnyFunSuite: val contextual = consumer.withInstances(substitution) val imported = contextual.inGraph(source.withInstances(sourceSubstitution)) assert(imported.instances == substitution) - assert(imported.withInstances(Map.empty) eq consumer.inGraph(source)) + assert(imported.withInstances(TypeSubstitution.empty) eq consumer.inGraph(source)) (1 to 1000).foreach: _ => assert(contextual.inGraph(source) eq imported) assert(imported.inGraph(source) eq imported) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index c4036cf58f..fbcaf872a3 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -22,7 +22,7 @@ class TypeRelationTest extends AnyFunSuite: def tpe(shape: TypeShape): DeclaredType = val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) resolution.publish(shape) - DeclaredType(resolution, Map.empty, Map.empty, true) + DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) def parameter(name: String): (VarSymbol, DeclaredType) = val symbol = VarSymbol(Tree.Ident(name)) (symbol, tpe(TypeShape.Parameter(symbol, symbol.inferenceHost))) @@ -218,7 +218,7 @@ class TypeRelationTest extends AnyFunSuite: val nextField = RcdField.signature(Term.Lit(Tree.StrLit("next")), next) val source: Term.Rcd = Term.Rcd(false, List(valueField, nextField)) resolution.publish(TypeShape.Record(source, List(valueField -> element.resolution, nextField -> resolution))) - DeclaredType(resolution, Map.empty, Map.empty, true) + DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) val left = ContextualType(recursiveRecord(at), Nil) val right = ContextualType(recursiveRecord(bt), Nil) val first = IntroShape(Term.UnitVal(), N) @@ -275,7 +275,7 @@ class TypeRelationTest extends AnyFunSuite: val (input, inputType) = h.parameter("Input") val (output, outputType) = h.parameter("Output") val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) - val argument = DeclaredType(resolution, Map.empty, Map.empty, true) + val argument = DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) val negative = h.resolver.selectArgument(argument, false) val positive = h.resolver.selectArgument(argument, true) val neg = ContextualType(negative, Nil) @@ -310,7 +310,7 @@ class TypeRelationTest extends AnyFunSuite: val h = new Harness import h.given val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) - val argument = DeclaredType(resolution, Map.empty, Map.empty, true) + val argument = DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) val selected = h.resolver.selectArgument(argument, true) val reference = ContextualType(selected, Nil) val seen = h.observe(reference) diff --git a/hkmc2/shared/src/main/scala/hkmc2/package.scala b/hkmc2/shared/src/main/scala/hkmc2/package.scala index 21197bd4a6..311944c6a7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/package.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/package.scala @@ -20,6 +20,9 @@ extension [A](xs: Ls[A]) case Nil => N case xs: NELs[A] => S(xs) +extension [A](xs: NELs[A]) + def ne_map[B](f: A => B): NELs[B] = f(xs.head) ne_:: xs.tail.map(f) + // * Valid identifiers for the members of module and class-like definitions // * Importantly, these are the same as valid JavaScript identifiers, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index a74099cc16..d707b3a49e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -113,21 +113,21 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) private def value(shape: TermShape, path: Path)(using NewResolverState): Unit = valueIn(shape, path, rstate.instances) - private def valueIn(shape: TermShape, path: Path, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + private def valueIn(shape: TermShape, path: Path, instances: TypeSubstitution)(using NewResolverState): Unit = val (head, marks) = shape.applicationHead def contextualTerm(value: Term, context: Ls[Marks], path: Path, - substitution: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + substitution: TypeSubstitution)(using NewResolverState): Unit = watch((new Identity(value), context, substitution))( listener => resolver.listenTerm(value)(listener)(using rstate.withInstances(substitution))): shape => emit(resolver.instantiateShape(shape, substitution).exit(context), path) head match case contextual: ContextualShape => contextual.source.exit(marks) match case value: TermShape => - val substitution = instances ++ contextual.instances + val substitution = instances.withOverrides(contextual.instances) valueIn(value, path, substitution)(using rstate.withInstances(substitution)) case NoShape => () case specialized: SpecializedShape => - resolver.instantiateShape(specialized.declaration, instances ++ specialized.instances).exit(marks) match + resolver.instantiateShape(specialized.declaration, instances.withOverrides(specialized.instances)).exit(marks) match case value: TermShape => valueIn(value, path, instances) case NoShape => () case _: InstanceShape => @@ -158,9 +158,9 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) case _ => () case record: RecordShape => record.elements.foreach: case RecordShape.Field(field) => contextualTerm(field.rhs, marks, - path.via(msg"This value is stored in this record field." -> field.toLoc), instances ++ record.instances) + path.via(msg"This value is stored in this record field." -> field.toLoc), instances.withOverrides(record.instances)) case RecordShape.Spread(inner, context) => - emit(resolver.instantiateShape(inner, instances ++ record.instances).exit(context).exit(marks), path) + emit(resolver.instantiateShape(inner, instances.withOverrides(record.instances)).exit(context).exit(marks), path) case _ => () case base: BaseShape => members(base.defn, marks, path) case callable: CallableTypeShape => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index dc43484d75..f9dad9090e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -80,7 +80,7 @@ class NewResolver: result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) // Validate unused annotations too, once their forward source graph // is complete. Interface observation uses the same gate below. - withCanonicalType(DeclaredType(result, Map.empty, Map.empty, true))(_ => ()) + withCanonicalType(DeclaredType(result, Map.empty, TypeSubstitution.empty, true))(_ => ()) case _ => result.publish(TypeShape.Abstract) symbol.defn match case S(defn) => definition(defn) @@ -486,7 +486,7 @@ class NewResolver: val relevant = if bindings.isEmpty then bindings else typeDependencies(resolution) match case R(binders) => bindings.filter((symbol, _) => binders(symbol)) case L(_) => bindings - DeclaredType(resolution, relevant, Map.empty, positive) + DeclaredType(resolution, relevant, TypeSubstitution.empty, positive) resolution.currentShapes.toList match case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) case TypeShape.Captured(base, thru) :: Nil => captureType(loop(base, bindings, aliases, positive), thru) @@ -503,7 +503,7 @@ class NewResolver: rstate.wildcardTypes.getOrElseUpdate((resolution, parts), { val normalized = new TypeResolution(resolution.source, resolution.fail) normalized.publish(TypeShape.Argument(parts)) - DeclaredType(normalized, Map.empty, Map.empty, true) + DeclaredType(normalized, Map.empty, TypeSubstitution.empty, true) }) case TypeShape.Applied(base, arguments) :: Nil => def applyAlias(base: DeclaredType, supplied: TypeApplication, seen: Set[TypeResolution]): Opt[DeclaredType] = @@ -556,7 +556,7 @@ class NewResolver: else rstate.combinedTypes.getOrElseUpdate(formula, { val resolution = new TypeResolution(source.source, source.fail) resolution.publish(TypeShape.Combined(formula)) - DeclaredType(resolution, Map.empty, Map.empty, true) + DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) }) private def declaredType(resolution: TypeResolution, context: DeclaredType)(using NewResolverState): DeclaredType = @@ -736,7 +736,7 @@ class NewResolver: case TypeShape.Function(_, result) => descend(declaredType(result, current)) case TypeShape.Polymorphic(parameters, _, body) => val symbols = parameters.map(_.parameter.symbol) - val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances -- symbols) + val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances.without(symbols)) follow(declaredType(body, lexical), args, captures, next) case TypeShape.Wildcard(_, output) => output.foreach(res => follow(declaredType(res, current), args, captures, next)) case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), args, captures, next) @@ -792,14 +792,14 @@ class NewResolver: * (DeclaredType, aggregate fields, or ContextualShape), preserving outer marks. * Receiver activation is separate and is attached by publishActivated. */ - private[semantics] def instantiateShape(value: TermShape, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): TermShape = + private[semantics] def instantiateShape(value: TermShape, instances: TypeSubstitution)(using NewResolverState): TermShape = if instances.isEmpty then value else rstate.shapeViews.getOrElseUpdate((value, instances), { val Marked(source, marks) = value val instantiated: NonMarkedShape = source match case instance: InstanceShape => InstanceShape(instance.tpe.instantiate(instances)) case rigid: RigidTypeShape => instances.get(rigid.parameter).fold[NonMarkedShape](rigid)(p => InstanceShape(instanceType(p))) - case tuple: TupleShape => tuple.copy(instances = instances ++ tuple.instances)(this) - case record: RecordShape => record.copy(instances = instances ++ record.instances) + case tuple: TupleShape => tuple.copy(instances = instances.withOverrides(tuple.instances))(this) + case record: RecordShape => record.copy(instances = instances.withOverrides(record.instances)) case nominal: NominalInstanceView => val bindings = instances.map((parameter, instance) => parameter -> instanceType(instance)) ++ nominal.bindings.map((parameter, bound) => parameter -> bound.instantiate(instances)) @@ -808,46 +808,37 @@ class NewResolver: val bindings = instances.map((parameter, instance) => parameter -> instanceType(instance)) ++ record.bindings.map((parameter, bound) => parameter -> bound.instantiate(instances)) record.copy(bindings = bindings) - case contextual: ContextualShape => contextual.copy(instances = instances ++ contextual.instances) + case contextual: ContextualShape => contextual.copy(instances = instances.withOverrides(contextual.instances)) case specialized: SpecializedShape => specialized.copy( - arguments = specialized.arguments.map(_.instantiate(instances)), instances = instances ++ specialized.instances) + arguments = specialized.arguments.map(_.instantiate(instances)), instances = instances.withOverrides(specialized.instances)) case callable: CallableTypeShape => - val captured = instances -- callable.tparams.map(_.parameter.symbol) + val captured = instances.without(callable.tparams.map(_.parameter.symbol)) def instantiate(tpe: DeclaredType): DeclaredType = tpe.instantiate(captured) val scheme = callable.scheme.map: scheme => scheme.copy(parameters = scheme.parameters.map: parameter => parameter.copy(lower = parameter.lower.map(instantiate), upper = parameter.upper.map(instantiate))) - callable.copy(paramLists = callable.paramLists.map: params => + callable.copy(paramLists = callable.paramLists.ne_map: params => DeclaredParams(params.params.map(_.map(instantiate)), params.hasRest, params.rest.map(instantiate)) , result = callable.result.map(instantiate), scheme = scheme) case literal: IntroShape if !literal.trm.isInstanceOf[Lam] => literal case _: (UnknownValueShape | DynShape | OpaqueTypeShape | ErrShape) => source - case _ => ContextualShape(source, instances) - marks match - case NoMarks => instantiated - case marks: SomeMarks => MarkedShape(instantiated, marks) + case source: ContextualSource => ContextualShape(source, instances) + Marked(instantiated, marks) }) - private def contextualParts(value: TermShape): (TermShape, Map[VarSymbol, TypeParameterInstance]) = value match - case Marked(ContextualShape(source, instances), marks) => - (marks match - case NoMarks => source - case marks: SomeMarks => MarkedShape(source, marks) - , instances) - case _ => (value, Map.empty) - - /** Operations on a specialized declaration reuse its consumed binder group. - * Keep that distinction separate from lexical captures of an open scheme. + /** Decode outer views only. ContextualSource excludes views and marked shapes; + * SpecializedShape contains a DefnShape; AppShape receivers are CoreTermShapes. + * After peeling outer marks, at most one view remains; application receivers + * cannot hide another view. MarkedShape preserves the decoded core's type. + * Keep applications intact: their consumed term lists are not view wrappers. */ - private def callableParts(value: TermShape): (TermShape, Map[VarSymbol, TypeParameterInstance], Bool) = - val (original, captured) = contextualParts(value) - original match - case Marked(specialized: SpecializedShape, marks) => - val declaration = marks match - case NoMarks => specialized.declaration - case marks: SomeMarks => MarkedShape(specialized.declaration, marks) - (declaration, captured ++ specialized.instances, true) - case _ => (original, captured, false) + private def shapeParts(value: TermShape): ShapeParts = + val Marked(source, marks) = value + source match + case ContextualShape(source, instances) => ShapeParts(Marked(source, marks), instances, N) + case SpecializedShape(declaration, arguments, instances) => + ShapeParts(Marked(declaration, marks), instances, S(arguments)) + case source: CoreShape => ShapeParts(Marked(source, marks), TypeSubstitution.empty, N) private def listenTypeViews(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = typeViews.get(tpe) match @@ -933,11 +924,11 @@ class NewResolver: case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current.copy(positive = !current.positive))) , fields.exists(!_.isInstanceOf[Fld]), N) case single => DeclaredParams(S(declaredType(typeResolution(single), current.copy(positive = !current.positive))) :: Nil, false, N) - publish(CallableTypeShape(tpe.resolution.source, ps :: Nil, + publish(CallableTypeShape(tpe.resolution.source, ps ne_:: Nil, S(declaredType(ret, current)), N, N, N)) case TypeShape.Polymorphic(params, _, body) => val symbols = params.map(_.parameter.symbol) - val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances -- symbols) + val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances.without(symbols)) val binders = params.map: param => DeclaredTypeParameter(param.parameter, param.lower.map(declaredType(_, lexical)), @@ -1071,22 +1062,22 @@ class NewResolver: val result = resultSignature(td).map(signature(_, positive)) if td.sign.nonEmpty && (td.k is syntax.Fun) && !td.flags.hasResultAnnotation && !isByName(td) then listenTypeViews(signature(td.sign.get, positive))(publish) - else if td.params.nonEmpty then - publish(CallableTypeShape(source, - td.params.map(ps => DeclaredParams(ps.params.map(_.sign.map(signature(_, !positive))), + else td.params match + case params: NELs[ParamList] => publish(CallableTypeShape(source, + params.ne_map(ps => DeclaredParams(ps.params.map(_.sign.map(signature(_, !positive))), ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature(_, !positive)))), result, N, N, S(td))) - else result match - case S(tpe) => listenTypeInstances(tpe)(publish) - case N => - // Keep the missing constructor annotation on the unknown value so - // later selections and calls can explain why argument flow is hidden. - val unknown = td.tsym.decl match - case S(param: Param) => UnknownValueShape(source)(ShapeProvenance( - (msg"Constructor parameter '${param.sym.nme}' has no type annotation." -> param.toLoc) :: - annotation.toList.map(sign => - msg"This type annotation does not provide a type for field '${member.nme}'." -> sign.toLoc))) - case _ => UnknownValueShape.at(source) - publish(unknown) + case Nil => result match + case S(tpe) => listenTypeInstances(tpe)(publish) + case N => + // Keep the missing constructor annotation on the unknown value so + // later selections and calls can explain why argument flow is hidden. + val unknown = td.tsym.decl match + case S(param: Param) => UnknownValueShape(source)(ShapeProvenance( + (msg"Constructor parameter '${param.sym.nme}' has no type annotation." -> param.toLoc) :: + annotation.toList.map(sign => + msg"This type annotation does not provide a type for field '${member.nme}'." -> sign.toLoc))) + case _ => UnknownValueShape.at(source) + publish(unknown) case _ => // A nested nominal declaration denotes its statically selected symbol; // selecting it does not inspect an instance field or method body. @@ -1144,7 +1135,7 @@ class NewResolver: * checked against an explicit argument rather than widening its interface. * Recursive and mutually dependent bounds reuse the ordinary relation graph. */ - private def constrainQuantifiedBounds(parameters: Ls[DeclaredTypeParameter], substitution: Map[VarSymbol, TypeParameterInstance]) + private def constrainQuantifiedBounds(parameters: Ls[DeclaredTypeParameter], substitution: TypeSubstitution) (using NewResolverState): Unit = parameters.foreach: parameter => val instance = ContextualType(parameterType(parameter.parameter).instantiate(substitution), Nil) @@ -1165,7 +1156,7 @@ class NewResolver: val substitution = rstate.instantiateTypeParameters(scheme.owner, site, scheme.parameters.map(_.parameter.symbol)) def instantiate(tpe: DeclaredType): DeclaredType = tpe.instantiate(substitution) val instantiated = callable.copy( - paramLists = callable.paramLists.map: params => + paramLists = callable.paramLists.ne_map: params => DeclaredParams(params.params.map(_.map(instantiate)), params.hasRest, params.rest.map(instantiate)) , result = callable.result.map(instantiate), scheme = N, supplied = N) // Reentrant observations must find the view before supplied arguments @@ -1271,7 +1262,7 @@ class NewResolver: case TypeShape.Nominal(cls) => listenInstanceViews(value): value => val arguments = typeArguments(tpe, args, cls.tparams).map(transportType(_, captures)) def constrainNominal(value: TermShape)(using NewResolverState): Unit = - val (actual, instances) = contextualParts(value) + val ShapeParts(actual, instances, _) = shapeParts(value) val (head, context) = actual.applicationHead def constrain(param: TyParam, bound: DeclaredType, pattern: DeclaredType)(using NewResolverState): Unit = listenArgumentParts(bound.instantiate(instances)): actual => @@ -1325,8 +1316,8 @@ class NewResolver: val expected = declaredType(sign, bindings, positive) val flow = rstate.fieldProjectionSite(field.sym) def receive(value: TermShape)(using NewResolverState): Unit = inferTypeArguments(expected, value, marks) - def member(info: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match - case MemberLookup.Contextual(source, substitution) => member(source, instances ++ substitution) + def member(info: MemberLookup, instances: TypeSubstitution)(using NewResolverState): Unit = info match + case MemberLookup.Contextual(source, substitution) => member(source, instances.withOverrides(substitution)) case MemberLookup.Found(RecordMember(member, true), _) => receive(UnknownValueShape.at(member.rhs)) case MemberLookup.Found(member, inner) => fromBMS(member.memberSymbol, flow, inner, value => receive(instantiateShape(value, instances)), field.rhs, _ => (), false)(using rstate.withInstances(instances)) @@ -1341,14 +1332,14 @@ class NewResolver: case _ => () actual match case Marked(_: UnknownValueShape, _) => receive(actual) - case _ => member(actual.getMember(field.sym.nme), Map.empty) + case _ => member(actual.getMember(field.sym.nme), TypeSubstitution.empty) /** Function constraints reverse the parameter flow and preserve result flow. * Read an implementation only while checking it against its declared interface; * calls through that interface continue to expose only the declared result. */ private[semantics] def constrainFunction(expected: DeclaredType, source: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = - val (actual, instances, _) = callableParts(source) + val ShapeParts(actual, instances, _) = shapeParts(source) actual match case Marked(_: InstanceShape, _) => listenInstanceViews(actual)(constrainFunction(expected, _, marks)) @@ -1427,7 +1418,7 @@ class NewResolver: case NoShape => () givenType.paramLists.tail match case Nil => givenType.result.foreach(listenTypeInstances(_)(receive)) - case tail => receive(givenType.copy(paramLists = tail)) + case tail: NELs[DeclaredParams] => receive(givenType.copy(paramLists = tail)) case _ => () case _ => matchLists(expected, actual.unappliedParams) @@ -1615,7 +1606,7 @@ class NewResolver: val cls = prelude.builtins.Array.defn.get softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") val param = cls.tparams.head.sym - val (source, instances) = contextualParts(shape) + val ShapeParts(source, instances, _) = shapeParts(shape) val Marked(value, _) = source // Supplied and inferred element types share the allocation's parameter. // Outer marks transport its bounds through callers without changing which @@ -1649,7 +1640,7 @@ class NewResolver: zipArgsIn(mss, ps, r, args, src, funSh, rstate.instances) private def zipArgsIn(mss: Ls[Marks], ps: Ls[Param], r: Opt[Param], args: Term, src: Term, funSh: TermShape, - instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + instances: TypeSubstitution)(using NewResolverState): Unit = val params = ps ::: r.toList zipArgumentShapes(mss, ps.length, r.nonEmpty, args, src, funSh): (index, shape) => constrainParameterIn(params(index), shape, mss, instances) @@ -1658,7 +1649,7 @@ class NewResolver: constrainParameterIn(p, shape, marks, rstate.instances) private def constrainParameterIn(p: Param, shape: TermShape | NoShape, marks: Ls[Marks], - instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + instances: TypeSubstitution)(using NewResolverState): Unit = shape match case NoShape => () case sh: TermShape => @@ -1794,8 +1785,8 @@ class NewResolver: if !rstate.hasError(res) then val psh = CtorPatternShape(cls, assoc, res, FlowSymbol.pat()(using rstate.owner)) if res.currentShapes.add(psh) then res.notifyShapeListeners(psh) - def valuePattern(sh: TermShape)(using NewResolverState): Unit = sh.applicationHead match - case (ds: DefnShape, _) => ds.defn match + def valuePattern(sh: TermShape)(using NewResolverState): Unit = shapeParts(sh).value match + case Marked(ds: DefnShape, _) => ds.defn match case cls: ClassDef => classPattern(cls) case obj: ModuleOrObjectDef if obj.sym.asObj.isDefined => classPattern(obj) case td: TermDefinition => td.tsym match @@ -1858,8 +1849,8 @@ class NewResolver: def check(sh: TermShape, unknown: Opt[TermShape])(using NewResolverState): Unit = if sh.isInstanceOfClass(cls) then fs.foreach: (bms, pat) => - def member(info: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match - case MemberLookup.Contextual(source, substitution) => member(source, instances ++ substitution) + def member(info: MemberLookup, instances: TypeSubstitution)(using NewResolverState): Unit = info match + case MemberLookup.Contextual(source, substitution) => member(source, instances.withOverrides(substitution)) case MemberLookup.Found(sym: BlockMemberSymbol, marks) => fromBMS(sym, resSym, marks, field => matchShapePat(instantiateShape(field, instances), pat)(_ => ()), ctor.target, _ => (), false) @@ -1887,7 +1878,7 @@ class NewResolver: // between nominal matching and member lookup, not recovery // from a malformed class (filtered by classPattern above). softAssert(false, "Matched constructor is missing its field") - member(sh.getMember(bms.nme), Map.empty) + member(sh.getMember(bms.nme), TypeSubstitution.empty) matched(sh) def narrow(sh: TermShape)(using NewResolverState): Unit = sh match case Marked(unknown: UnknownValueShape, _) => @@ -2008,15 +1999,15 @@ class NewResolver: case _ => lastWords("A callable definition must have a class or term owner") private def instantiateDefinition(definition: DefnShape, site: FlowSymbol, marks: Ls[Marks], - captured: Map[VarSymbol, TypeParameterInstance], supplied: Opt[Ls[DeclaredType]]) - (using NewResolverState): Map[VarSymbol, TypeParameterInstance] = + captured: TypeSubstitution, supplied: Opt[Ls[DeclaredType]]) + (using NewResolverState): TypeSubstitution = val (owner, parameters) = definitionParameters(definition) instantiateParameters(owner, parameters, site, marks, captured, supplied) private def instantiateParameters(owner: AnyDefinitionSymbol, parameters: Ls[VarSymbol], site: FlowSymbol, - marks: Ls[Marks], captured: Map[VarSymbol, TypeParameterInstance], supplied: Opt[Ls[DeclaredType]]) - (using NewResolverState): Map[VarSymbol, TypeParameterInstance] = - val substitution = captured ++ rstate.instantiateTypeParameters(owner, site, parameters) + marks: Ls[Marks], captured: TypeSubstitution, supplied: Opt[Ls[DeclaredType]]) + (using NewResolverState): TypeSubstitution = + val substitution = captured.withOverrides(rstate.instantiateTypeParameters(owner, site, parameters)) supplied.foreach: arguments => bindTypeArguments(parameters.map: parameter => val instance = substitution(parameter) @@ -2033,10 +2024,10 @@ class NewResolver: def appShape(source: TermShape, args: Term, res: App)(using NewResolverState): Unit = val callerInstances = rstate.instances - val (lhs, lexical, specialized) = callableParts(source) - val captured = callerInstances ++ lexical + val ShapeParts(lhs, lexical, supplied) = shapeParts(source) + val captured = callerInstances.withOverrides(lexical) val instances = lhs match - case Marked(definition: DefnShape, _) if !specialized => + case Marked(definition: DefnShape, _) if supplied.isEmpty => instantiateDefinition(definition, res.resSym, lhs.applicationHead._2, captured, N) case Marked(construction: NewShape, _) if construction.argss.isEmpty && !construction.isSaturated && construction.supplied.isEmpty => instantiateDefinition(construction.receiver, res.resSym, lhs.applicationHead._2, captured, N) @@ -2070,13 +2061,13 @@ class NewResolver: case NoShape => () def publish(shape: TermShape)(using NewResolverState): Unit = transportShape(shape, context :: Nil) match case value: TermShape => - publishViewed(res, value, Map.empty)(using rstate.withInstances(callerInstances)) + publishViewed(res, value, TypeSubstitution.empty)(using rstate.withInstances(callerInstances)) case NoShape => () callable.paramLists.tail match case Nil => callable.result match case S(tpe) => listenTypeInstances(tpe)(publish) case N => publish(UnknownValueShape.at(callable.source)) - case tail => publish(callable.copy(paramLists = tail)) + case tail: NELs[DeclaredParams] => publish(callable.copy(paramLists = tail)) return case _ => () // log(s"appShape? lhs = $lhs, args = $args, res = $res") @@ -2162,22 +2153,22 @@ class NewResolver: * refer to the caller's binder instance when delivered into a callee activation. */ private def publishViewed(host: ShapeHost, source: Shape, - instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + instances: TypeSubstitution)(using NewResolverState): Unit = val shape = source match case value: TermShape => instantiateShape(value, instances) case symbol: SymShape if instances.nonEmpty => val (original, substitution) = symbol match - case contextual: ContextualSymShape => (contextual.source, instances ++ contextual.instances) - case _ => (symbol, instances) + case contextual: ContextualSymShape => (contextual.source, instances.withOverrides(contextual.instances)) + case core: CoreSymShape => (core, instances) rstate.contextualSymbols.getOrElseUpdate((original, substitution), ContextualSymShape(original, substitution)) case _ => source publishActivated(host, shape) private def publishMember(host: NewResolvable & ShapeHost, member: BlockMemberSymbol | RecordMember, - flow: FlowSymbol, marks: Ls[Marks], instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + flow: FlowSymbol, marks: Ls[Marks], instances: TypeSubstitution)(using NewResolverState): Unit = def publish(shape: Shape)(using NewResolverState): Unit = publishViewed(host, shape, instances) def definition(sym: BlockMemberSymbol)(using NewResolverState): Unit = - publish(symShapes.getOrElseUpdate((sym, flow, marks), SymShape(sym, flow, marks))) + publish(symShapes.getOrElseUpdate((sym, flow, marks), CoreSymShape(sym, flow, marks))) member match case member: BlockMemberSymbol => definition(member) case RecordMember(field, false) => definition(field.sym) @@ -2190,7 +2181,7 @@ class NewResolver: private def publishDeclared(host: NewResolvable & ShapeHost, member: BlockMemberSymbol, flow: FlowSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term], positive: Bool, - instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = + instances: TypeSubstitution)(using NewResolverState): Unit = // Keep one diagnostic witness per semantic candidate, as for unknown values. // A different annotation location must not create additional inference flow. val shape = declaredSymShapes.getOrElseUpdate((member, flow, marks, bindings, positive), @@ -2215,8 +2206,8 @@ class NewResolver: def unresolvedRef(ref: UnresolvedRef)(using NewResolverState): Unit = ref.prefixes.foreach: prefix => listenReceiver(prefix): shape => - def member(info: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match - case MemberLookup.Contextual(source, substitution) => member(source, instances ++ substitution) + def member(info: MemberLookup, instances: TypeSubstitution)(using NewResolverState): Unit = info match + case MemberLookup.Contextual(source, substitution) => member(source, instances.withOverrides(substitution)) case MemberLookup.Found(member, marks) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> member.memberSymbol) => val candidate = prefix -> member.memberSymbol rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) @@ -2237,7 +2228,7 @@ class NewResolver: case MemberLookup.Unknown(reason, provenance) => unknownMember(ref, ref.id.name, reason, provenance) case _ => () // Completed references only route their selected members. - member(shape.getMember(ref.id.name), Map.empty) + member(shape.getMember(ref.id.name), TypeSubstitution.empty) /** Inspect an overload set only once its definitions have all been published. */ private def completedClass(shape: SymShape)(selected: ShapeListener[ClassDef], absent: () => NewResolverState ?=> Unit)(using NewResolverState): Unit = @@ -2250,7 +2241,7 @@ class NewResolver: * companions. Aliases can supply constructor shapes; applied instances cannot. * Capture marks are retained for subsequent instance-member lookup. */ private case class ClassReference(definition: ClassDef, marks: Ls[Marks], - instances: Map[VarSymbol, TypeParameterInstance], supplied: Opt[Ls[DeclaredType]]) + instances: TypeSubstitution, supplied: Opt[Ls[DeclaredType]]) private def listenClass(trm: Term)(selected: ShapeListener[ClassReference], reject: ShapeListener[Shape])(using NewResolverState): Unit = def select(ref: ClassReference)(using NewResolverState): Unit = @@ -2261,16 +2252,14 @@ class NewResolver: case _ => () selected(ref) def value(sh: TermShape)(using NewResolverState): Unit = - val (source, instances) = contextualParts(sh) + val ShapeParts(source, instances, supplied) = shapeParts(sh) val marks = source.applicationHead._2 - def fromDefinition(ds: DefnShape, supplied: Opt[Ls[DeclaredType]], captured: Map[VarSymbol, TypeParameterInstance]): Unit = + def fromDefinition(ds: DefnShape): Unit = ds.clsDef match - case S(cls: ClassDef) => select(ClassReference(cls, marks, instances ++ captured, supplied)) + case S(cls: ClassDef) => select(ClassReference(cls, marks, instances, supplied)) case _ => reject(sh) source match - case Marked(ds: DefnShape, _) => fromDefinition(ds, N, Map.empty) - case Marked(specialized: SpecializedShape, _) => - fromDefinition(specialized.declaration, S(specialized.arguments), specialized.instances) + case Marked(ds: DefnShape, _) => fromDefinition(ds) case _ => reject(sh) trm match case application @ TyApp(base, args) => listenClass(base)(ref => @@ -2282,7 +2271,7 @@ class NewResolver: if ref.supplied.nonEmpty then select(ref) else val instances = instantiateParameters(ref.definition.sym, ref.definition.tparams.map(_.sym), - rstate.typeApplicationSite(application), ref.marks, rstate.instances ++ ref.instances, S(supplied)) + rstate.typeApplicationSite(application), ref.marks, rstate.instances.withOverrides(ref.instances), S(supplied)) select(ref.copy(instances = instances, supplied = S(supplied))) , reject) case Capture(base, thru) => @@ -2294,7 +2283,7 @@ class NewResolver: ExitMark(ResolutionBoundary(cls.sym), S(sh.resSym), NoMarks) :: sh.markss, sh match case contextual: ContextualSymShape => contextual.instances - case _ => Map.empty + case _ => TypeSubstitution.empty , N)), () => fromSymbol(sh, value, trm, _ => (), false)) case sh: TermShape => value(sh) @@ -2302,8 +2291,8 @@ class NewResolver: def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") def member(info: MemberLookup, description: Message, loc: Opt[Loc], - instances: Map[VarSymbol, TypeParameterInstance])(using NewResolverState): Unit = info match - case MemberLookup.Contextual(source, substitution) => member(source, description, loc, instances ++ substitution) + instances: TypeSubstitution)(using NewResolverState): Unit = info match + case MemberLookup.Contextual(source, substitution) => member(source, description, loc, instances.withOverrides(substitution)) case MemberLookup.Found(bms, marks) if rstate.canResolve(sel) || sel.resolvedMembers.contains(bms.memberSymbol) => log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") rstate.recordResolution(sel, sel.resolvedMembers.contains(bms.memberSymbol))(sel.resolvedMembers ::= bms.memberSymbol) @@ -2333,7 +2322,7 @@ class NewResolver: sel.cls match case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") - member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc, Map.empty) + member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc, TypeSubstitution.empty) case S(cls) => listenClass(cls)(ref => val cd = ref.definition @@ -2362,8 +2351,8 @@ class NewResolver: val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation, true) case _ => info - member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, Map.empty) - case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc, Map.empty)) + member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, TypeSubstitution.empty) + case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc, TypeSubstitution.empty)) , sh => rstate.markError(sel) resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc :: Nil) @@ -2409,7 +2398,7 @@ class NewResolver: val callerInstances = rstate.instances listenExt(cd, extsh => val dsh = constructorShape(cd, extsh) - val captured = callerInstances ++ ref.instances + val captured = callerInstances.withOverrides(ref.instances) // Explicit type arguments already consumed this scheme. Otherwise an // unapplied constructor waits for its first term application. val instances = if ref.supplied.nonEmpty || (nw.args.isEmpty && dsh.unappliedParams.nonEmpty) then captured @@ -2523,7 +2512,7 @@ class NewResolver: private def instantiateByName(td: TermDefinition, site: FlowSymbol, marks: Ls[Marks], specialization: Opt[(FlowSymbol, Ls[DeclaredType])], bindings: Map[VarSymbol, DeclaredType]) - (using NewResolverState): Map[VarSymbol, TypeParameterInstance] = + (using NewResolverState): TypeSubstitution = val instances = instantiateParameters(td.tsym, definitionBinders(td), specialization.fold(site)(_._1), marks, rstate.instances, specialization.map(_._2)) val parameters = definitionQuantifiers(td).map: parameter => @@ -2560,7 +2549,7 @@ class NewResolver: log(s"FILTER OUT ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") case sh: TermShape => // if sh is sh0 - if sh0.isInstanceOf[MarkedShape] + if sh0.isInstanceOf[MarkedShape[?]] then log(s"MATCH ${sh.shwDbg} for ${sym.showDbg} % ${resSym.showDbg}") else log(s"PUSH ${sh.shwDbg}") listener(sh) @@ -2570,7 +2559,7 @@ class NewResolver: val callerInstances = rstate.instances val instances = if isByName(td) then instantiateByName(td, resSym, ExitMark(ResolutionBoundary(td.tsym), S(resSym), NoMarks) :: markss, specialization, Map.empty) - else callerInstances -- td.tparams.toList.flatten.map(_.sym) + else callerInstances.without(td.tparams.toList.flatten.map(_.sym)) def receive(shape: TermShape)(using NewResolverState): Unit = wrappedListener(instantiateShape(shape, instances))(using rstate.withInstances(callerInstances)) resultSignature(td) match @@ -2727,11 +2716,11 @@ class NewResolver: // event and supply that activation to the receiving operation, without // replacing the enclosed value's independent caller-side substitution. val requestedInstances = rstate.instances - def compatible(instances: Map[VarSymbol, TypeParameterInstance]): Bool = + def compatible(instances: TypeSubstitution): Bool = requestedInstances.forall((parameter, instance) => instances.get(parameter).forall(_ eq instance)) val listener: ShapeListener[ShapeEvent] = event => event match case ActivatedShapeEvent(value, instances) => - if compatible(instances) then receive(value)(using rstate.withInstances(requestedInstances ++ instances)) + if compatible(instances) then receive(value)(using rstate.withInstances(requestedInstances.withOverrides(instances))) case value: TermShape => receive(instantiateShape(value, rstate.instances)) case symbol: SymShape => receive(symbol) log(s"listen: trm = ${trm.showDbg}") @@ -2747,11 +2736,11 @@ class NewResolver: resolError(trm, msg"${callee.describe.capitalize} expected ${count} type ${ "argument".pluralized(count)}, but got ${args.length}" -> callee.toLoc :: Nil) def instantiate(value: TermShape)(receive: Listener)(using NewResolverState): Unit = listenInstanceViews(value): viewed => - val (actual, captured) = contextualParts(viewed) - actual.applicationHead match - case (specialized: SpecializedShape, _) => - checkArity(specialized, 0) - receive(value) + val ShapeParts(actual, captured, supplied) = shapeParts(viewed) + if supplied.nonEmpty then + checkArity(actual, 0) + receive(value) + else actual.applicationHead match case (callable: CallableTypeShape, marks) => checkArity(callable, callable.tparams.length) val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) @@ -2773,7 +2762,7 @@ class NewResolver: else val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) val instances = instantiateDefinition(callee, rstate.typeApplicationSite(application), marks, - rstate.instances ++ captured, S(supplied)) + rstate.instances.withOverrides(captured), S(supplied)) SpecializedShape(callee, supplied, instances).exit(marks) match case specialized: TermShape => receive(specialized) case NoShape => () @@ -2944,7 +2933,7 @@ class NewResolver: case NoShape => ()) case ref @ MemberRef(sym: BlockMemberSymbol) => val fs = ref.resSym - val sh = symShapes.getOrElseUpdate((sym, fs, Nil), SymShape(sym, fs, Nil)) + val sh = symShapes.getOrElseUpdate((sym, fs, Nil), CoreSymShape(sym, fs, Nil)) listener(sh) case Capture(base, thru) => if discardMarks then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index c927b6f6c8..19e0e712f9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -38,18 +38,18 @@ object NewResolverState: */ final class NewResolverState private ( val owner: Elaborator.State, private val consumer: Opt[NewResolverState], private val source: Opt[NewResolverState], - private val contextBase: Opt[NewResolverState], val instances: Map[VarSymbol, TypeParameterInstance]): + private val contextBase: Opt[NewResolverState], val instances: TypeSubstitution): import NewResolverState.{Cache, Seen} - def this(owner: Elaborator.State) = this(owner, N, N, N, Map.empty) + def this(owner: Elaborator.State) = this(owner, N, N, N, TypeSubstitution.empty) private def root: NewResolverState = consumer.getOrElse(this) private def inherited: Opt[NewResolverState] = contextBase.orElse(source) - private val contexts = mutable.Map.empty[(NewResolverState, Map[VarSymbol, TypeParameterInstance]), NewResolverState] + private val contexts = mutable.Map.empty[(NewResolverState, TypeSubstitution), NewResolverState] /** Activations share the consuming unit's hosts and memoized source graph. * Only the finite substitution varies; composing views never adds a parent * context or changes either endpoint's originating graph. */ - def withInstances(substitution: Map[VarSymbol, TypeParameterInstance]): NewResolverState = + def withInstances(substitution: TypeSubstitution): NewResolverState = val base = contextBase.getOrElse(this) if substitution.isEmpty then base else root.contexts.getOrElseUpdate((base, substitution), @@ -75,12 +75,12 @@ final class NewResolverState private ( val origin = source.fold(graph)(_.rebase(graph, destination)) if origin.root eq root then origin else if root eq destination then - root.views.getOrElseUpdate(origin, new NewResolverState(owner, S(root), S(origin), N, Map.empty)) + root.views.getOrElseUpdate(origin, new NewResolverState(owner, S(root), S(origin), N, TypeSubstitution.empty)) else root.views.get(origin).getOrElse: // A previously unused path can require a view of an intermediate exporter. // Memoize that read-only view in the consumer, never in the exporter. destination.inheritedViews.getOrElseUpdate((root, origin), - new NewResolverState(owner, S(root), S(origin), N, Map.empty)) + new NewResolverState(owner, S(root), S(origin), N, TypeSubstitution.empty)) private val copies = mutable.Map.empty[Publisher.Data[?], Publisher.Data[?]] private val pending = mutable.Map.empty[Identity[Publisher[?]], Publisher.Data[?]] @@ -188,15 +188,15 @@ final class NewResolverState private ( new Cache(inherited.map(_.spreadInputs), _.clone()) val reportedArities: Cache[Object, mutable.Set[Int]] = new Cache(inherited.map(_.reportedArities), _.clone()) - val appShapes: Cache[(TermShape, FlowSymbol), AppShape] = + val appShapes: Cache[(CoreTermShape, FlowSymbol), AppShape] = new Cache(inherited.map(_.appShapes), identity) val newShapes: Cache[(ClassLikeSymbol, Ls[Marks], FlowSymbol, Opt[Ls[DeclaredType]]), NewShape] = new Cache(inherited.map(_.newShapes), identity) - val constructorApplications: Seen[(NewShape, Map[VarSymbol, TypeParameterInstance])] = + val constructorApplications: Seen[(NewShape, TypeSubstitution)] = new Seen(inherited.map(_.constructorApplications)) val introShapes: Cache[Identity[IntroTerm], IntroShape] = new Cache(inherited.map(_.introShapes), identity) - val symShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), SymShape] = + val symShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), CoreSymShape] = new Cache(inherited.map(_.symShapes), identity) val declaredSymShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks], Map[VarSymbol, DeclaredType], Bool), DeclaredSymShape] = new Cache(inherited.map(_.declaredSymShapes), identity) @@ -232,15 +232,15 @@ final class NewResolverState private ( new Cache(inherited.map(_.quantifiedTypes), identity) val instantiatedCallables: Cache[(CallableTypeShape, FlowSymbol, Ls[Marks]), CallableTypeShape] = new Cache(inherited.map(_.instantiatedCallables), identity) - val contextualSymbols: Cache[(SymShape, Map[VarSymbol, TypeParameterInstance]), ContextualSymShape] = + val contextualSymbols: Cache[(CoreSymShape, TypeSubstitution), ContextualSymShape] = new Cache(inherited.map(_.contextualSymbols), identity) - val shapeViews: Cache[(TermShape, Map[VarSymbol, TypeParameterInstance]), TermShape] = + val shapeViews: Cache[(TermShape, TypeSubstitution), TermShape] = new Cache(inherited.map(_.shapeViews), identity) - val inferredInstantiations: Seen[(AnyDefinitionSymbol, FlowSymbol, Map[VarSymbol, TypeParameterInstance], Ls[Marks])] = + val inferredInstantiations: Seen[(AnyDefinitionSymbol, FlowSymbol, TypeSubstitution, Ls[Marks])] = new Seen(inherited.map(_.inferredInstantiations)) // A scheme is owned by its source definition, or by the original interpretation // of an anonymous quantified annotation. Neither a view nor an instance is an owner. - private val typeInstances: Cache[(AnyDefinitionSymbol | TypeResolution, FlowSymbol), Map[VarSymbol, TypeParameterInstance]] = + private val typeInstances: Cache[(AnyDefinitionSymbol | TypeResolution, FlowSymbol), TypeSubstitution] = new Cache(inherited.map(_.typeInstances), identity) private var allocatedTypeInstances: Int = 0 private[hkmc2] def allocatedTypeInstanceCount: Int = root.allocatedTypeInstances @@ -263,7 +263,7 @@ final class NewResolverState private ( root.fieldProjectionSites.getOrElseUpdate(field, fieldProjectionSites.get(field).getOrElse( FlowSymbol.memSym(field)(using owner))) private[hkmc2] def instantiateTypeParameters(scheme: AnyDefinitionSymbol | TypeResolution, - site: FlowSymbol, parameters: Ls[VarSymbol]): Map[VarSymbol, TypeParameterInstance] = + site: FlowSymbol, parameters: Ls[VarSymbol]): TypeSubstitution = require(parameters.distinct.length == parameters.length, "A scheme cannot bind a parameter twice") val origin = scheme match case constructor: ClassCtorSymbol => constructor.associatedCls @@ -276,9 +276,9 @@ final class NewResolverState private ( // Construct the complete group without activating constraints. Recursive // subscribers may use it only after the cache contains every binder. val result = parameters.map: parameter => - parameter -> new TypeParameterInstance(parameter)(using owner) + new TypeParameterInstance(parameter)(using owner) root.allocatedTypeInstances += result.length - result.toMap + TypeSubstitution(result) }) assert(instances.keySet == parameters.toSet, "A source scheme's binders must remain stable") instances diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index dc7cf42ee8..a651746897 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -23,7 +23,7 @@ sealed trait Shape extends ShapeEvent, ShapeLike: // case ds: DefnShape => s"DefnShape(${ds.defn.describe} ${ds.defn.sym.showDbg})" case ds: DefnShape => ds.defn.sym.showDbg case as: AppShape => s"AppShape(${as.receiver.shwDbg}, ${as.args.showDbg})" - case ms: MarkedShape => s"MarkedShape(${ms.sh.shwDbg}, ${ms.mark.showDbg})" + case ms: MarkedShape[?] => s"MarkedShape(${ms.sh.shwDbg}, ${ms.mark.showDbg})" case ns: SymShape => s"SymShape(${ns.sym.showDbg})" case ns: NewShape => s"NewShape(${ns.cls.showDbg}, ${ns.argss.map(_.showDbg).mkString(", ")})" case is: IntroShape => s"IntroShape(${is.trm.showDbg})" @@ -45,6 +45,24 @@ sealed trait Shape extends ShapeEvent, ShapeLike: sealed trait NonMarkedShape extends TermShape sealed trait NonAppTermShape extends NonMarkedShape +/** A shape with its outer contextual/specialization view removed. Application + * receivers use this family, so a view cannot hide inside an application chain. + * Marks remain on the receiver and are accumulated by applicationHead. + */ +sealed trait CoreShape extends NonMarkedShape +sealed trait CoreHeadShape extends CoreShape, NonAppTermShape +type CoreTermShape = CoreShape | MarkedShape[CoreShape] + +// Only these shapes defer substitution to a ContextualShape. All other cores +// either store substitutions in their own fields or are independent of binders. +type ContextualSource = AppShape | NewShape | DefnShape | BaseShape | IntroShape + +/** The value's lexical substitution and any already consumed type arguments. + * Removing these views preserves the full application chain and its marks. + */ +final case class ShapeParts(value: CoreTermShape, instances: TypeSubstitution, + supplied: Opt[Ls[DeclaredType]]) + /** A lexical resolution boundary, independent of a definition's term/type interpretation. * A class and its companion constructor enter the same instance scope. Keeping their * symbol identities distinct is still necessary for overload selection and lowering. @@ -115,20 +133,24 @@ case object NoShape extends ShapeLike: type NoShape = NoShape.type object Marked: + // Reattach an already normalized outer path without erasing the core's type. + def apply[T <: NonMarkedShape](shape: T, marks: Marks): T | MarkedShape[T] = marks match + case NoMarks => shape + case marks: SomeMarks => MarkedShape(shape, marks) def unapply(sh: TermShape): S[(NonMarkedShape, Marks)] = sh match case sh: NonMarkedShape => S((sh, NoMarks)) case MarkedShape(sh, mark) => S((sh, mark)) end Marked -case class MarkedShape(sh: NonMarkedShape, mark: SomeMarks) extends TermShape: +case class MarkedShape[+T <: NonMarkedShape](sh: T, mark: SomeMarks) extends TermShape: protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = sh.getMemberThrough(name, mark) override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = sh.isInstanceOfClass(cls) def describe: Str = sh.describe def toLoc: Opt[Loc] = sh.toLoc object MarkedShape: - def enter(sh: TermShape, boundary: ResolutionBoundary, id: Opt[FlowSymbol])(using TL): MarkedShape = + def enter(sh: TermShape, boundary: ResolutionBoundary, id: Opt[FlowSymbol])(using TL): MarkedShape[NonMarkedShape] = sh match case sh: NonMarkedShape => MarkedShape(sh, EntryMark(boundary, id, NoMarks)) case MarkedShape(sh, marks) => MarkedShape(sh, EntryMark(boundary, id, marks)) @@ -279,7 +301,7 @@ end TermShape /** Missing means definitely absent; Unknown must not be treated as a miss when * searching wildcard opens, since it can introduce an additional candidate. */ enum MemberLookup: - case Contextual(source: MemberLookup, instances: Map[VarSymbol, TypeParameterInstance]) + case Contextual(source: MemberLookup, instances: TypeSubstitution) case Found(member: BlockMemberSymbol | RecordMember, marks: Ls[Marks]) // Declared members expose signatures only. Their marks transport dependent // type arguments; they never authorize reading an implementation's value flow. @@ -303,9 +325,9 @@ enum MemberLookup: case Declared(member, bindings, marks, _, positive) => Declared(member, bindings, marks, annotation, positive) case _ => this - def instantiate(instances: Map[VarSymbol, TypeParameterInstance]): MemberLookup = + def instantiate(instances: TypeSubstitution): MemberLookup = if instances.isEmpty then this else this match - case Contextual(source, previous) => Contextual(source, instances ++ previous) + case Contextual(source, previous) => Contextual(source, instances.withOverrides(previous)) case Missing | Unknown(_, _) | Dynamic(_) => this case _ => Contextual(this, instances) @@ -324,12 +346,12 @@ extension (member: BlockMemberSymbol | RecordMember) case symbol: BlockMemberSymbol => symbol case member: RecordMember => member.field.sym -class ErrShape(val err: ErrorReport) extends NonAppTermShape: +class ErrShape(val err: ErrorReport) extends CoreHeadShape: def describe: Str = s"error: ${err.mainMsg}" protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Missing def toLoc: Opt[Loc] = N -class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using DebugPrinter) extends NonMarkedShape: +class AppShape(val receiver: CoreTermShape, val args: Term, val src: Term.App)(using DebugPrinter) extends CoreShape: protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = // An unsaturated term definition is just a concrete function shape if !isSaturated then MemberLookup.Missing @@ -350,7 +372,7 @@ class AppShape(val receiver: TermShape, val args: Term, val src: Term.App)(using // def target: Opt[AppTarget] class NewShape(val receiver: DefnShape, val cls: ClassLikeSymbol, val clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New, - val supplied: Opt[Ls[DeclaredType]])(using DebugPrinter) extends NonMarkedShape: + val supplied: Opt[Ls[DeclaredType]])(using DebugPrinter) extends CoreShape: protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = if isSaturated then receiver.getInstanceMember(name).withMarks(clsMarks) else MemberLookup.Missing @@ -362,7 +384,7 @@ class NewShape(val receiver: DefnShape, val cls: ClassLikeSymbol, val clsMarks: override def toString: String = s"NewNewShape(${cls.showDbg}, $argss)" def toLoc: Opt[Loc] = src.toLoc -class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: Ls[Marks]) extends Shape: +sealed abstract class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: Ls[Marks]) extends Shape: def describe: Str = s"${sym.describe} symbol '${sym.nme}'" def toLoc: Opt[Loc] = sym.toLoc override def toString: String = s"SymShape($sym)" @@ -371,10 +393,16 @@ class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: L def enter(revMarkss: Ls[Marks])(using TL): TermShape | NoShape = ??? def enter(marks: Marks)(using TL): TermShape | NoShape = ??? +/** A symbolic value before its captured substitution is attached. Contextual + * symbol views compose substitutions instead of wrapping one another. + */ +sealed class CoreSymShape(symbol: BlockMemberSymbol, site: FlowSymbol, marks: Ls[Marks]) +extends SymShape(symbol, site, marks) + /** The symbolic counterpart of ContextualShape: retain the value's substitution * until overload selection produces a term shape. This does not consume a scheme. */ -final class ContextualSymShape(val source: SymShape, val instances: Map[VarSymbol, TypeParameterInstance]) +final class ContextualSymShape(val source: CoreSymShape, val instances: TypeSubstitution) extends SymShape(source.sym, source.resSym, source.markss) @@ -382,7 +410,7 @@ extends SymShape(source.sym, source.resSym, source.markss) * type, and constructor interpretations of the same overload set. */ final class DeclaredSymShape(symbol: BlockMemberSymbol, site: FlowSymbol, marks: Ls[Marks], - val bindings: Map[VarSymbol, DeclaredType], val annotation: Opt[Term], val positive: Bool) extends SymShape(symbol, site, marks) + val bindings: Map[VarSymbol, DeclaredType], val annotation: Opt[Term], val positive: Bool) extends CoreSymShape(symbol, site, marks) /* class ThisShape(val defn: Definition) extends NonAppTermShape: @@ -392,7 +420,7 @@ class ThisShape(val defn: Definition) extends NonAppTermShape: */ // TODO: make it not a TermShape? -class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppTermShape: +class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends CoreHeadShape: override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = (defn is cls) || ext.exists(_.isInstanceOfClass(cls)) def describe: Str = s"${defn.describe}" @@ -404,7 +432,7 @@ class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends NonAppT * to the annotation, not to whichever record happens to be passed by a caller. */ final case class RecordTypeShape(source: Term.Rcd, fields: Ls[(RcdField, TypeResolution)], - bindings: Map[VarSymbol, DeclaredType], positive: Bool) extends NonAppTermShape: + bindings: Map[VarSymbol, DeclaredType], positive: Bool) extends CoreHeadShape: def describe: Str = "record type" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = @@ -417,7 +445,7 @@ final case class RecordTypeShape(source: Term.Rcd, fields: Ls[(RcdField, TypeRes * candidates would lose negative uses of generic arguments and compound types. * Operations obtain concrete member/call views through listenInstanceViews. */ -final case class InstanceShape(tpe: DeclaredType) extends NonAppTermShape: +final case class InstanceShape(tpe: DeclaredType) extends CoreHeadShape: def describe: Str = "value with a declared type" def toLoc: Opt[Loc] = tpe.resolution.source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = @@ -433,8 +461,7 @@ final case class InstanceShape(tpe: DeclaredType) extends NonAppTermShape: * Unlike ActivatedShapeEvent.instances, this map belongs to the value, not its receiver. * See doc/new-resolution-type-value-flow.md, "Value views, consumed schemes, and activation events". */ -final case class ContextualShape(source: NonMarkedShape, instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: - require(!source.isInstanceOf[ContextualShape], "Compose shape views rather than nesting them") +final case class ContextualShape(source: ContextualSource, instances: TypeSubstitution) extends NonAppTermShape: def describe: Str = source.describe def toLoc: Opt[Loc] = source.toLoc override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = source.isInstanceOfClass(cls) @@ -445,13 +472,13 @@ final case class ContextualShape(source: NonMarkedShape, instances: Map[VarSymbo /** Explicit type application, such as f[Int], has consumed this declaration's * scheme before any term argument list need be applied. `arguments` retains the * supplied caller type references; `instances` retains the chosen binder group - * and lexical captures. `callableParts` tells `appShape` to reuse that group even + * and lexical captures. `shapeParts` tells `appShape` to reuse that group even * through stored aliases, rather than instantiate the declaration again. * A ContextualShape alone does not record scheme consumption. Complete annotated * CallableTypeShape views record consumption by substituting and clearing `scheme`. */ final case class SpecializedShape(declaration: DefnShape, arguments: Ls[DeclaredType], - instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: + instances: TypeSubstitution) extends NonAppTermShape: def describe: Str = declaration.describe def toLoc: Opt[Loc] = declaration.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = declaration.getMember(name).instantiate(instances) @@ -468,7 +495,7 @@ final case class SpecializedShape(declaration: DefnShape, arguments: Ls[Declared * Equality includes the activation so distinct deliveries are not deduplicated. * Neither map changes the ordinary mark algebra used for lexical scope crossings. */ -final case class ActivatedShapeEvent(value: Shape, instances: Map[VarSymbol, TypeParameterInstance]) extends ShapeEvent: +final case class ActivatedShapeEvent(value: Shape, instances: TypeSubstitution) extends ShapeEvent: require(instances.nonEmpty, "Publish an activation-independent shape without an envelope") /** A nominal annotation exposes only declarations, including inherited declarations. @@ -477,7 +504,7 @@ final case class ActivatedShapeEvent(value: Shape, instances: Map[VarSymbol, Typ * interfaces such as a tuple's Array parent have no written annotation. */ final case class NominalInstanceView(defn: ClassLikeDef, bindings: Map[VarSymbol, DeclaredType], - parent: Opt[TermShape])(val annotation: Opt[Term])(resolver: NewResolver) extends NonAppTermShape: + parent: Opt[TermShape])(val annotation: Opt[Term])(resolver: NewResolver) extends CoreHeadShape: def describe: Str = s"value of type '${defn.sym.nme}'" def toLoc: Opt[Loc] = defn.toLoc override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = @@ -506,24 +533,23 @@ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool, re * remaining curried lists reuse the chosen instances. This is the annotated * counterpart of SpecializedShape, not an inference-event activation. */ -final case class CallableTypeShape(source: Term, paramLists: Ls[DeclaredParams], +final case class CallableTypeShape(source: Term, paramLists: NELs[DeclaredParams], result: Opt[DeclaredType], scheme: Opt[TypeScheme], supplied: Opt[Ls[DeclaredType]], - declaration: Opt[TermDefinition]) extends NonAppTermShape: - require(paramLists.nonEmpty) + declaration: Opt[TermDefinition]) extends CoreHeadShape: def tparams: Ls[DeclaredTypeParameter] = scheme.toList.flatMap(_.parameters) def describe: Str = declaration.fold("function with a declared signature")(d => s"function '${d.bsym.nme}'") def toLoc: Opt[Loc] = declaration.fold(source.toLoc)(_.toLoc) protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Missing /** An abstract annotation authorizes no operations based on the implementation. */ -final case class OpaqueTypeShape(source: Term) extends NonAppTermShape: +final case class OpaqueTypeShape(source: Term) extends CoreHeadShape: def describe: Str = "value of abstract type" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, ShapeProvenance(msg"This abstract type does not specify a member interface." -> toLoc :: Nil)) -class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends NonAppTermShape: +class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends CoreHeadShape: /** Instance lookup is shared by constructor calls and explicit `new`. * Inherited members retain their marks before the caller adds its captures. */ def getInstanceMember(name: Str)(using NewResolverState): MemberLookup = defn match @@ -579,7 +605,7 @@ class RefinedShape(val base: TermShape, val refinements: Ls[Str -> Term]) extend * them instead of discarding either those fields or the unresolved possibilities. */ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element], - instances: Map[VarSymbol, TypeParameterInstance])(resolver: NewResolver) extends NonAppTermShape: + instances: TypeSubstitution)(resolver: NewResolver) extends CoreHeadShape: def arrayParent(using NewResolverState): NominalInstanceView = resolver.tupleArrayParent(this) override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = arrayParent.isInstanceOfClass(cls) private lazy val sourceSegments: Ls[TupleShape.Segment] = elements.flatMap: @@ -629,7 +655,7 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element], * Unlike UnknownValueShape, this authorizes dynamic operations; it is introduced * by JavaScript interop and explicit `dyn` types, not by failed inference. */ -final case class DynShape() extends NonAppTermShape: +final case class DynShape() extends CoreHeadShape: def describe: Str = "dynamic value" def toLoc: Opt[Loc] = N protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Dynamic(Nil) @@ -652,7 +678,7 @@ object ShapeProvenance: * Provenance is outside case-class equality: another diagnostic witness must * not turn the same unknown into a new inference candidate. */ -final case class UnknownValueShape(source: Term)(val provenance: ShapeProvenance) extends NonAppTermShape: +final case class UnknownValueShape(source: Term)(val provenance: ShapeProvenance) extends CoreHeadShape: def describe: Str = "value of unknown shape" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = @@ -661,7 +687,7 @@ final case class UnknownValueShape(source: Term)(val provenance: ShapeProvenance /** A generic definition must be valid without choosing a caller's type. This * checking witness is not an inferred bound on any call-site parameter. */ -final case class RigidTypeShape(parameter: VarSymbol, source: Term) extends NonAppTermShape: +final case class RigidTypeShape(parameter: VarSymbol, source: Term) extends CoreHeadShape: def describe: Str = "value of a type parameter" def toLoc: Opt[Loc] = source.toLoc def provenance: ShapeProvenance = ShapeProvenance( @@ -682,7 +708,7 @@ object UnknownValueShape: object TupleShape: def apply(source: Term, elements: Ls[Element])(resolver: NewResolver): TupleShape = - new TupleShape(source, elements, Map.empty)(resolver) + new TupleShape(source, elements, TypeSubstitution.empty)(resolver) sealed trait Element sealed trait Segment extends Element sealed trait Fixed extends Segment: @@ -693,11 +719,11 @@ object TupleShape: case UnknownField(source, inner) => UnknownField(source, inner ::: outer) case ValueField(value, inner) => ValueField(value, inner ::: outer) case ViewedField(source, instances, inner) => ViewedField(source, instances, inner ::: outer) - def instantiate(instances: Map[VarSymbol, TypeParameterInstance]): Fixed = + def instantiate(instances: TypeSubstitution): Fixed = if instances.isEmpty then this else this match - case ViewedField(source, previous, marks) => ViewedField(source, instances ++ previous, marks) + case ViewedField(source, previous, marks) => ViewedField(source, instances.withOverrides(previous), marks) case _ => ViewedField(this, instances, Nil) - final case class ViewedField(source: Fixed, instances: Map[VarSymbol, TypeParameterInstance], marks: Ls[Marks]) extends Fixed + final case class ViewedField(source: Fixed, instances: TypeSubstitution, marks: Ls[Marks]) extends Fixed final case class Field(field: Fld, marks: Ls[Marks]) extends Fixed final case class ValueField(value: TermShape, marks: Ls[Marks]) extends Fixed final case class TypedField(tpe: DeclaredType, marks: Ls[Marks]) extends Fixed @@ -742,7 +768,7 @@ final case class RecordMember(field: RcdField, mutable: Bool) * are barriers: an opaque spread or computed key can overwrite earlier fields. */ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element], - instances: Map[VarSymbol, TypeParameterInstance]) extends NonAppTermShape: + instances: TypeSubstitution) extends CoreHeadShape: def describe: Str = "record literal" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = @@ -776,7 +802,7 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] case _ => false object RecordShape: - def apply(source: Term.Rcd, elements: Ls[Element]): RecordShape = new RecordShape(source, elements, Map.empty) + def apply(source: Term.Rcd, elements: Ls[Element]): RecordShape = new RecordShape(source, elements, TypeSubstitution.empty) enum Element: case Field(field: RcdField) case Spread(shape: RecordShape, marks: Marks) @@ -786,7 +812,7 @@ object RecordShape: type IntroTerm = Term.Lit | Term.UnitVal | Term.Lam //| Term.New -class IntroShape(val trm: IntroTerm, val primitive: Opt[NominalInstanceView]) extends NonAppTermShape: +class IntroShape(val trm: IntroTerm, val primitive: Opt[NominalInstanceView]) extends CoreHeadShape: def describe: Str = trm.describe protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = trm match case _: Term.Lit | _: Term.UnitVal => primitive.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)) @@ -801,7 +827,7 @@ class IntroShape(val trm: IntroTerm, val primitive: Opt[NominalInstanceView]) ex def toLoc: Opt[Loc] = trm.toLoc override def toString: Str = s"IntroShape(${trm})" -sealed trait LitShape extends NonAppTermShape: +sealed trait LitShape extends CoreHeadShape: self: Term.Lit => protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Missing // TODO: methods on literals, e.g. string methods diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index d524a62530..063dde8d0e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -118,19 +118,17 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => /** A type with its lexical type-parameter bindings. These bindings describe declared * interfaces, not constructor arguments or value-flow capture paths. + * `positive` interprets substitutions in the source expression; it is not the + * direction of a constraint subsequently applied to the interpreted type. */ final case class DeclaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], - instances: Map[VarSymbol, TypeParameterInstance], positive: Bool): - // This polarity interprets substitutions in the source expression; it is not - // the direction of a constraint subsequently applied to the interpreted type. - require(instances.forall((source, instance) => instance.origin eq source), - "A substitution must map original binders to their call-site instances") + instances: TypeSubstitution, positive: Bool): /** Instantiation changes references to source binders, never expands their bounds. * A reference already interpreted in another call retains that interpretation. * The finite map contains original binders and canonical site instances only. */ - def instantiate(substitution: Map[VarSymbol, TypeParameterInstance]): DeclaredType = - copy(instances = substitution ++ instances) + def instantiate(substitution: TypeSubstitution): DeclaredType = + copy(instances = substitution.withOverrides(instances)) /** A type reference observed from a common comparison scope. The marks belong * to this endpoint: reversing a constraint swaps endpoints, not an expanded diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeSubstitution.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeSubstitution.scala new file mode 100644 index 0000000000..45a2f7a357 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeSubstitution.scala @@ -0,0 +1,21 @@ +package hkmc2 +package semantics + +import hkmc2.utils.*, shorthands.* + +/** A flat substitution from source binders to their canonical call-site instances. + * Keys are derived from instance origins; callers cannot choose a different key. + * Composition preserves existing instance identities, never instantiates an + * instance, and uses the right operand when both views interpret the same binder. + * The opaque map retains structural equality for inference-cache keys. + */ +opaque type TypeSubstitution <: Map[VarSymbol, TypeParameterInstance] = Map[VarSymbol, TypeParameterInstance] + +object TypeSubstitution: + val empty: TypeSubstitution = Map.empty + def apply(instances: IterableOnce[TypeParameterInstance]): TypeSubstitution = + instances.iterator.map(instance => instance.origin -> instance).toMap + + extension (self: TypeSubstitution) + def withOverrides(that: TypeSubstitution): TypeSubstitution = self ++ that + def without(parameters: IterableOnce[VarSymbol]): TypeSubstitution = self -- parameters diff --git a/hkmc2/shared/src/test/mlscript/newres/ShapeViews.mls b/hkmc2/shared/src/test/mlscript/newres/ShapeViews.mls new file mode 100644 index 0000000000..3f88cc6bdb --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ShapeViews.mls @@ -0,0 +1,156 @@ +:. + +// Returning a generic curried function captures outer binders without consuming +// its own scheme. Marks on its partial applications still distinguish callers. +:expect [1, 3, 5, 2, 4, 6] +class First(val first: Int) +class Second(val second: Int) +private fun enclosing[A](x: A) = + private fun pair[B](y: B)(z) = [x, y, z] + pair +private val left = enclosing(First(1)) +private val right = enclosing(Second(2)) +private val leftTail = left[Second](Second(3)) +private val rightTail = right[First](First(4)) +let a = leftTail(First(5)) +let b = rightTail(Second(6)) +[a.0.first, a.1.second, a.2.first, b.0.second, b.1.first, b.2.second] +//│ = [1, 3, 5, 2, 4, 6] +//│ a = [First(1), Second(3), First(5)] +//│ b = [Second(2), First(4), Second(6)] +//│ left = fun pair +//│ leftTail = fun +//│ right = fun pair +//│ rightTail = fun + +// A specialization and a consumed term list survive multiple nested captures. +:expect [7, 8, 9] +class First(val first: Int) +class Second(val second: Int) +private fun enclosing[A](x: A) = + private fun pair[B](y: B)(z) = [x, y, z] + private fun middle() = + private fun inner() = pair[Second](Second(8)) + inner() + middle() +private val tail = enclosing(First(7)) +let result = tail(First(9)) +[result.0.first, result.1.second, result.2.first] +//│ = [7, 8, 9] +//│ result = [First(7), Second(8), First(9)] +//│ tail = fun + +// Callback checking must retain the binder view of a partially applied function. +:expect 10 +class Item(val value: Int) +private fun first[A](x: A)(get) = get(x) +private fun use[B](f: (Item -> Int) -> B): B = f(x => x.value) +use(first[Item](Item(10))) +//│ = 10 + +// A partial new expression keeps its constructor parameters and captured marks. +:expect [11, 12, 13, 14] +class First(val first: Int) +class Second(val second: Int) +class Pair[A](val left: A)(val right) +private fun start[A](x: A) = new Pair[A](x) +private val left = start(First(11)) +private val right = start(Second(13)) +let a = left(Second(12)) +let b = right(First(14)) +[a.left.first, a.right.second, b.left.second, b.right.first] +//│ = [11, 12, 13, 14] +//│ a = Pair(First(11)) +//│ b = Pair(Second(13)) +//│ left = fun +//│ right = fun + +// Constructor patterns recognize an explicitly specialized constructor value. +:expect 15 +class Box[A](val value: A) +private val IntBox = Box[Int] +private fun read(x) = if x is IntBox(value) then value else 0 +read(Box(15)) +//│ = 15 +//│ IntBox = fun Box { class: class Box } + +// A captured constructor value is still available as a constructor pattern. +:expect 16 +class Box[A](val value: A) +private fun makeConstructor[A](unused: A) = Box +private val Alias = makeConstructor(0) +private fun read(x) = if x is Alias(value) then value else 0 +read(Box(16)) +//│ = 16 +//│ Alias = fun Box { class: class Box } + +// An application head identifies the original constructor, but an already +// constructed object is not itself a constructor pattern. +:e +class Box(val value: Int) +private val instance = Box(17) +private fun invalid(x) = if x is instance(value) then value else 0 +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.92: private fun invalid(x) = if x is instance(value) then value else 0 +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Box' cannot be used as a constructor pattern. +//│ ║ l.91: private val instance = Box(17) +//│ ╙── ^^^^^^^ +//│ instance = Box(17) + +// Nor does a partially applied constructor denote the original constructor. +:e +class Pair(val left: Int)(val right: Int) +private val partial = Pair(18) +private fun invalid(x) = if x is partial(left) then left else 0 +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.105: private fun invalid(x) = if x is partial(left) then left else 0 +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Pair' cannot be used as a constructor pattern. +//│ ║ l.104: private val partial = Pair(18) +//│ ╙── ^^^^^^^^ +//│ partial = fun + +// Explicit parent projections preserve an annotated receiver's binder view. +:expect [19, 20] +class First(val first: Int) +class Second(val second: Int) +class Parent[A](val item: A) +class Child[A](item: A) extends Parent[A](item) +private fun make[A](value: A): Child[A] = new Child[A](value) +private fun read[A](value: Child[A]) = value.Parent#item +[read(make[First](First(19))).first, read(make[Second](Second(20))).second] +//│ = [19, 20] + +// A nominal parent may itself refer to a captured enclosing type parameter. +// Disable the independent optimizer bug recorded in dead-param-elim/inherited-field.mls. +:deadParamElim off +:expect [21, 22] +class First(val first: Int) +class Second(val second: Int) +private fun enclosing[A](item: A) = + class Parent(val value: A) + class Child(value: A) extends Parent(value) + private fun nested() = + let receiver = new Child(item) as Child + receiver.Parent#value + nested() +[enclosing(First(21)).first, enclosing(Second(22)).second] +//│ = [21, 22] + +// A selected annotated method can cross nested captures before its remaining +// empty parameter list is consumed. Each receiver keeps its own type argument. +:expect [23, 24] +class First(val first: Int) +class Second(val second: Int) +class Holder[A](val item: A) with + fun keep[B](other: B)(): A = item +private fun capture[A](holder: Holder[A]) = + private fun nested() = holder.keep[Int](0) + nested() +private val first = capture(Holder[First](First(23))) +private val second = capture(Holder[Second](Second(24))) +[first().first, second().second] +//│ = [23, 24] +//│ first = fun +//│ second = fun From e69653d5c37850d3dfbf5e0d46c9cd7d7902ac64 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 10:32:04 +0800 Subject: [PATCH 136/228] Preserve reasons for unavailable type interfaces in diagnostics --- .../src/test/scala/hkmc2/PublisherTest.scala | 2 +- .../test/scala/hkmc2/TypeRelationTest.scala | 10 +- .../scala/hkmc2/semantics/Elaborator.scala | 8 +- .../scala/hkmc2/semantics/NewResolver.scala | 147 +++++++++------ .../main/scala/hkmc2/semantics/Shape.scala | 46 ++++- .../scala/hkmc2/semantics/TypeShape.scala | 11 +- .../src/test/mlscript/newres/SpreadCalls.mls | 47 +++-- .../mlscript/newres/TypeArgumentVariance.mls | 25 +-- .../newres/TypeInterfaceDiagnostics.mls | 174 ++++++++++++++++++ 9 files changed, 366 insertions(+), 104 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls diff --git a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala index 9ff8b30928..f289fc1962 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala @@ -130,7 +130,7 @@ class PublisherTest extends AnyFunSuite: def publish(shape: TermShape)(using NewResolverState): Unit = if variable.currentShapes.add(shape) then variable.notifyShapeListeners(shape) val before = DynShape() - val after = OpaqueTypeShape(Term.UnitVal()) + val after = OpaqueTypeShape(Term.UnitVal())(semantics.TypeInterfaceReason.Unavailable(Term.UnitVal())) publish(before) val originalListeners = use.shapeListeners.toVector state.completeBlock(use) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index fbcaf872a3..8c1c6481ac 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -22,7 +22,7 @@ class TypeRelationTest extends AnyFunSuite: def tpe(shape: TypeShape): DeclaredType = val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) resolution.publish(shape) - DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) + DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true)(N) def parameter(name: String): (VarSymbol, DeclaredType) = val symbol = VarSymbol(Tree.Ident(name)) (symbol, tpe(TypeShape.Parameter(symbol, symbol.inferenceHost))) @@ -218,7 +218,7 @@ class TypeRelationTest extends AnyFunSuite: val nextField = RcdField.signature(Term.Lit(Tree.StrLit("next")), next) val source: Term.Rcd = Term.Rcd(false, List(valueField, nextField)) resolution.publish(TypeShape.Record(source, List(valueField -> element.resolution, nextField -> resolution))) - DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) + DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true)(N) val left = ContextualType(recursiveRecord(at), Nil) val right = ContextualType(recursiveRecord(bt), Nil) val first = IntroShape(Term.UnitVal(), N) @@ -275,7 +275,7 @@ class TypeRelationTest extends AnyFunSuite: val (input, inputType) = h.parameter("Input") val (output, outputType) = h.parameter("Output") val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) - val argument = DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) + val argument = DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true)(N) val negative = h.resolver.selectArgument(argument, false) val positive = h.resolver.selectArgument(argument, true) val neg = ContextualType(negative, Nil) @@ -310,7 +310,7 @@ class TypeRelationTest extends AnyFunSuite: val h = new Harness import h.given val resolution = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) - val argument = DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) + val argument = DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true)(N) val selected = h.resolver.selectArgument(argument, true) val reference = ContextualType(selected, Nil) val seen = h.observe(reference) @@ -408,7 +408,7 @@ class TypeRelationTest extends AnyFunSuite: for target <- List(one, two); bound <- List(first, second) do assert(h.state.typeConstraints((target, bound, Nil))) val (b, bt) = h.parameter("B") - val distinct = OpaqueTypeShape(Term.UnitVal()) + val distinct = OpaqueTypeShape(Term.UnitVal())(TypeInterfaceReason.Unavailable(Term.UnitVal())) h.resolver.constrainTypes(ContextualType(bt, Nil), ContextualType(union, Nil)) h.resolver.publishParameter(b, distinct) assert(h.state.typeConstraints((union, distinct, Nil))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f080fd14b5..e795d977b2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1399,10 +1399,10 @@ extends Importer: case Trm | Receiver | Specialization => Specialization case _ => interp val result = Term.TyApp(subterm(lhs, baseInterp), targs.map { - case Modified(Keywrd(Keyword.`in`), arg) => Term.WildcardTy(S(subterm(arg, Tpe)), N) - case Modified(Keywrd(Keyword.`out`), arg) => Term.WildcardTy(N, S(subterm(arg, Tpe))) - case Tup(Modified(Keywrd(Keyword.`in`), arg1) :: Modified(Keywrd(Keyword.`out`), arg2) :: Nil) => - Term.WildcardTy(S(subterm(arg1, Tpe)), S(subterm(arg2, Tpe))) + case Modified(kw @ Keywrd(Keyword.`in`), arg) => Term.WildcardTy(S(subterm(arg, Tpe)), N).mkLocWith(kw) + case Modified(kw @ Keywrd(Keyword.`out`), arg) => Term.WildcardTy(N, S(subterm(arg, Tpe))).mkLocWith(kw) + case Tup(Modified(in @ Keywrd(Keyword.`in`), arg1) :: Modified(out @ Keywrd(Keyword.`out`), arg2) :: Nil) => + Term.WildcardTy(S(subterm(arg1, Tpe)), S(subterm(arg2, Tpe))).mkLocWith(in, out) case arg => subterm(arg, Tpe) })(N).withLocOf(tree) // A term specialization must validate its arguments even when unused. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index f9dad9090e..dbccef9ed8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -80,7 +80,7 @@ class NewResolver: result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) // Validate unused annotations too, once their forward source graph // is complete. Interface observation uses the same gate below. - withCanonicalType(DeclaredType(result, Map.empty, TypeSubstitution.empty, true))(_ => ()) + withCanonicalType(DeclaredType(result, Map.empty, TypeSubstitution.empty, true)(N))(_ => ()) case _ => result.publish(TypeShape.Abstract) symbol.defn match case S(defn) => definition(defn) @@ -412,7 +412,7 @@ class NewResolver: case TypeShape.Combined(formula) :: Nil => formula.atoms.flatMap(uses(_, wrapped, next, aliases)) case TypeShape.Contextual(reference) :: Nil => uses(reference.tpe, wrapped, next, aliases) case TypeShape.Wildcard(input, output) :: Nil => - input.toSet.flatMap(part => uses(declaredType(part, tpe.copy(positive = !tpe.positive)), wrapped, next, aliases)) ++ + input.toSet.flatMap(part => uses(declaredType(part, tpe.copy(positive = !tpe.positive)(tpe.origin)), wrapped, next, aliases)) ++ output.toSet.flatMap(part => uses(declaredType(part, tpe), wrapped, next, aliases)) case TypeShape.Argument(parts) :: Nil => uses(parts.input, wrapped, next, aliases) ++ uses(parts.output, wrapped, next, aliases) case TypeShape.SelectedArgument(argument, _) :: Nil => uses(argument, wrapped, next, aliases) @@ -422,7 +422,7 @@ class NewResolver: // the alias's substitution. The guard bounds body expansion; // substitution values are finite immutable reference DAGs. val substitutions = symbol.defn.get.tparams.zip(args).map: (formal, argument) => - formal.sym -> argumentType(declaredType(argument, tpe), formal.vce) + formal.sym -> argumentType(declaredType(argument, tpe), formal) uses(declaredType(rhs, tpe.bindings ++ substitutions, tpe.positive), wrapped, next, aliases + symbol) case S(_) => dependencies(tpe.resolution).flatMap(bound(_, wrapped)) case N => dependencies(tpe.resolution).flatMap(bound(_, true)) @@ -455,7 +455,7 @@ class NewResolver: def publish(binders: Set[VarSymbol])(using NewResolverState): Unit = if regularType(tpe.resolution) then listener(declaredType(tpe.resolution, tpe.bindings.filter((symbol, _) => binders(symbol)), tpe.positive) - .instantiate(tpe.instances)) + .instantiate(tpe.instances).withOrigin(tpe.origin)) typeDependencies(tpe.resolution) match case R(binders) => publish(binders) case L(_) => @@ -486,24 +486,24 @@ class NewResolver: val relevant = if bindings.isEmpty then bindings else typeDependencies(resolution) match case R(binders) => bindings.filter((symbol, _) => binders(symbol)) case L(_) => bindings - DeclaredType(resolution, relevant, TypeSubstitution.empty, positive) + DeclaredType(resolution, relevant, TypeSubstitution.empty, positive)(N) resolution.currentShapes.toList match case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) case TypeShape.Captured(base, thru) :: Nil => captureType(loop(base, bindings, aliases, positive), thru) case TypeShape.Union(left, right) :: Nil => - combinedType(resolution, typeFormula(loop(left, bindings, aliases, positive)).union(typeFormula(loop(right, bindings, aliases, positive)))) + combinedType(resolution, loop(left, bindings, aliases, positive), loop(right, bindings, aliases, positive), true) case TypeShape.Intersection(left, right) :: Nil => - combinedType(resolution, typeFormula(loop(left, bindings, aliases, positive)).intersection(typeFormula(loop(right, bindings, aliases, positive)))) + combinedType(resolution, loop(left, bindings, aliases, positive), loop(right, bindings, aliases, positive), false) case TypeShape.Wildcard(input, output) :: Nil => // Normalize both parts before retaining the argument. Otherwise even // out A keeps another A-to-wildcard environment at each recursive // step. Preserve the written source: it overrides declaration variance. val parts = TypeArgument(input.fold(extremeType(false, S(resolution)))(loop(_, bindings, aliases, !positive)), - output.fold(extremeType(true, S(resolution)))(loop(_, bindings, aliases, positive))) + output.fold(missingOutput(resolution))(loop(_, bindings, aliases, positive))) rstate.wildcardTypes.getOrElseUpdate((resolution, parts), { val normalized = new TypeResolution(resolution.source, resolution.fail) normalized.publish(TypeShape.Argument(parts)) - DeclaredType(normalized, Map.empty, TypeSubstitution.empty, true) + DeclaredType(normalized, Map.empty, TypeSubstitution.empty, true)(N) }) case TypeShape.Applied(base, arguments) :: Nil => def applyAlias(base: DeclaredType, supplied: TypeApplication, seen: Set[TypeResolution]): Opt[DeclaredType] = @@ -520,7 +520,7 @@ class NewResolver: // arguments to the template endpoint, substitute there, then // transport the result back. A wildcard exit followed by entry // is not cancelled; transportType retains its ordinary marks. - applyAlias(reference.tpe.instantiate(base.instances), + applyAlias(reference.tpe.instantiate(base.instances).withOrigin(base.origin), supplied.move(inverseMarks(reference.marks)), seen + base.resolution) .map(transportType(_, reference.marks)) case _ => N @@ -541,26 +541,36 @@ class NewResolver: // Different written occurrences of A are the same lattice atom. Limit // this canonicalization to combinations, so ordinary annotation and // storage references retain their precise diagnostic source location. - TypeFormula.atom(parameterType(parameter).copy(positive = tpe.positive).instantiate(tpe.instances)) + TypeFormula.atom(parameterType(parameter).copy(positive = tpe.positive)(tpe.origin).instantiate(tpe.instances)) case TypeShape.Combined(formula) :: Nil => formula.map(_.instantiate(tpe.instances)) case TypeShape.Contextual(reference) :: Nil => - typeFormula(reference.tpe.instantiate(tpe.instances)).map(transportType(_, reference.marks)) + typeFormula(reference.tpe.instantiate(tpe.instances).withOrigin(tpe.origin)).map(transportType(_, reference.marks)) case TypeShape.Top :: Nil => TypeFormula.top case TypeShape.Bottom :: Nil => TypeFormula.bottom case _ => TypeFormula.atom(tpe) - private def combinedType(source: TypeResolution, formula: TypeFormula[DeclaredType])(using NewResolverState): DeclaredType = + private def combinedType(source: TypeResolution, left: DeclaredType, right: DeclaredType, union: Bool) + (using NewResolverState): DeclaredType = + val lhs = typeFormula(left) + val rhs = typeFormula(right) + val formula = if union then lhs.union(rhs) else lhs.intersection(rhs) if formula.clauses.isEmpty then extremeType(false) - else if formula.clauses.contains(Set.empty) then extremeType(true) + else if formula.clauses.contains(Set.empty) then + // A top operand can explain a top result after Boolean normalization has + // erased its atom. Do not blame an opaque operand when a union/intersection + // instead simplifies to a more precise type that still has an interface. + val witness = (if lhs.clauses.contains(Set.empty) then left.origin else N) + .orElse(if rhs.clauses.contains(Set.empty) then right.origin else N) + extremeType(true).withOrigin(witness.orElse(S(TypeInterfaceReason.Unrestricted(source.source)))) else if formula.clauses.size == 1 && formula.atoms.size == 1 then formula.atoms.head else rstate.combinedTypes.getOrElseUpdate(formula, { val resolution = new TypeResolution(source.source, source.fail) resolution.publish(TypeShape.Combined(formula)) - DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true) + DeclaredType(resolution, Map.empty, TypeSubstitution.empty, true)(N) }) private def declaredType(resolution: TypeResolution, context: DeclaredType)(using NewResolverState): DeclaredType = - declaredType(resolution, context.bindings, context.positive).instantiate(context.instances) + declaredType(resolution, context.bindings, context.positive).instantiate(context.instances).withOrigin(context.origin) /** A substituted argument denotes one type at this occurrence. Freeze its * selected part before it becomes an invariant nominal argument; later input @@ -578,14 +588,15 @@ class NewResolver: argument.resolution.currentShapes.toList match case TypeShape.SelectedArgument(_, _) :: Nil => argument case TypeShape.Argument(parts) :: Nil => - (if positive then parts.output else parts.input).instantiate(argument.instances) + val selected = (if positive then parts.output else parts.input).instantiate(argument.instances) + if positive then selected.withOrigin(argument.origin) else selected case TypeShape.Wildcard(input, output) :: Nil => // Keep the wildcard's source for a missing part's diagnostics; the // shared extreme node itself has no source location. (if positive then output else input).fold(deferred): part => - declaredType(part, argument.copy(positive = if positive then argument.positive else !argument.positive)) + declaredType(part, argument.copy(positive = if positive then argument.positive else !argument.positive)(argument.origin)) case TypeShape.Contextual(reference) :: Nil => - transportType(selectArgument(reference.tpe.instantiate(argument.instances), positive), reference.marks) + transportType(selectArgument(reference.tpe.instantiate(argument.instances).withOrigin(argument.origin), positive), reference.marks) case Nil | TypeShape.Captured(_, _) :: Nil => deferred case _ => argument @@ -611,10 +622,14 @@ class NewResolver: declaredType(resolution, Map.empty) }) + private def missingOutput(source: TypeResolution)(using NewResolverState): DeclaredType = + extremeType(true, S(source)).withOrigin(S(TypeInterfaceReason.MissingOutput(source.source))) + /** Written wildcards override declaration variance. Unqualified arguments use * the declared variance; an invariant argument supplies both identical parts. */ - private def argumentType(tpe: DeclaredType, variance: Opt[Bool])(using NewResolverState): DeclaredType = + private def argumentType(tpe: DeclaredType, parameter: TyParam)(using NewResolverState): DeclaredType = + val variance = parameter.vce val selected = tpe.resolution.currentShapes.exists: case TypeShape.SelectedArgument(_, _) => true case _ => false @@ -623,27 +638,32 @@ class NewResolver: // Reusing a synthesized argument must select it first, so recursive // substitutions cannot accumulate repeated variance wrappers. tpe.resolution.currentShapes.toList match - case TypeShape.Argument(_) :: Nil => argumentType(selectArgument(tpe, tpe.positive), variance) - case _ => rstate.variantTypes.getOrElseUpdate((tpe, variance.get), { - val resolution = new TypeResolution(tpe.resolution.source, tpe.resolution.fail) - val parts = if variance.get then TypeArgument(extremeType(false), tpe) - else TypeArgument(tpe, extremeType(true)) - resolution.publish(TypeShape.Argument(parts)) - declaredType(resolution, Map.empty) - }) + case TypeShape.Argument(_) :: Nil => argumentType(selectArgument(tpe, tpe.positive), parameter) + case _ => + val variant = rstate.variantTypes.getOrElseUpdate((tpe, variance.get), { + val resolution = new TypeResolution(tpe.resolution.source, tpe.resolution.fail) + val parts = if variance.get then TypeArgument(extremeType(false), tpe) + else TypeArgument(tpe, extremeType(true)) + resolution.publish(TypeShape.Argument(parts)) + declaredType(resolution, Map.empty) + }) + // Different parameters can share the same semantic argument pair. + // Keep the declaration witness on this use, outside the interned pair. + if variance.get then variant else variant.withOrigin( + S(TypeInterfaceReason.ContravariantParameter(parameter, tpe.resolution.source))) private def listenArgumentParts(tpe: DeclaredType)(listener: ShapeListener[TypeArgument])(using NewResolverState): Unit = tpe.resolution.listen: case TypeShape.Wildcard(input, output) => - listener(TypeArgument(input.fold(extremeType(false))(declaredType(_, tpe.copy(positive = !tpe.positive))), - output.fold(extremeType(true))(declaredType(_, tpe)))) + listener(TypeArgument(input.fold(extremeType(false))(declaredType(_, tpe.copy(positive = !tpe.positive)(tpe.origin))), + output.fold(missingOutput(tpe.resolution))(declaredType(_, tpe)))) case TypeShape.Argument(parts) => - listener(TypeArgument(parts.input.instantiate(tpe.instances), parts.output.instantiate(tpe.instances))) + listener(TypeArgument(parts.input.instantiate(tpe.instances), parts.output.instantiate(tpe.instances).withOrigin(tpe.origin))) case TypeShape.Captured(base, thru) => listenArgumentParts(declaredType(base, tpe)): parts => listener(TypeArgument(captureType(parts.input, thru), captureType(parts.output, thru))) case TypeShape.Contextual(reference) => - listenArgumentParts(reference.tpe.instantiate(tpe.instances)): parts => + listenArgumentParts(reference.tpe.instantiate(tpe.instances).withOrigin(tpe.origin)): parts => listener(TypeArgument(transportType(parts.input, reference.marks), transportType(parts.output, reference.marks))) case TypeShape.Parameter(symbol, _) if tpe.bindings.contains(symbol) => val selected = selectArgument(tpe.bindings(symbol).instantiate(tpe.instances), tpe.positive) @@ -697,7 +717,7 @@ class NewResolver: private def typeBindings(source: DeclaredType, arguments: TypeApplication, parameters: Ls[TyParam]) (using NewResolverState): Map[VarSymbol, DeclaredType] = effectiveBindings(source) ++ parameters.zip(typeArguments(source, arguments, parameters)).map: (parameter, argument) => - parameter.sym -> argumentType(argument, parameter.vce) + parameter.sym -> argumentType(argument, parameter) /** External inputs contribute unknowns only to missing annotation parts. * A written binder still denotes its declared type, including after a partial @@ -721,7 +741,7 @@ class NewResolver: case TypeShape.Captured(base, thru) => follow(captureType(declaredType(base, current), thru), args, captures, next) case TypeShape.Contextual(reference) => - follow(reference.tpe.instantiate(current.instances), args.move(inverseMarks(reference.marks)), + follow(reference.tpe.instantiate(current.instances).withOrigin(current.origin), args.move(inverseMarks(reference.marks)), reference.marks ::: captures, next) case TypeShape.Applied(base, arguments) => follow(declaredType(base, current), TypeApplication(arguments.map(declaredType(_, current)), Nil), captures, next) @@ -729,17 +749,17 @@ class NewResolver: follow(declaredType(rhs, typeBindings(current, args, symbol.defn.get.tparams), current.positive), noTypeArguments, captures, next) case TypeShape.Nominal(cls) => cls.tparams.zip(typeArguments(current, args, cls.tparams)).foreach: (param, argument) => - listenArgumentParts(argumentType(argument, param.vce)): parts => + listenArgumentParts(argumentType(argument, param)): parts => exposeTypeHoles(transportType(parts.output, captures), unknown, marks) case TypeShape.Tuple(fields) => fields.foreach(field => descend(declaredType(field, current))) case TypeShape.Record(_, fields) => fields.foreach((_, field) => descend(declaredType(field, current))) case TypeShape.Function(_, result) => descend(declaredType(result, current)) case TypeShape.Polymorphic(parameters, _, body) => val symbols = parameters.map(_.parameter.symbol) - val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances.without(symbols)) + val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances.without(symbols))(current.origin) follow(declaredType(body, lexical), args, captures, next) case TypeShape.Wildcard(_, output) => output.foreach(res => follow(declaredType(res, current), args, captures, next)) - case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), args, captures, next) + case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances).withOrigin(current.origin), args, captures, next) case TypeShape.SelectedArgument(argument, positive) => listenArgumentParts(argument.instantiate(current.instances)): parts => follow(if positive then parts.output else parts.input, args, captures, next) @@ -841,12 +861,21 @@ class NewResolver: case source: CoreShape => ShapeParts(Marked(source, marks), TypeSubstitution.empty, N) private def listenTypeViews(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = + // Equal types can be observed through different input-only arguments. Reuse + // their semantic host, but report this observation's witness rather than the + // parameter declaration retained by the host's first subscriber. + def observe(shape: TermShape)(using NewResolverState): Unit = + val origin = tpe.origin.orElse: + if tpe.resolution.currentShapes.contains(TypeShape.Top) then S(TypeInterfaceReason.Unrestricted(tpe.resolution.source)) else N + (shape, origin) match + case (Marked(opaque: OpaqueTypeShape, marks), S(reason)) => listener(Marked(opaque.copy()(reason), marks)) + case _ => listener(shape) typeViews.get(tpe) match - case S(host) => host.listen(listener) + case S(host) => host.listen(observe) case N => val host = new TermShapeHost typeViews(tpe) = host - host.listen(listener) + host.listen(observe) def follow(current: DeclaredType, args: TypeApplication, aliases: Set[TypeResolution], publish: Listener)(using NewResolverState): Unit = current.resolution.listen: shape => def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = @@ -854,11 +883,12 @@ class NewResolver: def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), noTypeArguments, aliases, publish) shape match case TypeShape.Dynamic => publish(DynShape()) - case TypeShape.Top => publish(OpaqueTypeShape(tpe.resolution.source)) + case TypeShape.Top => publish(OpaqueTypeShape(tpe.resolution.source)( + current.origin.getOrElse(TypeInterfaceReason.Unrestricted(tpe.resolution.source)))) case TypeShape.Bottom => () case TypeShape.Wildcard(_, output) => - output.fold(publish(OpaqueTypeShape(tpe.resolution.source)))(next) - case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances), noTypeArguments, aliases, publish) + output.fold(publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.MissingOutput(current.resolution.source))))(next) + case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances).withOrigin(current.origin), noTypeArguments, aliases, publish) case TypeShape.SelectedArgument(argument, positive) => // An argument cycle can forward this selection without exposing // an interface. Keep the subscription for later parts, but stop @@ -882,11 +912,11 @@ class NewResolver: // Revisiting the same alias reference without reaching a structural // shape supplies no additional interface. Track // references, not symbols: Id[Id[T]] has two distinct references. - if aliases(current.resolution) then publish(OpaqueTypeShape(tpe.resolution.source)) + if aliases(current.resolution) then publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.RecursiveAlias(symbol))) else rhs match case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams), current.positive), noTypeArguments, aliases + current.resolution, publish) - case N => publish(OpaqueTypeShape(tpe.resolution.source)) + case N => publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.AbstractDeclaration(symbol))) case TypeShape.Applied(base, params) => follow(declaredType(base, current), TypeApplication(params.map(declaredType(_, current)), Nil), aliases, publish) case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match @@ -914,21 +944,21 @@ class NewResolver: // Arguments belong to the application scope, whereas the // template belongs to the reference's source scope. Move the // arguments back before substitution, then transport the result. - follow(reference.tpe.instantiate(current.instances), args.move(inverseMarks(reference.marks)), aliases, + follow(reference.tpe.instantiate(current.instances).withOrigin(current.origin), args.move(inverseMarks(reference.marks)), aliases, shape => shape.exit(reference.marks) match case value: TermShape => publish(value) case NoShape => ()) case TypeShape.Function(params, ret) => val ps = params match case Tup(fields) => DeclaredParams(fields.collect: - case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current.copy(positive = !current.positive))) + case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current.copy(positive = !current.positive)(current.origin))) , fields.exists(!_.isInstanceOf[Fld]), N) - case single => DeclaredParams(S(declaredType(typeResolution(single), current.copy(positive = !current.positive))) :: Nil, false, N) + case single => DeclaredParams(S(declaredType(typeResolution(single), current.copy(positive = !current.positive)(current.origin))) :: Nil, false, N) publish(CallableTypeShape(tpe.resolution.source, ps ne_:: Nil, S(declaredType(ret, current)), N, N, N)) case TypeShape.Polymorphic(params, _, body) => val symbols = params.map(_.parameter.symbol) - val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances.without(symbols)) + val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances.without(symbols))(current.origin) val binders = params.map: param => DeclaredTypeParameter(param.parameter, param.lower.map(declaredType(_, lexical)), @@ -947,7 +977,9 @@ class NewResolver: case TypeShape.Intersection(left, right) => next(left); next(right) case TypeShape.Combined(formula) => formula.orderedAtoms.foreach: atom => follow(atom.instantiate(current.instances), noTypeArguments, aliases, publish) - case TypeShape.Unit | TypeShape.Abstract | TypeShape.Negation(_) => publish(OpaqueTypeShape(tpe.resolution.source)) + case TypeShape.Negation(_) => publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.Negated(current.resolution.source))) + case TypeShape.Unit | TypeShape.Abstract => + publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.Unavailable(current.resolution.source))) withCanonicalType(tpe)(follow(_, noTypeArguments, Set.empty, host.publish)) // A separate full signature also annotates the implementation's parameters. @@ -981,7 +1013,7 @@ class NewResolver: */ private[semantics] def transportType(bound: DeclaredType, marks: Ls[Marks])(using NewResolverState): DeclaredType = val (base, previous) = bound.resolution.currentShapes.toList match - case TypeShape.Contextual(reference) :: Nil => (reference.tpe.instantiate(bound.instances), reference.marks) + case TypeShape.Contextual(reference) :: Nil => (reference.tpe.instantiate(bound.instances).withOrigin(bound.origin), reference.marks) case _ => (bound, Nil) softAssert(!base.resolution.currentShapes.exists: case TypeShape.Contextual(_) => true @@ -998,7 +1030,7 @@ class NewResolver: val resolution = new TypeResolution(base.resolution.source, base.resolution.fail) resolution.publish(TypeShape.Contextual(reference)) declaredType(resolution, Map.empty) - }) + }).withOrigin(base.origin) case NoShape => extremeType(false) private def inverseMarks(marks: Ls[Marks])(using NewResolverState): Ls[Marks] = @@ -1228,7 +1260,7 @@ class NewResolver: case TypeShape.Captured(base, thru) => follow(captureType(declaredType(base, tpe), thru), args, captures, next) case TypeShape.Contextual(reference) => - follow(reference.tpe.instantiate(tpe.instances), args.move(inverseMarks(reference.marks)), + follow(reference.tpe.instantiate(tpe.instances).withOrigin(tpe.origin), args.move(inverseMarks(reference.marks)), reference.marks ::: captures, next) case TypeShape.Applied(base, params) => follow(declaredType(base, tpe), TypeApplication(params.map(declaredType(_, tpe)), Nil), captures, next) @@ -1254,7 +1286,7 @@ class NewResolver: case TypeShape.Function(_, _) => observe(constrainFunction(tpe, _, marks)) case TypeShape.Polymorphic(_, _, body) => follow(declaredType(body, tpe), args, captures, next) - case TypeShape.Wildcard(input, _) => input.foreach(res => follow(declaredType(res, tpe.copy(positive = !tpe.positive)), args, captures, next)) + case TypeShape.Wildcard(input, _) => input.foreach(res => follow(declaredType(res, tpe.copy(positive = !tpe.positive)(tpe.origin)), args, captures, next)) case TypeShape.Argument(parts) => follow(parts.input.instantiate(tpe.instances), args, captures, next) case TypeShape.SelectedArgument(argument, positive) => listenArgumentParts(argument.instantiate(tpe.instances)): parts => @@ -1266,12 +1298,12 @@ class NewResolver: val (head, context) = actual.applicationHead def constrain(param: TyParam, bound: DeclaredType, pattern: DeclaredType)(using NewResolverState): Unit = listenArgumentParts(bound.instantiate(instances)): actual => - listenArgumentParts(argumentType(pattern, param.vce)): expected => + listenArgumentParts(argumentType(pattern, param)): expected => constrainTypes(ContextualType(actual.output, context), ContextualType(expected.output, Nil)) constrainTypes(ContextualType(expected.input, Nil), ContextualType(actual.input, context)) def originalParameters()(using NewResolverState): Unit = cls.tparams.zip(arguments).foreach: (param, pattern) => val bound = parameterType(TypeShape.Parameter(param.sym, param.sym.inferenceHost)) - constrain(param, argumentType(bound, param.vce), pattern) + constrain(param, argumentType(bound, param), pattern) def parent(ext: TermShape)(using NewResolverState): Unit = // Parent arguments live at the parent's endpoint, which can cross // both class and enclosing scopes. Preserve those marks together @@ -1282,7 +1314,7 @@ class NewResolver: head match case _: UnknownValueShape => cls.tparams.zip(arguments).foreach: (param, argument) => - listenArgumentParts(argumentType(argument, param.vce)): parts => + listenArgumentParts(argumentType(argument, param)): parts => inferTypeArguments(parts.output, value, marks) case ds: DefnShape => if ds.clsDef.contains(cls) then originalParameters() else ds.ext.foreach(parent) @@ -1893,7 +1925,7 @@ class NewResolver: applied.publish(TypeShape.Applied(base, cls.tparams.map(_ => arg))) listenTypeViews(declaredType(applied, Map.empty))(check(_, S(sh))) case Marked(opaque: OpaqueTypeShape, marks) => - UnknownValueShape.at(opaque.source).exit(marks) match + UnknownValueShape(opaque.source)(opaque.provenance).exit(marks) match case value: TermShape => narrow(value) case NoShape => () case Marked(nominal: NominalInstanceView, _) if !nominal.isInstanceOfClass(cls) => @@ -2096,6 +2128,7 @@ class NewResolver: msg"${lhs.describe.capitalize} cannot be called like a function." val notes = lhs.applicationHead._1 match case unknown: UnknownValueShape => unknown.provenance.diagnosticNotes + case opaque: OpaqueTypeShape => opaque.provenance.diagnosticNotes case _ => Nil resolError(res, (message -> lhs.toLoc) :: notes) if sh.isSaturated then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index a651746897..672f19f9c6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -541,13 +541,49 @@ final case class CallableTypeShape(source: Term, paramLists: NELs[DeclaredParams def toLoc: Opt[Loc] = declaration.fold(source.toLoc)(_.toLoc) protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = MemberLookup.Missing -/** An abstract annotation authorizes no operations based on the implementation. */ -final case class OpaqueTypeShape(source: Term) extends CoreHeadShape: - def describe: Str = "value of abstract type" +/** Why observing a type did not expose a member interface. Keep this evidence + * separate from semantic candidates: normalization can synthesize Any without + * making the written input bound an abstract type. Each reason retains a finite + * source witness, not a path that grows when recursive aliases are revisited. + */ +enum TypeInterfaceReason: + case MissingOutput(source: Term) + case ContravariantParameter(parameter: TyParam, source: Term) + case Unrestricted(source: Term) + case AbstractDeclaration(symbol: TypeAliasSymbol) + case RecursiveAlias(symbol: TypeAliasSymbol) + case Negated(source: Term) + case Unavailable(source: Term) + + def provenance: ShapeProvenance = ShapeProvenance(this match + case MissingOutput(source) => + (msg"This type argument specifies only an input bound." -> source.toLoc) :: + (msg"Values read through it have no output bound more specific than 'Any'." -> N) :: Nil + case ContravariantParameter(parameter, source) => + (msg"This type argument supplies only an input bound for '${parameter.sym.nme}'." -> source.toLoc) :: + (msg"Type parameter '${parameter.sym.nme}' is declared contravariant here." -> parameter.sym.toLoc) :: + (msg"Values read through it have no output bound more specific than 'Any'." -> N) :: Nil + case Unrestricted(source) => + msg"This type permits values of any type, so no member interface is guaranteed." -> source.toLoc :: Nil + case AbstractDeclaration(symbol) => + msg"Type '${symbol.nme}' is declared without a member interface." -> symbol.toLoc :: Nil + case RecursiveAlias(symbol) => + msg"Following type alias '${symbol.nme}' does not expose a member interface." -> symbol.toLoc :: Nil + case Negated(source) => + msg"This negated type does not specify a member interface." -> source.toLoc :: Nil + case Unavailable(source) => + msg"No member interface is known for this type." -> source.toLoc :: Nil + ) + +/** A type view that authorizes no member operations. The diagnostic reason is + * excluded from equality so alternative witnesses do not add inference flow. + */ +final case class OpaqueTypeShape(source: Term)(val reason: TypeInterfaceReason) extends CoreHeadShape: + def describe: Str = "value with no known interface" def toLoc: Opt[Loc] = source.toLoc + def provenance: ShapeProvenance = reason.provenance protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, - ShapeProvenance(msg"This abstract type does not specify a member interface." -> toLoc :: Nil)) + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, provenance) class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends CoreHeadShape: /** Instance lookup is shared by constructor calls and explicit `new`. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 063dde8d0e..0790bf3490 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -122,13 +122,20 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => * direction of a constraint subsequently applied to the interpreted type. */ final case class DeclaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], - instances: TypeSubstitution, positive: Bool): + instances: TypeSubstitution, positive: Bool)(val origin: Opt[TypeInterfaceReason]): + /** Diagnostic evidence is not part of type equality or cache keys. Retain one + * witness when normalizing/substituting the type; do not accumulate paths. + * A use's witness takes precedence over evidence in a shared cached reference. + */ + def withOrigin(witness: Opt[TypeInterfaceReason]): DeclaredType = + copy()(witness.orElse(origin)) + /** Instantiation changes references to source binders, never expands their bounds. * A reference already interpreted in another call retains that interpretation. * The finite map contains original binders and canonical site instances only. */ def instantiate(substitution: TypeSubstitution): DeclaredType = - copy(instances = substitution.withOverrides(instances)) + copy(instances = substitution.withOverrides(instances))(origin) /** A type reference observed from a common comparison scope. The marks belong * to this endpoint: reversing a constraint swaps endpoints, not an expanded diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index 2d12bf0850..d5789b17b7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -234,9 +234,13 @@ invokeUnknown(...opaque([() => 95])) //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.232: fun invokeUnknown(f) = f() //│ ║ ^^^ -//│ ╟── Value of abstract type cannot be called like a function. +//│ ╟── Value with no known interface cannot be called like a function. //│ ║ l.173: fun opaque(values): Array[Any] = values -//│ ╙── ^^^ +//│ ║ ^^^ +//│ ╟── Type 'Any' is declared without a member interface. +//│ ║ in ../decls/Prelude.mls +//│ ║ l.2: declare type Any +//│ ╙── ^^^ //│ = 95 // Unknown elements must not silently resolve against another known candidate. @@ -245,19 +249,20 @@ fun inspectUnknown(x) = x.value inspectUnknown(Box(96)) inspectUnknown(...opaque([Box(96)])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.244: fun inspectUnknown(x) = x.value +//│ ║ l.248: fun inspectUnknown(x) = x.value //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This abstract type does not specify a member interface. -//│ ║ l.173: fun opaque(values): Array[Any] = values -//│ ╙── ^^^ +//│ ╟── Type 'Any' is declared without a member interface. +//│ ║ in ../decls/Prelude.mls +//│ ║ l.2: declare type Any +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Too many known fields remain a proven mismatch even with an unknown spread. :e four(...opaque([]), 1, 2, 3, 4, 5) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.258: four(...opaque([]), 1, 2, 3, 4, 5) +//│ ║ l.263: four(...opaque([]), 1, 2, 3, 4, 5) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got at least 5 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -269,11 +274,15 @@ four(...opaque([]), 1, 2, 3, 4, 5) fun invokeRest(f, x, ...tail) = f() invokeRest(...opaque([() => 97]), Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.269: fun invokeRest(f, x, ...tail) = f() +//│ ║ l.274: fun invokeRest(f, x, ...tail) = f() //│ ║ ^^^ -//│ ╟── Value of abstract type cannot be called like a function. +//│ ╟── Value with no known interface cannot be called like a function. //│ ║ l.173: fun opaque(values): Array[Any] = values -//│ ╙── ^^^ +//│ ║ ^^^ +//│ ╟── Type 'Any' is declared without a member interface. +//│ ║ in ../decls/Prelude.mls +//│ ║ l.2: declare type Any +//│ ╙── ^^^ //│ = 97 // When repeatedTuples receives [Box(98)], both a and b must acquire its element @@ -307,16 +316,16 @@ invokeTypedRest(...callbacks, Box(0)) fun invokePublicRest(f, x, ...tail) = f() invokePublicRest(...callbacks, Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.307: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.316: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.307: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.316: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.307: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.316: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── 'invokePublicRest' is exposed by this compilation unit. -//│ ║ l.307: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.316: fun invokePublicRest(f, x, ...tail) = f() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 97 @@ -352,7 +361,7 @@ class Child extends Base with fun readBase(xs: Array[Base]) = first(...xs).extra readBase([new Child]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.352: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ║ l.361: fun readBase(xs: Array[Base]) = first(...xs).extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -391,16 +400,16 @@ invokeUnannotatedArray(...unknownCallbacks) private fun firstInArray(xs) = first(...xs) [firstInArray(lefts).left, firstInArray(rights).right] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.392: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.401: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Right' does not contain member 'left' -//│ ║ l.363: class Right(val right: Int) +//│ ║ l.372: class Right(val right: Int) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.392: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.401: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Left' does not contain member 'right' -//│ ║ l.362: class Left(val left: Int) +//│ ║ l.371: class Left(val left: Int) //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[43, 44]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls b/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls index bf252df8c0..d9a3cd36a4 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeArgumentVariance.mls @@ -18,9 +18,10 @@ xs.0.value //│ ║ l.16: xs.0.value //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This abstract type does not specify a member interface. +//│ ╟── This type argument specifies only an input bound. //│ ║ l.14: let xs = (mut [] as Array[in Item]) -//│ ╙── ^^^^ +//│ ║ ^^^^^^^ +//│ ╙── Values read through it have no output bound more specific than 'Any'. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Item(2)] @@ -32,7 +33,7 @@ let xs = mut [] connect(xs, Item(3)) xs.0.value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.33: xs.0.value +//│ ║ l.34: xs.0.value //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [] @@ -62,12 +63,13 @@ class Box[out T](val item: T) let box = (new Box(Item(6)) as Box[in Item]) box.item.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.63: box.item.value +//│ ║ l.64: box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This abstract type does not specify a member interface. -//│ ║ l.62: let box = (new Box(Item(6)) as Box[in Item]) -//│ ╙── ^^^^ +//│ ╟── This type argument specifies only an input bound. +//│ ║ l.63: let box = (new Box(Item(6)) as Box[in Item]) +//│ ║ ^^^^^^^ +//│ ╙── Values read through it have no output bound more specific than 'Any'. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Box(Item(6)) @@ -89,12 +91,13 @@ class Box[T](val item: T) with let box = (new Box(Item(8)) as Box[in Item]) box.apply(x => x.value) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.90: box.apply(x => x.value) +//│ ║ l.92: box.apply(x => x.value) //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This abstract type does not specify a member interface. -//│ ║ l.89: let box = (new Box(Item(8)) as Box[in Item]) -//│ ╙── ^^^^ +//│ ╟── This type argument specifies only an input bound. +//│ ║ l.91: let box = (new Box(Item(8)) as Box[in Item]) +//│ ║ ^^^^^^^ +//│ ╙── Values read through it have no output bound more specific than 'Any'. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Box(Item(8)) diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls new file mode 100644 index 0000000000..c3974a1472 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls @@ -0,0 +1,174 @@ +:. + +// Following an alias must retain the input wildcard's source witness. +:ge +class Item(val value: Int) +type Items[A] = Array[A] +let xs = (mut [] as Items[in Item]) +xs.0.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.8: xs.0.value +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This type argument specifies only an input bound. +//│ ║ l.7: let xs = (mut [] as Items[in Item]) +//│ ║ ^^^^^^^ +//│ ╙── Values read through it have no output bound more specific than 'Any'. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [] + +// Boolean normalization must retain why the output became unrestricted. +:ge +class Item(val value: Int) +type Items[A] = Array[A | Int] +let xs = (mut [] as Items[in Item]) +xs.0.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.25: xs.0.value +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This type argument specifies only an input bound. +//│ ║ l.24: let xs = (mut [] as Items[in Item]) +//│ ║ ^^^^^^^ +//│ ╙── Values read through it have no output bound more specific than 'Any'. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [] + +// A precise intersection must not inherit the discarded top operand's diagnostic. +:expect 3 +class Item(val value: Int) +type Items[A] = Array[A & Item] +let xs = (mut [Item(3)] as Items[in Item]) +xs.0.value +//│ = 3 +//│ xs = [Item(3)] + +// Declaration-site input variance identifies the responsible parameter. +:ge +class Item(val value: Int) +class Box[in T](val item: T) +let box = (new Box(Item(4)) as Box[Item]) +box.item.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.51: box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This type argument supplies only an input bound for 'T'. +//│ ║ l.50: let box = (new Box(Item(4)) as Box[Item]) +//│ ║ ^^^^ +//│ ╟── Type parameter 'T' is declared contravariant here. +//│ ║ l.49: class Box[in T](val item: T) +//│ ║ ^ +//│ ╙── Values read through it have no output bound more specific than 'Any'. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ box = Box(Item(4)) + +// Explicit Any does not become an unnamed abstract type in the diagnostic. +:ge +class Item(val value: Int) +let xs = (mut [Item(5)] as Array[Any]) +xs.0.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.70: xs.0.value +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Type 'Any' is declared without a member interface. +//│ ║ in ../decls/Prelude.mls +//│ ║ l.2: declare type Any +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ xs = [Item(5)] + +// An abstract declaration is identified by its name and declaration location. +:ge +declare type Hidden +fun inspect(value: Hidden) = value.member +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.85: fun inspect(value: Hidden) = value.member +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'member' of a value with unknown shape. +//│ ╟── Type 'Hidden' is declared without a member interface. +//│ ║ l.84: declare type Hidden +//│ ╙── ^^^^^^ + +// Unsupported singleton interfaces must not be described as abstract declarations. +:ge +fun inspect(value: 1) = value.member +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.96: fun inspect(value: 1) = value.member +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'member' of a value with unknown shape. +//│ ╟── No member interface is known for this type. +//│ ║ l.96: fun inspect(value: 1) = value.member +//│ ╙── ^ + +// Negation has a different explanation from an abstract declaration. +:ge +fun inspect(value: ~Int) = value.member +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.107: fun inspect(value: ~Int) = value.member +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'member' of a value with unknown shape. +//│ ╟── This negated type does not specify a member interface. +//│ ║ l.107: fun inspect(value: ~Int) = value.member +//│ ╙── ^^^ + +// Calling a value retains the same explanation as selecting its members. +:ge +class Item(val value: Int) +fun invoke(xs: Array[in Item]) = xs.0() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.119: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ ^^^^^^ +//│ ╟── Value with no known interface cannot be called like a function. +//│ ║ l.119: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ ^^^^^^^ +//│ ╟── This type argument specifies only an input bound. +//│ ║ l.119: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ ^^^^^^^ +//│ ╙── Values read through it have no output bound more specific than 'Any'. + +// Shared argument types must not attribute a different declaration's variance. +:ge +class Item(val value: Int) +class Left[in L](val item: L) +class Right[in R](val item: R) +type Pair[A] = [Left[A], Right[A]] +let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +pair.1.item.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.138: pair.1.item.value +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This type argument supplies only an input bound for 'R'. +//│ ║ l.137: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +//│ ║ ^^^^ +//│ ╟── Type parameter 'R' is declared contravariant here. +//│ ║ l.135: class Right[in R](val item: R) +//│ ║ ^ +//│ ╙── Values read through it have no output bound more specific than 'Any'. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ pair = [Left(Item(1)), Right(Item(2))] + +// Capturing and returning a typed value preserves the selected parameter's witness. +:ge +class Item(val value: Int) +class Left[in L](val item: L) +class Right[in R](val item: R) +type Pair[A] = [Left[A], Right[A]] +private fun delay[A](value: A) = () => value +let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +delay(pair)().1.item.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.161: delay(pair)().1.item.value +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This type argument supplies only an input bound for 'R'. +//│ ║ l.160: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +//│ ║ ^^^^ +//│ ╟── Type parameter 'R' is declared contravariant here. +//│ ║ l.157: class Right[in R](val item: R) +//│ ║ ^ +//│ ╙── Values read through it have no output bound more specific than 'Any'. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ pair = [Left(Item(1)), Right(Item(2))] From ecf8b74d5c1cd1996e2ce69af8ea56b7bc8562b2 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 16:26:02 +0800 Subject: [PATCH 137/228] Address type-interface diagnostic review comments --- .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../scala/hkmc2/semantics/NewResolver.scala | 14 +++-- .../main/scala/hkmc2/semantics/Shape.scala | 7 ++- .../src/test/mlscript/basics/StrTest.mls | 6 +- .../newres/RecursiveBooleanInterfaces.mls | 2 +- .../src/test/mlscript/newres/SpreadCalls.mls | 33 +++++++---- .../newres/TypeInterfaceDiagnostics.mls | 58 +++++++++++++------ .../src/test/mlscript/syntax/TypesInTerms.mls | 2 +- 8 files changed, 81 insertions(+), 43 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index e795d977b2..1854c39d4b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1499,7 +1499,7 @@ extends Importer: case App(Ident("!"), Tup(rhs :: Nil)) => Term.Deref(subterm(rhs)) case App(Ident("~"), Tup(rhs :: Nil)) => - Term.Neg(subterm(rhs, Tpe)) + Term.Neg(subterm(rhs, Tpe)).withLocOf(tree) case PrefixApp(Keywrd(Keyword.`|` | Keyword.`&`), rhs) => subterm(rhs, interp) case tree @ OpSplit(lhs, rhss) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index dbccef9ed8..a4a7889ae0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -630,26 +630,32 @@ class NewResolver: */ private def argumentType(tpe: DeclaredType, parameter: TyParam)(using NewResolverState): DeclaredType = val variance = parameter.vce + // SelectedArgument fixes one endpoint of a type argument. If the argument + // was a wildcard, its source survives for diagnostics even though it no + // longer denotes an input/output pair. Treating that retained syntax as a + // fresh use-site override would wrongly skip this parameter's variance. + // Only an unselected written wildcard overrides declaration-site variance. val selected = tpe.resolution.currentShapes.exists: case TypeShape.SelectedArgument(_, _) => true case _ => false if variance.isEmpty || (!selected && tpe.resolution.source.withoutCaptures.isInstanceOf[WildcardTy]) then tpe else + val varianceValue = variance.get // Apply declaration variance to the interpreted type, not another pair. // Reusing a synthesized argument must select it first, so recursive // substitutions cannot accumulate repeated variance wrappers. tpe.resolution.currentShapes.toList match case TypeShape.Argument(_) :: Nil => argumentType(selectArgument(tpe, tpe.positive), parameter) case _ => - val variant = rstate.variantTypes.getOrElseUpdate((tpe, variance.get), { + val variant = rstate.variantTypes.getOrElseUpdate((tpe, varianceValue), { val resolution = new TypeResolution(tpe.resolution.source, tpe.resolution.fail) - val parts = if variance.get then TypeArgument(extremeType(false), tpe) + val parts = if varianceValue then TypeArgument(extremeType(false), tpe) else TypeArgument(tpe, extremeType(true)) resolution.publish(TypeShape.Argument(parts)) declaredType(resolution, Map.empty) }) // Different parameters can share the same semantic argument pair. // Keep the declaration witness on this use, outside the interned pair. - if variance.get then variant else variant.withOrigin( + if varianceValue then variant else variant.withOrigin( S(TypeInterfaceReason.ContravariantParameter(parameter, tpe.resolution.source))) private def listenArgumentParts(tpe: DeclaredType)(listener: ShapeListener[TypeArgument])(using NewResolverState): Unit = @@ -916,7 +922,7 @@ class NewResolver: else rhs match case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams), current.positive), noTypeArguments, aliases + current.resolution, publish) - case N => publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.AbstractDeclaration(symbol))) + case N => publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.AbstractDeclaration(symbol, tpe.resolution.source))) case TypeShape.Applied(base, params) => follow(declaredType(base, current), TypeApplication(params.map(declaredType(_, current)), Nil), aliases, publish) case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 672f19f9c6..4ac2a0dded 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -550,7 +550,7 @@ enum TypeInterfaceReason: case MissingOutput(source: Term) case ContravariantParameter(parameter: TyParam, source: Term) case Unrestricted(source: Term) - case AbstractDeclaration(symbol: TypeAliasSymbol) + case AbstractDeclaration(symbol: TypeAliasSymbol, source: Term) case RecursiveAlias(symbol: TypeAliasSymbol) case Negated(source: Term) case Unavailable(source: Term) @@ -565,8 +565,9 @@ enum TypeInterfaceReason: (msg"Values read through it have no output bound more specific than 'Any'." -> N) :: Nil case Unrestricted(source) => msg"This type permits values of any type, so no member interface is guaranteed." -> source.toLoc :: Nil - case AbstractDeclaration(symbol) => - msg"Type '${symbol.nme}' is declared without a member interface." -> symbol.toLoc :: Nil + case AbstractDeclaration(symbol, source) => + (msg"This type annotation supplies the value's shape." -> source.toLoc) :: + (msg"Type '${symbol.nme}' is declared without a member interface." -> symbol.toLoc) :: Nil case RecursiveAlias(symbol) => msg"Following type alias '${symbol.nme}' does not expose a member interface." -> symbol.toLoc :: Nil case Negated(source) => diff --git a/hkmc2/shared/src/test/mlscript/basics/StrTest.mls b/hkmc2/shared/src/test/mlscript/basics/StrTest.mls index 1722f0d3da..9f7222e215 100644 --- a/hkmc2/shared/src/test/mlscript/basics/StrTest.mls +++ b/hkmc2/shared/src/test/mlscript/basics/StrTest.mls @@ -44,7 +44,7 @@ concat of ~"a" //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (negation type) //│ ║ l.44: ~"a" -//│ ╙── ^^^ +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -52,7 +52,7 @@ concat of ~("a", "b") //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (negation type) //│ ║ l.52: ~("a", "b") -//│ ╙── ^^^ +//│ ╙── ^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -67,7 +67,7 @@ concat of //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (negation type) //│ ║ l.61: ~ of "a", "b" -//│ ╙── ^^^ +//│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. (~) of "a", "b" diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls index 183a08b204..a735099f3f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls @@ -287,7 +287,7 @@ type Loop = First[Loop] type Loop = ~Loop //│ ╔══[COMPILATION ERROR] Resolution error in negation type //│ ║ l.287: type Loop = ~Loop -//│ ║ ^^^^ +//│ ║ ^^^^^ //│ ╟── Unguarded recursive type alias 'Loop'. //│ ║ l.287: type Loop = ~Loop //│ ║ ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index d5789b17b7..a95fcdfabc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -237,6 +237,9 @@ invokeUnknown(...opaque([() => 95])) //│ ╟── Value with no known interface cannot be called like a function. //│ ║ l.173: fun opaque(values): Array[Any] = values //│ ║ ^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.173: fun opaque(values): Array[Any] = values +//│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. //│ ║ in ../decls/Prelude.mls //│ ║ l.2: declare type Any @@ -249,9 +252,12 @@ fun inspectUnknown(x) = x.value inspectUnknown(Box(96)) inspectUnknown(...opaque([Box(96)])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.248: fun inspectUnknown(x) = x.value +//│ ║ l.251: fun inspectUnknown(x) = x.value //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.173: fun opaque(values): Array[Any] = values +//│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. //│ ║ in ../decls/Prelude.mls //│ ║ l.2: declare type Any @@ -262,7 +268,7 @@ inspectUnknown(...opaque([Box(96)])) :e four(...opaque([]), 1, 2, 3, 4, 5) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.263: four(...opaque([]), 1, 2, 3, 4, 5) +//│ ║ l.269: four(...opaque([]), 1, 2, 3, 4, 5) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got at least 5 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -274,11 +280,14 @@ four(...opaque([]), 1, 2, 3, 4, 5) fun invokeRest(f, x, ...tail) = f() invokeRest(...opaque([() => 97]), Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.274: fun invokeRest(f, x, ...tail) = f() +//│ ║ l.280: fun invokeRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value with no known interface cannot be called like a function. //│ ║ l.173: fun opaque(values): Array[Any] = values //│ ║ ^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.173: fun opaque(values): Array[Any] = values +//│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. //│ ║ in ../decls/Prelude.mls //│ ║ l.2: declare type Any @@ -316,16 +325,16 @@ invokeTypedRest(...callbacks, Box(0)) fun invokePublicRest(f, x, ...tail) = f() invokePublicRest(...callbacks, Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.316: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.325: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.316: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.325: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.316: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.325: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── 'invokePublicRest' is exposed by this compilation unit. -//│ ║ l.316: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.325: fun invokePublicRest(f, x, ...tail) = f() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 97 @@ -361,7 +370,7 @@ class Child extends Base with fun readBase(xs: Array[Base]) = first(...xs).extra readBase([new Child]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.361: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ║ l.370: fun readBase(xs: Array[Base]) = first(...xs).extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╙── Value of type 'Base' does not contain member 'extra' //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -400,16 +409,16 @@ invokeUnannotatedArray(...unknownCallbacks) private fun firstInArray(xs) = first(...xs) [firstInArray(lefts).left, firstInArray(rights).right] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.401: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.410: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Right' does not contain member 'left' -//│ ║ l.372: class Right(val right: Int) +//│ ║ l.381: class Right(val right: Int) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.401: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.410: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Left' does not contain member 'right' -//│ ║ l.371: class Left(val left: Int) +//│ ║ l.380: class Left(val left: Int) //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[43, 44]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls index c3974a1472..c71cd69c11 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls @@ -72,6 +72,9 @@ xs.0.value //│ ║ l.70: xs.0.value //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.69: let xs = (mut [Item(5)] as Array[Any]) +//│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. //│ ║ in ../decls/Prelude.mls //│ ║ l.2: declare type Any @@ -84,47 +87,50 @@ xs.0.value declare type Hidden fun inspect(value: Hidden) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.85: fun inspect(value: Hidden) = value.member +//│ ║ l.88: fun inspect(value: Hidden) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.88: fun inspect(value: Hidden) = value.member +//│ ║ ^^^^^^ //│ ╟── Type 'Hidden' is declared without a member interface. -//│ ║ l.84: declare type Hidden +//│ ║ l.87: declare type Hidden //│ ╙── ^^^^^^ // Unsupported singleton interfaces must not be described as abstract declarations. :ge fun inspect(value: 1) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.96: fun inspect(value: 1) = value.member -//│ ║ ^^^^^^^^^^^^ +//│ ║ l.102: fun inspect(value: 1) = value.member +//│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. //│ ╟── No member interface is known for this type. -//│ ║ l.96: fun inspect(value: 1) = value.member -//│ ╙── ^ +//│ ║ l.102: fun inspect(value: 1) = value.member +//│ ╙── ^ // Negation has a different explanation from an abstract declaration. :ge fun inspect(value: ~Int) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.107: fun inspect(value: ~Int) = value.member +//│ ║ l.113: fun inspect(value: ~Int) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. //│ ╟── This negated type does not specify a member interface. -//│ ║ l.107: fun inspect(value: ~Int) = value.member -//│ ╙── ^^^ +//│ ║ l.113: fun inspect(value: ~Int) = value.member +//│ ╙── ^^^^ // Calling a value retains the same explanation as selecting its members. :ge class Item(val value: Int) fun invoke(xs: Array[in Item]) = xs.0() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.119: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ l.125: fun invoke(xs: Array[in Item]) = xs.0() //│ ║ ^^^^^^ //│ ╟── Value with no known interface cannot be called like a function. -//│ ║ l.119: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ l.125: fun invoke(xs: Array[in Item]) = xs.0() //│ ║ ^^^^^^^ //│ ╟── This type argument specifies only an input bound. -//│ ║ l.119: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ l.125: fun invoke(xs: Array[in Item]) = xs.0() //│ ║ ^^^^^^^ //│ ╙── Values read through it have no output bound more specific than 'Any'. @@ -137,14 +143,14 @@ type Pair[A] = [Left[A], Right[A]] let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) pair.1.item.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.138: pair.1.item.value +//│ ║ l.144: pair.1.item.value //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This type argument supplies only an input bound for 'R'. -//│ ║ l.137: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +//│ ║ l.143: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) //│ ║ ^^^^ //│ ╟── Type parameter 'R' is declared contravariant here. -//│ ║ l.135: class Right[in R](val item: R) +//│ ║ l.141: class Right[in R](val item: R) //│ ║ ^ //│ ╙── Values read through it have no output bound more specific than 'Any'. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -160,15 +166,31 @@ private fun delay[A](value: A) = () => value let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) delay(pair)().1.item.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.161: delay(pair)().1.item.value +//│ ║ l.167: delay(pair)().1.item.value //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This type argument supplies only an input bound for 'R'. -//│ ║ l.160: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +//│ ║ l.166: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) //│ ║ ^^^^ //│ ╟── Type parameter 'R' is declared contravariant here. -//│ ║ l.157: class Right[in R](val item: R) +//│ ║ l.163: class Right[in R](val item: R) //│ ║ ^ //│ ╙── Values read through it have no output bound more specific than 'Any'. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ pair = [Left(Item(1)), Right(Item(2))] + +// An alias keeps the annotation use as well as the abstract declaration's location. +:ge +declare type Hidden +type Alias = Hidden +fun inspect(value: Alias) = value.member +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.186: fun inspect(value: Alias) = value.member +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'member' of a value with unknown shape. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.186: fun inspect(value: Alias) = value.member +//│ ║ ^^^^^ +//│ ╟── Type 'Hidden' is declared without a member interface. +//│ ║ l.184: declare type Hidden +//│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/syntax/TypesInTerms.mls b/hkmc2/shared/src/test/mlscript/syntax/TypesInTerms.mls index 2c0d0b680c..d7805d0340 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/TypesInTerms.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/TypesInTerms.mls @@ -19,7 +19,7 @@ print("A") & print("B") ~false //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (negation type) //│ ║ l.19: ~false -//│ ╙── ^^^^^ +//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 7e3f5f114cbdc45fa42c475890320b0046d9cfde Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 18:10:04 +0800 Subject: [PATCH 138/228] Resolve static array indexing to the element type Reapply 622a9246a from new-resolution-no-hashing independently of the candidate-identity optimizations. Preserve the static/dynamic syntax distinction so xs.[i] observes the array element interface while xs![i] remains dynamic. Include behavioral regressions and updated parser golden outputs. Validated with Metals, ctest, focused array diff tests, and hkmc2AllTests/test. --- doc/reference.md | 5 +++ .../main/scala/hkmc2/codegen/Lowering.scala | 4 +-- .../scala/hkmc2/semantics/Elaborator.scala | 8 ++--- .../scala/hkmc2/semantics/NewResolver.scala | 23 ++++++++++++- .../hkmc2/semantics/NewResolverState.scala | 2 ++ .../src/main/scala/hkmc2/semantics/Term.scala | 14 ++++---- .../src/main/scala/hkmc2/syntax/Parser.scala | 10 +++--- .../src/main/scala/hkmc2/syntax/Tree.scala | 9 +++--- .../test/mlscript/basics/DynamicSelection.mls | 17 +++++----- .../src/test/mlscript/basics/FunDefs.mls | 1 + .../src/test/mlscript/newres/Arrays.mls | 32 +++++++++++++++++++ 11 files changed, 95 insertions(+), 30 deletions(-) diff --git a/doc/reference.md b/doc/reference.md index 879daa9b20..2fa3afcd8f 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -691,10 +691,15 @@ Here `a: expression` is a field value, not a type annotation. mut [1, 2, 3] // mutable array xs.[0] // index access (uses .at() internally) +xs![0] // dynamic index access xs.(0) // dynamic index access set xs.[0] = v // index assignment ``` +Under new resolution, `xs.[i]` is checked like the array's methods: `xs` must be +an array, and the result has its element type. A dynamic access such as `xs![i]` +is only checked at runtime, and so are operations on its result. + **Spread syntax:** ```mlscript [1, ..xs, 4] // lazy spread — xs is expanded in the middle diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index e081e51f19..2a8d45ba89 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1275,7 +1275,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu subTerm(prefix): p => subTerm_nonTail(rhs): r => AssignField(p, memberIdent(nme, sel.sym), castTo(r, fieldErasedType(sym), sel.toLoc), k(unit))(sym) - case sel @ DynSel(prefix, fld, ai) => + case sel @ DynSel(prefix, fld, ai, _) => subTerm(prefix): p => subTerm_nonTail(fld): f => subTerm_nonTail(rhs): r => @@ -1346,7 +1346,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu subTerm(prefix): p => k(Select(p, definitionIdent(nme, sym))(S(sym))(false)) - case DynSel(prefix, fld, ai) => + case DynSel(prefix, fld, ai, _) => subTerm(prefix): p => subTerm_nonTail(fld): f => k(DynSelect(p, f, ai)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 1854c39d4b..9631b32fff 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1812,12 +1812,12 @@ extends Importer: error case OpenIn(op, body) => subterm(Block(Open(op) :: body :: Nil), interp) - case DynAccess(obj, rhs) => + case DynAccess(obj, rhs, dynamic) => rhs match - case Bra(bk @ (Round | Square), fld) => Term.DynSel(subterm(obj), subterm(fld), bk is Square) - case fld: Literal => Term.DynSel(subterm(obj), subterm(fld), false) + case Bra(bk @ (Round | Square), fld) => Term.DynSel(subterm(obj), subterm(fld), bk is Square, !dynamic) + case fld: Literal => Term.DynSel(subterm(obj), subterm(fld), false, false) case id: Ident => - Term.DynSel(subterm(obj), Term.Lit(StrLit(id.name)).withLocOf(id), false) + Term.DynSel(subterm(obj), Term.Lit(StrLit(id.name)).withLocOf(id), false, false) case _ => raise(ErrorReport(msg"Illegal dynamic field access selector (${rhs.describe})." -> tree.toLoc :: Nil)) error diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index a4a7889ae0..7382903345 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1600,6 +1600,25 @@ class NewResolver: case path: ExitMarks => exits(path) go(receiver) + /** A static index `prefix.[idx]` reads an element of an array. The index is + * not known statically, so every element is a candidate, as for a spread. + * Unlike a dynamic access `prefix![idx]`, it does not authorize dynamic + * operations on the result: only a dynamic array has dynamic elements. + */ + private def listenArrayIndex(sel: DynSel, prefix: Term)(listener: Listener)(using NewResolverState): Unit = + listenTermViews(prefix): receiver => + if !listenArrayElements(receiver)(listener) then receiver match + case Marked(_: DynShape, _) => listener(receiver) + case Marked(unknown: UnknownValueShape, marks) => + UnknownValueShape(sel)(unknown.provenance.via(msg"This array index has no known element shape." -> sel.toLoc)) + .exit(marks) match + case value: TermShape => listener(value) + case NoShape => () + case _ => + if rstate.arrayIndexErrors.add(new Identity(sel)) then + resolError(sel, (msg"${receiver.describe.capitalize} cannot be indexed statically." -> receiver.toLoc) :: + (msg"Use '![...]' for a dynamic access." -> N) :: Nil) + private[semantics] def arrayElementType(array: NominalInstanceView): Opt[DeclaredType] = val cls = prelude.builtins.Array.defn.get array.ancestor(cls).flatMap(_.bindings.get(cls.tparams.head.sym)) @@ -1611,7 +1630,8 @@ class NewResolver: private[semantics] def assignArrayElement(lhs: Term, rhs: Term)(using NewResolverState): Unit = val receiver = lhs match case NewSel(prefix, id, N) if id.name.toIntOption.exists(_ >= 0) => S(prefix) - case DynSel(prefix, _, true) => S(prefix) + // Dynamic writes also reach the elements that static indexing reads. + case DynSel(prefix, _, true, _) => S(prefix) case _ => N receiver.foreach: prefix => listenTermViews(prefix): array => @@ -2768,6 +2788,7 @@ class NewResolver: case _: SynthSel => lastWords("Synthetic selections must not enter new resolution") case Asc(_, sign) => listenTypeInstances(sign)(listener) + case sel @ DynSel(prefix, _, true, true) => listenArrayIndex(sel, prefix)(listener) case _: DynSel | _: DynNew => listener(DynShape()) case application @ TyApp(underlying, args) => def checkArity(callee: TermShape, count: Int)(using NewResolverState): Unit = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 19e0e712f9..6d5eab2396 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -332,6 +332,8 @@ final class NewResolverState private ( new Seen(inherited.map(_.typeRelations)) val typeArgumentArityErrors: Seen[(Identity[TyApp], Int)] = new Seen(inherited.map(_.typeArgumentArityErrors)) + val arrayIndexErrors: Seen[Identity[Term]] = + new Seen(inherited.map(_.arrayIndexErrors)) private[semantics] final class TermShapeHost extends Host[TermShape]: def showDbg(using DebugPrinter): Str = "instance views" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 414138de4e..ad0d8e3589 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -422,7 +422,9 @@ enum Term extends Statement, ShapePublisher: (val tree: Tree.App, val typ: Opt[Type], val resSym: FlowSymbol) extends Term, AppImpl case TyApp(lhs: Term, targs: Ls[Term]) (val typ: Opt[Type]) extends Term, ResolvableImpl - case DynSel(prefix: Term, fld: Term, arrayIdx: Bool) + /** A static selection (`obj.[idx]`) indexes an array whose element type + * resolution determines; other selections are only checked at runtime. */ + case DynSel(prefix: Term, fld: Term, arrayIdx: Bool, static: Bool) case Tup(fields: Ls[Elem])(val tree: Tree.Tup) extends Term, ShapeHost case Mut(underlying: Tup | Rcd | New | DynNew) case CtxTup(fields: Ls[Elem])(val tree: Tree.Tup) @@ -683,7 +685,7 @@ enum Term extends Statement, ShapePublisher: val copy = LeadingDotSel(Tree.Ident(nme.name))(term.originalCtx) copy.resolvedTargets = term.resolvedTargets copyResolution(term, copy) - case DynSel(prefix, fld, arrayIdx) => DynSel(prefix.mkClone, fld.mkClone, arrayIdx) + case DynSel(prefix, fld, arrayIdx, static) => DynSel(prefix.mkClone, fld.mkClone, arrayIdx, static) case term @ Tup(fields) => val copy = Tup(fields.map { case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) @@ -848,7 +850,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case NewSel(pre, nme, _) => "selection" case Sel(pre, nme) => "selection" case SynthSel(pre, nme) => "selection" - case DynSel(o, f, _) => "dynamic selection" + case DynSel(o, f, _, static) => if static then "array index" else "dynamic selection" case Tup(fields) => "tuple literal" case CtxTup(fields) => "contextual tuple literal" case IfLike(_, IfLikeForm.ReturningIf, body) => "`if` expression" @@ -924,7 +926,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case SynthSel(pre, _) => Vector.single(pre) case NewSel(pre, _, cls) => Vector.single(pre) ++ cls.toVector case UnresolvedRef(prefixes, _) => prefixes.toVector - case DynSel(o, f, _) => Vector.double(o, f) + case DynSel(o, f, _, _) => Vector.double(o, f) case Tup(fields) => fields.flatMap(_.subTerms).toVector case Mut(und) => Vector.single(und) case CtxTup(fields) => fields.flatMap(_.subTerms).toVector @@ -1100,7 +1102,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: // val fs = fomr match case Missing => doc"‹missing›" case TyApp(lhs, targs) => doc"${lhs.show}[${targs.map(_.show).mkDocument(", ")}]" - case DynSel(prefix, field, arrayIdx) => + case DynSel(prefix, field, arrayIdx, _) => if arrayIdx then doc"${prefix.show}.[${field.show}]" else doc"${prefix.show}.(${field.show})" case SelProj(prefix, cls, field) => doc"${prefix.show}.${cls.show}#${field.name}" @@ -1219,7 +1221,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" case NewSel(pre, nme, cls) => s"${pre.showDbg}.${cls.fold("")(c => s"${c.showDbg}#")}${nme.name}" case UnresolvedRef(_, id) => s"${id.name}‹open›" - case DynSel(pre, fld, _) => s"${pre.showDbg}[${fld.showDbg}]" + case DynSel(pre, fld, _, _) => s"${pre.showDbg}[${fld.showDbg}]" case IfLike(kw, _, split) => s"${kw.name} { ${split.showDbg} }" case SynthIf(split) => s"if { ${split.showDbg} }" case SynthWhile(split) => s"while { ${split.showDbg} }" diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index d19a2e192f..ee16e618cc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -938,13 +938,13 @@ abstract class Parser( consume consume val inner = rec(toks, S(br.innerLoc), br.describe).concludeWith(_.expr(0, allowNewlines = true)) - exprCont(DynAccess(acc, Bra(bk, inner)), prec, allowNewlines = allowNewlines) - // TODO: these should eventually no longer be treated as dynamic: + exprCont(DynAccess(acc, Bra(bk, inner), true), prec, allowNewlines = allowNewlines) + // `obj.[idx]` indexes an array; `obj.(fld)` should eventually no longer be dynamic either. case (PERIOD, l0) :: (br @ BRACKETS(bk @ (Round | Square), toks), l1) :: _ => consume consume val inner = rec(toks, S(br.innerLoc), br.describe).concludeWith(_.expr(0, allowNewlines = true)) - exprCont(DynAccess(acc, Bra(bk, inner)).withLoc(S(l0 ++ l1)), prec, allowNewlines = allowNewlines) + exprCont(DynAccess(acc, Bra(bk, inner), bk is Round).withLoc(S(l0 ++ l1)), prec, allowNewlines = allowNewlines) case (PERIOD, l0) :: (br @ BRACKETS(Curly, toks), l1) :: _ => consume @@ -955,7 +955,7 @@ abstract class Parser( case (PERIOD, l0) :: (LITVAL(lit: (Tree & Literal)), l1) :: _ => consume consume - exprCont(DynAccess(acc, lit.withLoc(S(l1))).withLoc(S(l0 ++ l1)), prec, allowNewlines = allowNewlines) + exprCont(DynAccess(acc, lit.withLoc(S(l1)), true).withLoc(S(l0 ++ l1)), prec, allowNewlines = allowNewlines) /* case (PERIOD, l0) :: (br @ BRACKETS(Square, toks), l1) :: _ => @@ -1058,7 +1058,7 @@ abstract class Parser( case (SELECT(name, dyn), l0) :: _ if SelPrec >= prec => consume val tree = if dyn then - DynAccess(acc, new Ident(name).withLoc(S(l0))) + DynAccess(acc, new Ident(name).withLoc(S(l0)), true) else Sel(acc, new Ident(name).withLoc(S(l0))) exprCont(tree, prec, allowNewlines = allowNewlines) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 65de846488..41745a1d7b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -79,7 +79,8 @@ enum Tree extends AutoLocated: extends Tree with TypeDefImpl case Open(opened: Tree) case OpenIn(opened: Tree, body: Tree) - case DynAccess(obj: Tree, fld: Tree) + /** `dynamic` is false only for `obj.[idx]`, an array index checked during resolution. */ + case DynAccess(obj: Tree, fld: Tree, dynamic: Bool) case Modified(modifier: Keywrd[Keyword.Modifier], body: Tree) case Quoted(body: Tree) case Unquoted(body: Tree) @@ -160,7 +161,7 @@ enum Tree extends AutoLocated: case TyTup(tys) => tys.toVector case Sel(prefix, name) => Vector.double(prefix, name) case SynthSel(prefix, name) => Vector.single(prefix) - case DynAccess(prefix, fld) => Vector.double(prefix, fld) + case DynAccess(prefix, fld, _) => Vector.double(prefix, fld) case Open(bod) => Vector.single(bod) case OpenIn(opened, body) => Vector.double(opened, body) case Def(lhs, rhs) => Vector.double(lhs, rhs) @@ -203,7 +204,7 @@ enum Tree extends AutoLocated: case Reft(base, reft) => "refinement" case Sel(prefix, name) => "selection" case SynthSel(prefix, name) => "synthetic selection" - case DynAccess(prefix, name) => "dynamic field access" + case DynAccess(prefix, name, _) => "dynamic field access" case PrefixApp(kw, body) => s"prefix operator '${kw.name}'" case InfixApp(lhs, kw, rhs) => s"infix operator '${kw.name}'" case LexicalNew(body, _) => "new" @@ -304,7 +305,7 @@ enum Tree extends AutoLocated: case N => this case N => this case Sel(pre, nme) if nme.name.startsWith("'") => - DynAccess(pre.desugared, StrLit(nme.name.drop(1)).withLocOf(nme)).withLocOf(this) + DynAccess(pre.desugared, StrLit(nme.name.drop(1)).withLocOf(nme), true).withLocOf(this) case Pun(false, id) => InfixApp(id, Keywrd(Keyword.`:`), id) diff --git a/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls b/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls index 77e1c3de8b..1ba278e4cf 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DynamicSelection.mls @@ -13,6 +13,7 @@ r!a //│ DynAccess: //│ obj = Ident of "r" //│ fld = Ident of "a" +//│ dynamic = true //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 @@ -26,10 +27,10 @@ r !a :fixme r! a //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.27: r! a +//│ ║ l.28: r! a //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Builtin '!' is not a binary operator -//│ ║ l.27: r! a +//│ ║ l.28: r! a //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -37,10 +38,10 @@ r! a :fixme r ! a //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.38: r ! a +//│ ║ l.39: r ! a //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Builtin '!' is not a binary operator -//│ ║ l.38: r ! a +//│ ║ l.39: r ! a //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -79,13 +80,13 @@ t.01 :fixme // * Strange that this is treated as a statement !(1,2) //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.80: !(1,2) +//│ ║ l.81: !(1,2) //│ ╙── ^ :ge (!)(1,2) //│ ╔══[COMPILATION ERROR] Builtin '!' is not a binary operator -//│ ║ l.86: (!)(1,2) +//│ ║ l.87: (!)(1,2) //│ ╙── ^^^^^^^^ //│ ═══[COMPILATION ERROR] Cannot call non-binary builtin symbol '!' //│ ═══[RUNTIME ERROR] Error: Cannot call non-binary builtin symbol '!' @@ -100,10 +101,10 @@ t.01 :fixme !1 //│ ╔══[PARSE ERROR] Expected an expression; found dynamic selector instead -//│ ║ l.101: !1 +//│ ║ l.102: !1 //│ ╙── ^^ //│ ╔══[PARSE ERROR] Expected end of input; found dynamic selector instead -//│ ║ l.101: !1 +//│ ║ l.102: !1 //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/FunDefs.mls b/hkmc2/shared/src/test/mlscript/basics/FunDefs.mls index 5735506001..b66958aead 100644 --- a/hkmc2/shared/src/test/mlscript/basics/FunDefs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/FunDefs.mls @@ -63,6 +63,7 @@ fun not(b) = !b //│ inner = Ident of "b" //│ rhs = S of Error //│ fld = Ident of "b" +//│ dynamic = true //│ ╔══[COMPILATION ERROR] Illegal definition in term position. //│ ║ l.51: fun not(b) = !b //│ ╙── ^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index aa3868851d..ddd7f07794 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -241,3 +241,35 @@ mk([1]) private fun mk(...args) = args.reduce((acc, x, ...) => if acc is Int then acc else mk(...acc), 0) mk(1, 2) //│ = 0 + +// A static index reads some element: its result has the element interface. +:expect [2, "b", 3, 4] +class Item(val value: Int) +fun get(xs: Array[Item], i: Int) = xs.[i].value +private fun last(xs) = xs.[xs.length - 1] +let items = mut [Item(4)] +[get([Item(1), Item(2)], 1), ["a", "b"].[1], last([Item(3)]).value, items.[0].value] +//│ = [2, "b", 3, 4] +//│ items = [Item(4)] + +// Only arrays can be indexed statically. +:e +:re +class Item(val value: Int) +(new Item(1)).[0] +//│ ╔══[COMPILATION ERROR] Resolution error in array index +//│ ║ l.259: (new Item(1)).[0] +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Item' cannot be indexed statically. +//│ ║ l.259: (new Item(1)).[0] +//│ ║ ^^^^^^^^^^^ +//│ ╙── Use '![...]' for a dynamic access. +//│ ═══[RUNTIME ERROR] TypeError: tmp48.at is not a function + +// A dynamic access authorizes dynamic operations on its result; a static index does not. +:e +fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.271: fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] +//│ ║ ^^^^^^^^^^^^^ +//│ ╙── Value of type 'Int' does not contain member 'whatever' From 0b30e20735697552d5be8827eb88223afe926115 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 18:10:22 +0800 Subject: [PATCH 139/228] Preserve array-spread substitutions and positional alternatives Reapply the correctness changes from a1e119347 and the deferred array-pattern binding from 3cd4fb714 without candidate identity caches, binder-support projection, or spread candidate coalescing. Unknown-length tuple segments now retain deferred element fields, so substitutions survive indexing, array interfaces, argument transfer, and rest patterns. Positional lookup includes suffix fields that can occupy the index when preceding spreads are empty. The imported behavioral regressions reproduced three rejected valid generic programs and two accepted invalid suffix selections on the original branch. BinderSupport regressions already passed without projection changes. Metals, ctest, focused diff tests, and hkmc2AllTests/test pass with the fixes; include the latest golden outputs. --- doc/new-resolution-design.md | 9 ++ .../hkmc2/semantics/InterfaceExposure.scala | 2 +- .../scala/hkmc2/semantics/NewResolver.scala | 71 +++++++-------- .../main/scala/hkmc2/semantics/Shape.scala | 47 ++++++---- .../test/mlscript/newres/ArraySpreadViews.mls | 86 +++++++++++++++++++ 5 files changed, 157 insertions(+), 58 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ArraySpreadViews.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 08624dcba4..94a2cf1786 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -146,6 +146,15 @@ shape; that does not authorize arbitrary member access on the result. Tuple-pattern transfer handles leading, trailing, and rest elements, including declared Array element types. See `newres/Arrays.mls` and `TuplePatternBindings.mls`. +An unknown-length tuple segment retains a deferred element field, just as a +fixed tuple position does. Tuple views instantiate both through the same field +representation, and indexing, array interfaces, argument transfer, and patterns +observe that field through `listenTupleField`. Unknown length does not erase the +element's type arguments or captured environment. A positional selection after +a spread includes every field that can occupy that position, including a fixed +suffix when the spread is empty; positions before a spread remain precise. +`newres/ArraySpreadViews.mls` covers these cases and distinct generic callers. + `DynShape` is distinct from `UnknownValueShape`: recursive widening, mutable reads, and other losses of precision do not license dynamic lookup or calls. Known invalid alternatives still report errors alongside dynamic candidates. Constructor patterns diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index d707b3a49e..521f227814 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -89,7 +89,7 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) val ref = SimpleRef(rest.sym)(rest.sym.id) val unknown = UnknownValueShape(ref)(ShapeProvenance( (msg"Rest parameter '${rest.sym.nme}' admits elements of unknown shape." -> rest.toLoc) :: path.diagnosticNotes)) - val tuple = TupleShape(ref, TupleShape.Unknown(ref, Nil, unknown) :: Nil)(resolver) + val tuple = TupleShape(ref, TupleShape.Unknown(ref, TupleShape.ValueField(unknown, Nil)) :: Nil)(resolver) resolver.constrainParameter(rest, tuple.enter(marks), marks) private def members(cls: ClassLikeDef, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 7382903345..3ed502aa43 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1281,11 +1281,12 @@ class NewResolver: case Marked(tuple: TupleShape, context) => fields.zipWithIndex.foreach: (field, index) => tuple.getMember(index.toString) match - case MemberLookup.Indexed(actual, inner) => listenTupleField(actual): shape => - shape.exit(inner).exit(context) match - case value: TermShape => - inferTypeArguments(declaredType(field, tpe), value, marks) - case NoShape => () + case MemberLookup.Indexed(actual, inner) => actual.foreach: element => + listenTupleField(element): shape => + shape.exit(inner).exit(context) match + case value: TermShape => + inferTypeArguments(declaredType(field, tpe), value, marks) + case NoShape => () case _ => () case _ => () case TypeShape.Record(_, fields) => observe(constrainRecord(fields, effectiveBindings(tpe), tpe.positive, _, marks)) @@ -1414,7 +1415,7 @@ class NewResolver: case S(tpe) => TupleShape.TypedField(tpe, Nil) case N => TupleShape.UnknownField(callable.source, Nil) val suffix = if declared.hasRest - then TupleShape.Unknown(callable.source, Nil, UnknownValueShape.at(callable.source)) :: Nil + then TupleShape.Unknown(callable.source, TupleShape.UnknownField(callable.source, Nil)) :: Nil else Nil val tuple = TupleShape(callable.source, fields ::: suffix)(this) constrainParameterIn(rest, tuple.exit(expectedContext).enter(context), context, instances) @@ -1481,12 +1482,9 @@ class NewResolver: val elements = new TypeResolution(tuple.source, messages => resolError(tuple.source, messages)) val parent = NominalInstanceView(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), implicitParent(cls))(N)(this) tupleArrayParents(new Identity(tuple)) = parent - tuple.segments.foreach: - case field: TupleShape.Fixed => listenTupleField(field): value => + tuple.segments.foreach: segment => + listenTupleField(segment.element): value => elements.publish(TypeShape.Inferred(value)) - case TupleShape.Unknown(_, marks, value) => value.exit(marks) match - case value: TermShape => elements.publish(TypeShape.Inferred(value)) - case NoShape => () parent /** Mutable literals share Array's existing element parameter, with an allocation @@ -1561,7 +1559,7 @@ class NewResolver: (name.toIntOption, arrayElementType(view)) match // An element's value keeps its whole path, including its provenance. case (S(index), S(element)) if index >= 0 => - MemberLookup.Indexed(TupleShape.TypedField(element, Nil), Nil).withMarks(receiver :: Nil) + MemberLookup.Indexed(TupleShape.TypedField(element, Nil) :: Nil, Nil).withMarks(receiver :: Nil) case _ => // The parent is referenced inside the class, like its own members' // annotations: `class Int extends Num` captures `Num` into Int's @@ -1729,11 +1727,8 @@ class NewResolver: val extra = (expectedCount - knownCount).max(0) def assign(segment: TupleShape.Segment, positions: Range)(using NewResolverState): Unit = positions.foreach: index => - segment match - case field: TupleShape.Fixed => listenTupleField(field): sh => - publish(index, sh.exit(marks).enter(mss)) - case TupleShape.Unknown(_, inner, value) => - publish(index, value.exit(inner).exit(marks).enter(mss)) + listenTupleField(segment.element): sh => + publish(index, sh.exit(marks).enter(mss)) def loop(rest: Ls[TupleShape.Segment], before: Int, unknownBefore: Bool)(using NewResolverState): Unit = rest match case Nil => () case (field: TupleShape.Fixed) :: tail => @@ -1767,7 +1762,7 @@ class NewResolver: if approximate then unknown :: Nil else Nil case (_: TupleShape.Unknown) :: rest => val suffix = drop(rest, count, false) - if approximate then TupleShape.Unknown(tuple.source, Nil, UnknownValueShape.at(tuple.source)) :: suffix else suffix + if approximate then TupleShape.Unknown(tuple.source, TupleShape.UnknownField(tuple.source, Nil)) :: suffix else suffix val remaining = drop(segments, expectedCount, true) val rest = if expectedCount == 0 then tuple else TupleShape.restView(tuple, remaining)(this) publish(expectedCount, rest.exit(marks).enter(mss)) @@ -1985,11 +1980,7 @@ class NewResolver: def receive(value: TermShape)(using NewResolverState): Unit = value.exit(marks) match case value: TermShape => matchShapePat(value, p)(_ => ()) case NoShape => () - segment match - case field: TupleShape.Fixed => listenTupleField(field)(receive) - case TupleShape.Unknown(_, inner, value) => value.exit(inner) match - case value: TermShape => receive(value) - case NoShape => () + listenTupleField(segment.element)(receive) // A spread can move subsequent fields. Retain every possible position // rather than treating the first unknown segment as the only candidate. def positions(xs: Ls[TupleShape.Segment], ps: Ls[Pattern])(using NewResolverState): Unit = @@ -2028,14 +2019,13 @@ class NewResolver: case Marked(tuple: TupleShape, marks) => tupleBindings(tuple, marks) case Marked(unknown: UnknownValueShape, marks) => tupleBindings(TupleShape(unknown.source, - TupleShape.Unknown(unknown.source, Nil, unknown) :: Nil)(this), marks) + TupleShape.Unknown(unknown.source, TupleShape.ValueField(unknown, Nil)) :: Nil)(this), marks) case Marked(nominal: NominalInstanceView, marks) => - nominal.ancestor(prelude.builtins.Array.defn.get).foreach: array => - array.bindings.get(prelude.builtins.Array.defn.get.tparams.head.sym).foreach: element => - listenTypeViews(element): - case Marked(shape, inner) => - tupleBindings(TupleShape(element.resolution.source, - TupleShape.Unknown(element.resolution.source, inner :: Nil, shape) :: Nil)(this), marks) + // Binding an element does not request its interface. Keep the declared + // type so later uses can apply the enclosing tuple's substitution. + arrayElementType(nominal).foreach: element => + tupleBindings(TupleShape(element.resolution.source, + TupleShape.Unknown(element.resolution.source, TupleShape.TypedField(element, Nil)) :: Nil)(this), marks) case _ => () shape match case value: TermShape => narrow(value) @@ -2359,15 +2349,16 @@ class NewResolver: case MemberLookup.Declared(member, bindings, marks, annotation, positive) if rstate.canResolve(sel) || sel.resolvedMembers.contains(member) => rstate.recordResolution(sel, sel.resolvedMembers.contains(member))(sel.resolvedMembers ::= member) publishDeclared(sel, member, sel.resSym, bindings, marks, annotation, positive, instances) - case MemberLookup.Indexed(field, marks) if rstate.canResolve(sel) || sel.tupleIndex == sel.id.name.toIntOption => + case MemberLookup.Indexed(fields, marks) if rstate.canResolve(sel) || sel.tupleIndex == sel.id.name.toIntOption => val index = sel.id.name.toIntOption softAssert(index.exists(_ >= 0), "Tuple lookup must identify a nonnegative index") rstate.recordResolution(sel, sel.tupleIndex == index)(sel.tupleIndex = index) - listenTupleField(field): shape => - transportShape(shape, marks) match - case value: TermShape => - publishViewed(sel, value, instances) - case NoShape => () + fields.foreach: field => + listenTupleField(field): shape => + transportShape(shape, marks) match + case value: TermShape => + publishViewed(sel, value, instances) + case NoShape => () case MemberLookup.Dynamic(marks) if rstate.canResolve(sel) || sel.hasDynamicTarget => rstate.recordResolution(sel, sel.hasDynamicTarget)(sel.hasDynamicTarget = true) publishDynamic(sel, marks) @@ -2903,17 +2894,19 @@ class NewResolver: else shape expand(rest, TupleShape.Spread(spread, marks) :: reversed) case Marked(_: DynShape, marks) => - val spread = TupleShape(term, TupleShape.Unknown(term, Nil, DynShape()) :: Nil)(this) + val spread = TupleShape(term, TupleShape.Unknown(term, TupleShape.ValueField(DynShape(), Nil)) :: Nil)(this) expand(rest, TupleShape.Spread(spread, marks) :: reversed) case shape @ Marked(_, marks) => val typed = listenArrayElements(shape): case Marked(element, context) => - val spread = TupleShape(term, TupleShape.Unknown(term, context :: Nil, element) :: Nil)(this) + val field = TupleShape.ValueField(element, context :: Nil) + val spread = TupleShape(term, TupleShape.Unknown(term, field) :: Nil)(this) expand(rest, TupleShape.Spread(spread, NoMarks) :: reversed) if !typed then // Other opaque iterables have no known element type. Their // runtime spread remains permitted without authorizing calls. - val unknown = TupleShape(term, TupleShape.Unknown(term, Nil, UnknownValueShape.spread(term, shape)) :: Nil)(this) + val field = TupleShape.ValueField(UnknownValueShape.spread(term, shape), Nil) + val unknown = TupleShape(term, TupleShape.Unknown(term, field) :: Nil)(this) expand(rest, TupleShape.Spread(unknown, marks) :: reversed) expand(tuple.fields, Nil) case record: Rcd => listenAggregate(record, listener): publish => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 4ac2a0dded..502bdf75a1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -306,7 +306,8 @@ enum MemberLookup: // Declared members expose signatures only. Their marks transport dependent // type arguments; they never authorize reading an implementation's value flow. case Declared(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term], positive: Bool) - case Indexed(field: TupleShape.Fixed, marks: Ls[Marks]) + // A preceding spread can place several different fields at the same index. + case Indexed(fields: Ls[TupleShape.Fixed], marks: Ls[Marks]) case Dynamic(marks: Ls[Marks]) case Missing case Unknown(reason: MemberLookup.Uncertainty, provenance: ShapeProvenance) @@ -315,7 +316,7 @@ enum MemberLookup: case Contextual(source, instances) => Contextual(source.withMarks(marks), instances) case Found(member, inner) => Found(member, inner ::: marks) case Declared(member, bindings, inner, annotation, positive) => Declared(member, bindings, inner ::: marks, annotation, positive) - case Indexed(field, inner) => Indexed(field, inner ::: marks) + case Indexed(fields, inner) => Indexed(fields, inner ::: marks) case Dynamic(inner) => Dynamic(inner ::: marks) case _ => this @@ -650,10 +651,10 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element], case TupleShape.Rest(_, segments) => segments case TupleShape.Spread(shape, marks) => shape.segments.map: case field: TupleShape.Fixed => field.withMarks(marks :: Nil) - case TupleShape.Unknown(source, inner, value) => TupleShape.Unknown(source, inner ::: marks :: Nil, value) + case TupleShape.Unknown(source, element) => TupleShape.Unknown(source, element.withMarks(marks :: Nil)) lazy val segments: Ls[TupleShape.Segment] = sourceSegments.map: case field: TupleShape.Fixed => field.instantiate(instances) - case other => other + case TupleShape.Unknown(source, element) => TupleShape.Unknown(source, element.instantiate(instances)) /** Does this candidate already depend on the given producer in the given context? * `source` identifies the producer by syntax-node identity; `marks` distinguish * its spread contexts. Inspect the selected dependency tree, not flattened @@ -674,15 +675,21 @@ final case class TupleShape(source: Term, elements: Ls[TupleShape.Element], def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = name.toIntOption match case S(index) if index >= 0 => - def loop(rest: Ls[TupleShape.Segment], index: Int): MemberLookup = rest match + // An unknown-length segment can be empty. After reaching one, every + // following field whose minimum position is at most `index` is possible. + // Keep an exact prefix precise, and stop once fixed fields alone have + // passed the index; later segments cannot move those fields backwards. + def loop(rest: Ls[TupleShape.Segment], index: Int, uncertain: Bool): Ls[TupleShape.Fixed] = rest match case (field: TupleShape.Fixed) :: tail => - if index == 0 then MemberLookup.Indexed(field, Nil) else loop(tail, index - 1) - case (segment: TupleShape.Unknown) :: _ => - // The position is a valid array operation even when a spread or - // mutation has erased the element layout. Its value remains unknown. - MemberLookup.Indexed(TupleShape.ValueField(segment.value, segment.marks), Nil) + if index == 0 then field :: Nil + else if uncertain then field :: loop(tail, index - 1, true) + else loop(tail, index - 1, false) + case (segment: TupleShape.Unknown) :: tail => + segment.element :: loop(tail, index, true) + case Nil => Nil + loop(segments, index, false) match case Nil => MemberLookup.Missing - loop(segments, index) + case fields => MemberLookup.Indexed(fields, Nil) case _ => arrayParent.getMember(name) override def getMemberThrough(name: Str, receiver: Marks)(using NewResolverState): MemberLookup = name.toIntOption match case S(index) if index >= 0 => super.getMemberThrough(name, receiver) @@ -747,8 +754,11 @@ object TupleShape: def apply(source: Term, elements: Ls[Element])(resolver: NewResolver): TupleShape = new TupleShape(source, elements, TypeSubstitution.empty)(resolver) sealed trait Element - sealed trait Segment extends Element + sealed trait Segment extends Element: + /** The deferred value of one element, independently of the segment's length. */ + def element: Fixed sealed trait Fixed extends Segment: + final def element: Fixed = this def marks: Ls[Marks] def withMarks(outer: Ls[Marks]): Fixed = this match case Field(field, inner) => Field(field, inner ::: outer) @@ -767,14 +777,15 @@ object TupleShape: // Mutable tuple slots retain their positions, but their initializer cannot // supply the shape of later reads (including reads through a spread copy). final case class UnknownField(source: Term, marks: Ls[Marks]) extends Fixed - /** An arbitrary number of arbitrary values. Use this for opaque layouts and - * recursive widening, never for a spread whose shape has not arrived yet. - * `value` distinguishes dynamically typed JS elements from values whose - * shape was lost through widening; `source` supplies diagnostic locations. */ - final case class Unknown(source: Term, marks: Ls[Marks], value: NonMarkedShape) extends Segment + /** An unknown number of elements described by the same deferred field as a + * fixed position. Its marks and retained substitution must survive tuple + * views, rest slicing, and spreading, even when its length is unknown. + * Use this for typed arrays, opaque layouts, and recursive widening, never + * for a spread whose shape has not arrived yet. */ + final case class Unknown(source: Term, element: Fixed) extends Segment final case class Spread(shape: TupleShape, marks: Marks) extends Element def unknown(source: Term)(resolver: NewResolver): TupleShape = - TupleShape(source, Unknown(source, Nil, UnknownValueShape.at(source)) :: Nil)(resolver) + TupleShape(source, Unknown(source, UnknownField(source, Nil)) :: Nil)(resolver) /** Retain the original candidate as well as the selected residual segments: * flattening away the parent would hide recursive producer dependencies from * containsSpread, allowing recursion through rest slicing to evade widening. */ diff --git a/hkmc2/shared/src/test/mlscript/newres/ArraySpreadViews.mls b/hkmc2/shared/src/test/mlscript/newres/ArraySpreadViews.mls new file mode 100644 index 0000000000..c92ce75405 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ArraySpreadViews.mls @@ -0,0 +1,86 @@ +:. + +// Unknown-length segments retain type arguments just like fixed tuple fields. +:expect [1, 2] +class First(val first: Int) +class Second(val second: Int) +private fun copy[A](xs: Array[A]) = [...xs] +[copy([First(1)]).0.first, copy([Second(2)]).0.second] +//│ = [1, 2] + +// Spreading a declared array keeps separate callers' interfaces distinct. +:expect [3, 4] +class First(val first: Int) +class Second(val second: Int) +fun first(): Array[First] = [First(3)] +fun second(): Array[Second] = [Second(4)] +private fun copy(xs) = [...xs] +[copy(first()).0.first, copy(second()).0.second] +//│ = [3, 4] + +// Array patterns observe the same declared element type without expanding it early. +:expect [5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun head[A](xs: Array[A]) = if xs is [x, ...rest] then x else throw "empty" +[head([First(5)]).first, head([Second(6)]).second] +//│ = [5, 6] + +// A static array index also sees substitutions through an enclosing generic call. +:expect [7, 8] +class First(val first: Int) +class Second(val second: Int) +private fun copy[A](xs: Array[A]) = [...xs] +private fun wrap[B](x: B) = copy([x]).[0] +[wrap(First(7)).first, wrap(Second(8)).second] +//│ = [7, 8] + +// A spread can be empty: the explicit suffix can occupy index zero too. +:e +class First(val first: Int) +class Second(val second: Int) +fun pick(xs: Array[First]) = [...xs, Second(2)].0.first +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.42: fun pick(xs: Array[First]) = [...xs, Second(2)].0.first +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.42: fun pick(xs: Array[First]) = [...xs, Second(2)].0.first +//│ ╙── ^^^^^^^^^ + +// A fixed prefix remains precise even when a later spread has a different type. +:expect 9 +class First(val first: Int) +class Second(val second: Int) +private fun pick[A](x: A, xs: Array[Second]) = [x, ...xs].0 +pick(First(9), [Second(10)]).first +//│ = 9 + +// Rest slicing and spreading into a call preserve the deferred element's view. +:expect [11, 12] +class First(val first: Int) +class Second(val second: Int) +private fun prepend[A](xs: Array[A]) = [0, ...xs] +private fun tail[B](x: B) = if prepend([x]) is [_, ...rest] then rest else throw "empty" +private fun head(x) = x +[head(...tail(First(11))).first, head(...tail(Second(12))).second] +//│ = [11, 12] + +// Multiple unknown-length segments can move either an element or the suffix here. +:e +class First(val first: Int) +class Second(val second: Int) +fun pick(xs: Array[First], ys: Array[First]) = [0, ...xs, ...ys, Second(2)].1.first +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.72: fun pick(xs: Array[First], ys: Array[First]) = [0, ...xs, ...ys, Second(2)].1.first +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Second' does not contain member 'first' +//│ ║ l.72: fun pick(xs: Array[First], ys: Array[First]) = [0, ...xs, ...ys, Second(2)].1.first +//│ ╙── ^^^^^^^^^ + +// Fields whose minimum position is past the selected index cannot contribute. +:expect 13 +class First(val first: Int) +class Second(val second: Int) +private fun pick(xs: Array[First]) = [...xs, First(13), Second(14)].0.first +pick([]) +//│ = 13 From e0b3d7709515c1aa4731314a00f60333975f5c25 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 18:22:33 +0800 Subject: [PATCH 140/228] Name the array resolution flag checked instead of static The flag requests receiver validation and element-interface resolution; the index value is evaluated at runtime. Update the comments and diagnostic wording, including its golden output. Metals compilation, ctest, and hkmc2AllTests/test pass. --- .../src/main/scala/hkmc2/semantics/NewResolver.scala | 8 ++++---- .../shared/src/main/scala/hkmc2/semantics/Term.scala | 11 ++++++----- .../src/test/mlscript/newres/ArraySpreadViews.mls | 2 +- hkmc2/shared/src/test/mlscript/newres/Arrays.mls | 8 ++++---- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 3ed502aa43..f1315c786d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1598,8 +1598,8 @@ class NewResolver: case path: ExitMarks => exits(path) go(receiver) - /** A static index `prefix.[idx]` reads an element of an array. The index is - * not known statically, so every element is a candidate, as for a spread. + /** A checked index `prefix.[idx]` reads an element of an array. The index is + * evaluated at runtime, so every element is a candidate, as for a spread. * Unlike a dynamic access `prefix![idx]`, it does not authorize dynamic * operations on the result: only a dynamic array has dynamic elements. */ @@ -1614,7 +1614,7 @@ class NewResolver: case NoShape => () case _ => if rstate.arrayIndexErrors.add(new Identity(sel)) then - resolError(sel, (msg"${receiver.describe.capitalize} cannot be indexed statically." -> receiver.toLoc) :: + resolError(sel, (msg"${receiver.describe.capitalize} does not support checked array indexing." -> receiver.toLoc) :: (msg"Use '![...]' for a dynamic access." -> N) :: Nil) private[semantics] def arrayElementType(array: NominalInstanceView): Opt[DeclaredType] = @@ -1628,7 +1628,7 @@ class NewResolver: private[semantics] def assignArrayElement(lhs: Term, rhs: Term)(using NewResolverState): Unit = val receiver = lhs match case NewSel(prefix, id, N) if id.name.toIntOption.exists(_ >= 0) => S(prefix) - // Dynamic writes also reach the elements that static indexing reads. + // Dynamic writes also reach the elements that checked indexing reads. case DynSel(prefix, _, true, _) => S(prefix) case _ => N receiver.foreach: prefix => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index ad0d8e3589..edb98f6edb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -422,9 +422,10 @@ enum Term extends Statement, ShapePublisher: (val tree: Tree.App, val typ: Opt[Type], val resSym: FlowSymbol) extends Term, AppImpl case TyApp(lhs: Term, targs: Ls[Term]) (val typ: Opt[Type]) extends Term, ResolvableImpl - /** A static selection (`obj.[idx]`) indexes an array whose element type - * resolution determines; other selections are only checked at runtime. */ - case DynSel(prefix: Term, fld: Term, arrayIdx: Bool, static: Bool) + /** `checked` asks resolution to validate the receiver and determine the element + * interface (`obj.[idx]`); it does not require a compile-time index value. + * `arrayIdx` independently selects array indexing in the generated code. */ + case DynSel(prefix: Term, fld: Term, arrayIdx: Bool, checked: Bool) case Tup(fields: Ls[Elem])(val tree: Tree.Tup) extends Term, ShapeHost case Mut(underlying: Tup | Rcd | New | DynNew) case CtxTup(fields: Ls[Elem])(val tree: Tree.Tup) @@ -685,7 +686,7 @@ enum Term extends Statement, ShapePublisher: val copy = LeadingDotSel(Tree.Ident(nme.name))(term.originalCtx) copy.resolvedTargets = term.resolvedTargets copyResolution(term, copy) - case DynSel(prefix, fld, arrayIdx, static) => DynSel(prefix.mkClone, fld.mkClone, arrayIdx, static) + case DynSel(prefix, fld, arrayIdx, checked) => DynSel(prefix.mkClone, fld.mkClone, arrayIdx, checked) case term @ Tup(fields) => val copy = Tup(fields.map { case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) @@ -850,7 +851,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case NewSel(pre, nme, _) => "selection" case Sel(pre, nme) => "selection" case SynthSel(pre, nme) => "selection" - case DynSel(o, f, _, static) => if static then "array index" else "dynamic selection" + case DynSel(o, f, _, checked) => if checked then "array index" else "dynamic selection" case Tup(fields) => "tuple literal" case CtxTup(fields) => "contextual tuple literal" case IfLike(_, IfLikeForm.ReturningIf, body) => "`if` expression" diff --git a/hkmc2/shared/src/test/mlscript/newres/ArraySpreadViews.mls b/hkmc2/shared/src/test/mlscript/newres/ArraySpreadViews.mls index c92ce75405..0ec811f09c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ArraySpreadViews.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ArraySpreadViews.mls @@ -26,7 +26,7 @@ private fun head[A](xs: Array[A]) = if xs is [x, ...rest] then x else throw "emp [head([First(5)]).first, head([Second(6)]).second] //│ = [5, 6] -// A static array index also sees substitutions through an enclosing generic call. +// A checked array index also sees substitutions through an enclosing generic call. :expect [7, 8] class First(val first: Int) class Second(val second: Int) diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index ddd7f07794..9e94d94361 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -242,7 +242,7 @@ private fun mk(...args) = args.reduce((acc, x, ...) => if acc is Int then acc el mk(1, 2) //│ = 0 -// A static index reads some element: its result has the element interface. +// A checked index reads some element: its result has the element interface. :expect [2, "b", 3, 4] class Item(val value: Int) fun get(xs: Array[Item], i: Int) = xs.[i].value @@ -252,7 +252,7 @@ let items = mut [Item(4)] //│ = [2, "b", 3, 4] //│ items = [Item(4)] -// Only arrays can be indexed statically. +// Checked indexing requires an array receiver. :e :re class Item(val value: Int) @@ -260,13 +260,13 @@ class Item(val value: Int) //│ ╔══[COMPILATION ERROR] Resolution error in array index //│ ║ l.259: (new Item(1)).[0] //│ ║ ^^^^^^^^^^^^^^^ -//│ ╟── Instance of class 'Item' cannot be indexed statically. +//│ ╟── Instance of class 'Item' does not support checked array indexing. //│ ║ l.259: (new Item(1)).[0] //│ ║ ^^^^^^^^^^^ //│ ╙── Use '![...]' for a dynamic access. //│ ═══[RUNTIME ERROR] TypeError: tmp48.at is not a function -// A dynamic access authorizes dynamic operations on its result; a static index does not. +// A dynamic access authorizes dynamic operations on its result; a checked index does not. :e fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] //│ ╔══[COMPILATION ERROR] Resolution error in selection From ecd90375f8d88318c9d1655e7276ba074ebb995e Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 29 Sep 2026 18:25:37 +0800 Subject: [PATCH 141/228] Minor cleanup --- .../src/main/scala/hkmc2/codegen/js/JSBuilder.scala | 9 +-------- .../shared/src/main/scala/hkmc2/semantics/Pattern.scala | 3 --- 2 files changed, 1 insertion(+), 11 deletions(-) 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 58cb558c65..1a1a8ac695 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -546,7 +546,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case AssignDynField(p, f, ai, r, rst) => doc" # ${result(p)}[${result(f)}] = ${result(r)};${returningTerm(rst, endSemi)}" case Define(defn, rst) => - def mkThis(sym: InnerSymbol): Document = + inline def mkThis(sym: InnerSymbol): Document = result(sym.asThis) val resJS = defn match case ValDefn(tsym, sym, p) => @@ -556,13 +556,6 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case N => doc"${scope.lookup_!(sym, sym.toLoc)} = ${result(p)};${returningTerm(rst, endSemi)}" case S(owner) => - - - - - - - val thisDoc = mkThis(owner) val nme = sym.nme owner match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index d6134c03ac..3519cb9a8a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -307,9 +307,6 @@ enum Pattern extends AutoLocated, Describable, PatternShapePublisher: .foldLeft(Set.empty[Str])(_ ++ _) def show(using Scope, ShowCfg, Raise): Document = this match - // case Constructor(target, arguments) => - // target.show + arguments.fold(""): - // args => s"(${args.map(_.show).mkString(", ")})" case Composition(true, left, right) => doc"${left.show} | ${right.show}" case Composition(false, left, right) => doc"${left.show} & ${right.show}" case Negation(pattern) => doc"not ${pattern.show}" From a220b0271d80b59d4897e7a0a408c322170f3815 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 20:52:16 +0800 Subject: [PATCH 142/228] Remove machine-specific paths from tooling configuration --- .codex/config.toml | 6 ++++-- hkmc2/shared/src/test/mlscript-compile/cpp/Makefile | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.codex/config.toml b/.codex/config.toml index 32817e2aa6..a2e3c7e5ee 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -1,5 +1,7 @@ [mcp_servers.metals] -command = "/opt/homebrew/bin/cs" -args = ["launch", "org.scalameta:metals-mcp_2.13:1.6.9", "--", "--workspace", "/Users/parreaux/work/Research/code/mlscript", "--transport", "stdio"] +# Coursier (cs) and Git must be available on Codex's PATH. +# Resolve the checkout at launch time, including when Codex starts in a subdirectory. +command = "sh" +args = ["-c", 'workspace=$(git rev-parse --show-toplevel) && exec cs launch org.scalameta:metals-mcp_2.13:1.6.9 -- --workspace "$workspace" --transport stdio'] startup_timeout_sec = 180 tool_timeout_sec = 600 diff --git a/hkmc2/shared/src/test/mlscript-compile/cpp/Makefile b/hkmc2/shared/src/test/mlscript-compile/cpp/Makefile index bba9ca31e9..3d255e68d0 100644 --- a/hkmc2/shared/src/test/mlscript-compile/cpp/Makefile +++ b/hkmc2/shared/src/test/mlscript-compile/cpp/Makefile @@ -1,6 +1,5 @@ CXX := g++ -CFLAGS += -O3 -Wall -Wextra -std=c++20 -I. -Wno-inconsistent-missing-override -I/opt/homebrew/include -LDFLAGS += -L/opt/homebrew/lib +CFLAGS += -O3 -Wall -Wextra -std=c++20 -I. -Wno-inconsistent-missing-override LDLIBS := -lmimalloc -lgmp SRC := INCLUDES := mlsprelude.h From ec417247f5a08b87a048f4a36c8186be85361ae1 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 21:23:06 +0800 Subject: [PATCH 143/228] Include type annotation origins in resolution diagnostics --- .../scala/hkmc2/semantics/NewResolver.scala | 34 ++- .../main/scala/hkmc2/semantics/Shape.scala | 22 ++ .../src/test/mlscript/newres/Arrays.mls | 38 ++-- .../mlscript/newres/ConstructorInstances.mls | 20 +- .../test/mlscript/newres/DeclaredTypes.mls | 209 +++++++++++------- .../test/mlscript/newres/GenericMethods.mls | 14 +- .../test/mlscript/newres/InferenceHoles.mls | 45 ++-- .../test/mlscript/newres/MutableArrays.mls | 36 +-- .../mlscript/newres/PartialSignatures.mls | 12 +- .../test/mlscript/newres/QuantifiedBounds.mls | 13 +- .../test/mlscript/newres/RecordInterfaces.mls | 17 +- .../src/test/mlscript/newres/SpreadCalls.mls | 23 +- .../newres/TypeInterfaceDiagnostics.mls | 130 ++++++++++- .../mlscript/newres/VarianceSubstitution.mls | 5 +- 14 files changed, 427 insertions(+), 191 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index f1315c786d..eeec2ff326 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1615,7 +1615,7 @@ class NewResolver: case _ => if rstate.arrayIndexErrors.add(new Identity(sel)) then resolError(sel, (msg"${receiver.describe.capitalize} does not support checked array indexing." -> receiver.toLoc) :: - (msg"Use '![...]' for a dynamic access." -> N) :: Nil) + (msg"Use '![...]' for a dynamic access." -> N) :: receiver.diagnosticNotes) private[semantics] def arrayElementType(array: NominalInstanceView): Opt[DeclaredType] = val cls = prelude.builtins.Array.defn.get @@ -1780,8 +1780,8 @@ class NewResolver: if mismatch then if rstate.reportedArities.getOrElseUpdate(reportKey, mutable.Set.empty).add(known) then val count = if unknown then msg"at least ${known}" else msg"${known}" - resolError(src, msg"${callable.describe.capitalize} expected ${expected} ${ - "argument".pluralized(expected)}, but got ${count}" -> callable.toLoc :: Nil) + resolError(src, (msg"${callable.describe.capitalize} expected ${expected} ${ + "argument".pluralized(expected)}, but got ${count}" -> callable.toLoc) :: callable.diagnosticNotes) else matched((tuple, marks)) case _ => resolError(src, msg"Expected an argument tuple." -> args.toLoc :: Nil) @@ -1802,7 +1802,7 @@ class NewResolver: true def reject(sh: TermShape)(using NewResolverState): Unit = rstate.markError(res) - resolError(res, msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> sh.toLoc :: Nil) + resolError(res, (msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> sh.toLoc) :: sh.diagnosticNotes) def classPattern(cls: ClassLikeDef)(using NewResolverState): Unit = if select(cls.sym) then val assoc = res.arguments match case N => Nil @@ -2142,11 +2142,7 @@ class NewResolver: msg"${lhs.describe.capitalize} cannot receive more argument lists." case _ => msg"${lhs.describe.capitalize} cannot be called like a function." - val notes = lhs.applicationHead._1 match - case unknown: UnknownValueShape => unknown.provenance.diagnosticNotes - case opaque: OpaqueTypeShape => opaque.provenance.diagnosticNotes - case _ => Nil - resolError(res, (message -> lhs.toLoc) :: notes) + resolError(res, (message -> lhs.toLoc) :: lhs.diagnosticNotes) if sh.isSaturated then def go(body: Term, mss: Ls[Marks])(using NewResolverState) = listenTerm(body): sh => @@ -2339,9 +2335,9 @@ class NewResolver: def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") - def member(info: MemberLookup, description: Message, loc: Opt[Loc], + def member(info: MemberLookup, description: Message, loc: Opt[Loc], notes: => Ls[(Message, Opt[Loc])], instances: TypeSubstitution)(using NewResolverState): Unit = info match - case MemberLookup.Contextual(source, substitution) => member(source, description, loc, instances.withOverrides(substitution)) + case MemberLookup.Contextual(source, substitution) => member(source, description, loc, notes, instances.withOverrides(substitution)) case MemberLookup.Found(bms, marks) if rstate.canResolve(sel) || sel.resolvedMembers.contains(bms.memberSymbol) => log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") rstate.recordResolution(sel, sel.resolvedMembers.contains(bms.memberSymbol))(sel.resolvedMembers ::= bms.memberSymbol) @@ -2364,7 +2360,7 @@ class NewResolver: publishDynamic(sel, marks) case MemberLookup.Missing if rstate.canResolve(sel) => rstate.markError(sel) - resolError(sel, msg"$description does not contain member '${sel.id.name}'" -> loc :: Nil) + resolError(sel, (msg"$description does not contain member '${sel.id.name}'" -> loc) :: notes) case MemberLookup.Unknown(reason, provenance) => unknownMember(sel, sel.id.name, reason, provenance) // Later activations still transport field values through the compiled // selection, but cannot choose a different member for that old syntax. @@ -2372,7 +2368,7 @@ class NewResolver: sel.cls match case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") - member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc, TypeSubstitution.empty) + member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc, shape.diagnosticNotes, TypeSubstitution.empty) case S(cls) => listenClass(cls)(ref => val cd = ref.definition @@ -2401,11 +2397,11 @@ class NewResolver: val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation, true) case _ => info - member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, TypeSubstitution.empty) - case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc, TypeSubstitution.empty)) + member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, receiver.diagnosticNotes, TypeSubstitution.empty) + case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc, Nil, TypeSubstitution.empty)) , sh => rstate.markError(sel) - resolError(sel, msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc :: Nil) + resolError(sel, (msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc) :: sh.diagnosticNotes) ) /** Both constructor references and explicit `new` use the same definition and @@ -2463,7 +2459,7 @@ class NewResolver: , shape => if !rstate.hasError(nw) then rstate.markError(nw) - resolError(nw, msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.toLoc :: Nil) + resolError(nw, (msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.toLoc) :: shape.diagnosticNotes) ) def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term)(using NewResolverState): DefineVar = @@ -2784,8 +2780,8 @@ class NewResolver: case application @ TyApp(underlying, args) => def checkArity(callee: TermShape, count: Int)(using NewResolverState): Unit = if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then - resolError(trm, msg"${callee.describe.capitalize} expected ${count} type ${ - "argument".pluralized(count)}, but got ${args.length}" -> callee.toLoc :: Nil) + resolError(trm, (msg"${callee.describe.capitalize} expected ${count} type ${ + "argument".pluralized(count)}, but got ${args.length}" -> callee.toLoc) :: callee.diagnosticNotes) def instantiate(value: TermShape)(receive: Listener)(using NewResolverState): Unit = listenInstanceViews(value): viewed => val ShapeParts(actual, captured, supplied) = shapeParts(viewed) if supplied.nonEmpty then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 502bdf75a1..ea610fab0b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -19,6 +19,28 @@ sealed trait Shape extends ShapeEvent, ShapeLike: def describe: Str /** Origin of the value or symbol described by this shape, independently of its use site. */ def toLoc: Opt[Loc] + /** Explain the interface used by a failing operation, independently of its marks. + * Keep annotation witnesses out of shape equality and evaluate their messages + * only when reporting; diagnostic context must not add inference candidates. + */ + final def diagnosticNotes: Ls[(Message, Opt[Loc])] = + def annotation(source: Term): Ls[(Message, Opt[Loc])] = + msg"This type annotation supplies the value's shape." -> source.toLoc :: Nil + this match + case value: TermShape => value.applicationHead._1 match + case view: NominalInstanceView => view.annotation.toList.flatMap(annotation) + case view: RecordTypeShape => annotation(view.source) + // A declaration-backed callable already locates its signature with toLoc; + // its source is the member selection, not a written type annotation. + case view: CallableTypeShape => if view.declaration.isEmpty then annotation(view.source) else Nil + case view: InstanceShape => annotation(view.tpe.resolution.source) + case view: ContextualShape => view.source.diagnosticNotes + case view: SpecializedShape => view.declaration.diagnosticNotes + case unknown: UnknownValueShape => unknown.provenance.diagnosticNotes + case opaque: OpaqueTypeShape => opaque.provenance.diagnosticNotes + case rigid: RigidTypeShape => rigid.provenance.diagnosticNotes + case _ => Nil + case _: SymShape => Nil def shwDbg(using DebugPrinter): Str = this match // case ds: DefnShape => s"DefnShape(${ds.defn.describe} ${ds.defn.sym.showDbg})" case ds: DefnShape => ds.defn.sym.showDbg diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 9e94d94361..08b51450dd 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -30,7 +30,10 @@ read([new Child]) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.28: fun read(xs: [Base]) = xs.map((item, ...) => item.extra) //│ ║ ^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.28: fun read(xs: [Base]) = xs.map((item, ...) => item.extra) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Mutable arrays expose methods without claiming their initial elements persist. @@ -61,10 +64,10 @@ fun singleton(x) = [x] :ge [1].nonexistent //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.62: [1].nonexistent +//│ ║ l.65: [1].nonexistent //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Tuple literal does not contain member 'nonexistent' -//│ ║ l.62: [1].nonexistent +//│ ║ l.65: [1].nonexistent //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -87,8 +90,8 @@ values([Item(4)]).map((item, ...) => item.value) //│ ║ l.162: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. -//│ ║ in Arrays.mls:85 -//│ ║ l.85: [1].map(x => x) +//│ ║ in Arrays.mls:88 +//│ ║ l.88: [1].map(x => x) //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 3 @@ -100,10 +103,10 @@ let xs = mut [Original(1)] set xs.0 = Replacement(2) xs.map((x, ...) => x.original) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.101: xs.map((x, ...) => x.original) +//│ ║ l.104: xs.map((x, ...) => x.original) //│ ║ ^^^^^^^^^^ //│ ╟── Instance of class 'Replacement' does not contain member 'original' -//│ ║ l.100: set xs.0 = Replacement(2) +//│ ║ l.103: set xs.0 = Replacement(2) //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Replacement(2)] @@ -135,7 +138,7 @@ new String("hello") is String :ge new Array[Int](1, 2) //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.136: new Array[Int](1, 2) +//│ ║ l.139: new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls @@ -154,7 +157,7 @@ new Array[Int](1, 2) :ge new Array[Int]() //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.155: new Array[Int]() +//│ ║ l.158: new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls @@ -206,10 +209,10 @@ class Item(val value: Int) class Total(val total: Int) [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.207: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) +//│ ║ l.210: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) //│ ║ ^^^^^^^^^^^ //│ ╟── Instance of class 'Total' does not contain member 'missing' -//│ ║ l.207: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) +//│ ║ l.210: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -218,7 +221,7 @@ class Total(val total: Int) :e [1, 2].reduce((acc, x, ...) => acc + x) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.219: [1, 2].reduce((acc, x, ...) => acc + x) +//│ ║ l.222: [1, 2].reduce((acc, x, ...) => acc + x) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls @@ -258,10 +261,10 @@ let items = mut [Item(4)] class Item(val value: Int) (new Item(1)).[0] //│ ╔══[COMPILATION ERROR] Resolution error in array index -//│ ║ l.259: (new Item(1)).[0] +//│ ║ l.262: (new Item(1)).[0] //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Item' does not support checked array indexing. -//│ ║ l.259: (new Item(1)).[0] +//│ ║ l.262: (new Item(1)).[0] //│ ║ ^^^^^^^^^^^ //│ ╙── Use '![...]' for a dynamic access. //│ ═══[RUNTIME ERROR] TypeError: tmp48.at is not a function @@ -270,6 +273,9 @@ class Item(val value: Int) :e fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.271: fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] +//│ ║ l.274: fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] //│ ║ ^^^^^^^^^^^^^ -//│ ╙── Value of type 'Int' does not contain member 'whatever' +//│ ╟── Value of type 'Int' does not contain member 'whatever' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.274: fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] +//│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls index c0d448e549..d0ec7fdda3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls @@ -172,7 +172,10 @@ callbacks.push(x => x.extra) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.171: callbacks.push(x => x.extra) //│ ║ ^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.170: let callbacks = new mut Array[Base -> Int](0) +//│ ╙── ^^^^ //│ = 1 //│ callbacks = [fun] @@ -194,9 +197,12 @@ callbacks.push(x => new Child(20)) private fun first(callback, ...tail) = callback first(...callbacks)(new Base).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.195: first(...callbacks)(new Base).extra +//│ ║ l.198: first(...callbacks)(new Base).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.195: let callbacks = new mut Array[Base -> Base](0) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ callbacks = [fun] @@ -212,16 +218,16 @@ let first = new Box(First(21)) let second = new Box(Second(22)) [first.copy().item.first, second.copy().item.second] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.213: [first.copy().item.first, second.copy().item.second] +//│ ║ l.219: [first.copy().item.first, second.copy().item.second] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'First' does not contain member 'second' -//│ ║ l.211: let first = new Box(First(21)) +//│ ║ l.217: let first = new Box(First(21)) //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.213: [first.copy().item.first, second.copy().item.second] +//│ ║ l.219: [first.copy().item.first, second.copy().item.second] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.212: let second = new Box(Second(22)) +//│ ║ l.218: let second = new Box(Second(22)) //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[21, 22]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 50f2f247ee..44cd97a1ff 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -21,7 +21,10 @@ hidden(new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.19: fun hidden(base: Base) = base.extra //│ ║ ^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.19: fun hidden(base: Base) = base.extra +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // The declared interface selects the method, while dispatch uses the actual receiver. @@ -42,9 +45,12 @@ class Child extends Base with fun make(): Base = new Child make().extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.43: make().extra +//│ ║ l.46: make().extra //│ ║ ^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.45: fun make(): Base = new Child +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Likewise for a getter/value annotation and for explicit ascription. @@ -56,13 +62,19 @@ val box: Base = new Child box.extra (new Child as Base).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.57: (new Child as Base).extra +//│ ║ l.63: (new Child as Base).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.63: (new Child as Base).extra +//│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.56: box.extra +//│ ║ l.62: box.extra //│ ║ ^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.61: val box: Base = new Child +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Child { extra: 42 } @@ -75,14 +87,14 @@ class Box(val item) fun read(box: Box) = box.item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.75: fun read(box: Box) = box.item.extra +//│ ║ l.87: fun read(box: Box) = box.item.extra //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.74: class Box(val item) +//│ ║ l.86: class Box(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.75: fun read(box: Box) = box.item.extra +//│ ║ l.87: fun read(box: Box) = box.item.extra //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -97,12 +109,12 @@ class Box with fun read(box: Box) = box.item().extra read(new Box) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.97: fun read(box: Box) = box.item().extra -//│ ║ ^^^^^^^^^^^^^^^^ +//│ ║ l.109: fun read(box: Box) = box.item().extra +//│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.97: fun read(box: Box) = box.item().extra -//│ ╙── ^^^^^^^^ +//│ ║ l.109: fun read(box: Box) = box.item().extra +//│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -148,10 +160,13 @@ read(() => Item(42)) :ge fun unused(factory: () -> Int) = factory(1) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.149: fun unused(factory: () -> Int) = factory(1) +//│ ║ l.161: fun unused(factory: () -> Int) = factory(1) //│ ║ ^^^^^^^^^^ //│ ╟── Function with a declared signature expected 0 arguments, but got 1 -//│ ║ l.149: fun unused(factory: () -> Int) = factory(1) +//│ ║ l.161: fun unused(factory: () -> Int) = factory(1) +//│ ║ ^^^^^^^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.161: fun unused(factory: () -> Int) = factory(1) //│ ╙── ^^^^^^^^^ // A callable annotation also hides extra members of the concrete result. @@ -162,9 +177,12 @@ class Child extends Base with fun read(factory: () -> Base) = factory().extra read(() => new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.162: fun read(factory: () -> Base) = factory().extra +//│ ║ l.177: fun read(factory: () -> Base) = factory().extra //│ ║ ^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.177: fun read(factory: () -> Base) = factory().extra +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Separate signatures annotate implementation parameters before any observed call. @@ -176,9 +194,12 @@ fun hidden: (Base) -> Int fun hidden(base) = base.extra hidden(new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.176: fun hidden(base) = base.extra +//│ ║ l.194: fun hidden(base) = base.extra //│ ║ ^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.193: fun hidden: (Base) -> Int +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Separate signatures supply an opaque result as well. @@ -190,9 +211,12 @@ fun make: () -> Base fun make() = new Child make().extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.191: make().extra +//│ ║ l.212: make().extra //│ ║ ^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.210: fun make: () -> Base +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Alias substitution keeps nested arguments in the scope where they were written. @@ -215,16 +239,16 @@ class Box[T] type Loop[T] = Loop[Box[T]] fun unused(value: Loop[Int]) = value.field //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.215: type Loop[T] = Loop[Box[T]] +//│ ║ l.239: type Loop[T] = Loop[Box[T]] //│ ║ ^^^^^^^^^^^^ //│ ╟── Unguarded recursive type alias 'Loop'. -//│ ║ l.215: type Loop[T] = Loop[Box[T]] +//│ ║ l.239: type Loop[T] = Loop[Box[T]] //│ ║ ^^^^ //│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. -//│ ║ l.215: type Loop[T] = Loop[Box[T]] +//│ ║ l.239: type Loop[T] = Loop[Box[T]] //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] This selection of member 'field' has no resolved target -//│ ║ l.216: fun unused(value: Loop[Int]) = value.field +//│ ║ l.240: fun unused(value: Loop[Int]) = value.field //│ ╙── ^^^^^^^^^^^ // A concrete instantiation cannot authorize calls through an opaque type parameter. @@ -232,16 +256,16 @@ fun unused(value: Loop[Int]) = value.field private fun invoke[T](value: T) = value() invoke[() -> Int](() => 42) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.232: private fun invoke[T](value: T) = value() +//│ ║ l.256: private fun invoke[T](value: T) = value() //│ ║ ^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.232: private fun invoke[T](value: T) = value() +//│ ║ l.256: private fun invoke[T](value: T) = value() //│ ║ ^ //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.232: private fun invoke[T](value: T) = value() +//│ ║ l.256: private fun invoke[T](value: T) = value() //│ ║ ^ //│ ╟── Type parameter 'T' does not specify a member interface. -//│ ║ l.232: private fun invoke[T](value: T) = value() +//│ ║ l.256: private fun invoke[T](value: T) = value() //│ ╙── ^ //│ = 42 @@ -254,14 +278,14 @@ class Box(val item) fun read(box: Box) = box.Box#item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.254: fun read(box: Box) = box.Box#item.extra +//│ ║ l.278: fun read(box: Box) = box.Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.253: class Box(val item) +//│ ║ l.277: class Box(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.254: fun read(box: Box) = box.Box#item.extra +//│ ║ l.278: fun read(box: Box) = box.Box#item.extra //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -292,9 +316,12 @@ class Child extends Base with fun identity[T](x: T): T = x identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.293: identity[Base](new Child).extra +//│ ║ l.317: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.317: identity[Base](new Child).extra +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Type-parameter uses in nested signatures retain their capture path. @@ -334,13 +361,19 @@ class Box[T](val value: T) (new Box[Base](new Child)).value.extra Box[Base](new Child).value.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.334: (new Box[Base](new Child)).value.extra +//│ ║ l.361: (new Box[Base](new Child)).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.361: (new Box[Base](new Child)).value.extra +//│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.335: Box[Base](new Child).value.extra +//│ ║ l.362: Box[Base](new Child).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.362: Box[Base](new Child).value.extra +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // A generic method's result carries the receiver's type argument to the caller. @@ -382,11 +415,11 @@ class Box with val item = new Item (new Box as Box).Box#item.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.383: (new Box as Box).Box#item.extra +//│ ║ l.416: (new Box as Box).Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.383: (new Box as Box).Box#item.extra +//│ ║ l.416: (new Box as Box).Box#item.extra //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -402,11 +435,11 @@ module Types with fun hidden(module types: Types) = types.Item.extra hidden(Types) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.402: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.435: fun hidden(module types: Types) = types.Item.extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.402: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.435: fun hidden(module types: Types) = types.Item.extra //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -426,9 +459,12 @@ class Child extends Base with fun apply[A](f: A -> A, x: A): A = f(x) apply[Base](arg => (arg.extra; arg), new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.427: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ l.460: apply[Base](arg => (arg.extra; arg), new Child) //│ ║ ^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.460: apply[Base](arg => (arg.extra; arg), new Child) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Independent callback instantiations must not mix their tuple element shapes. @@ -449,9 +485,12 @@ class Child extends Base with fun read(xs: [Base, Int]) = xs.0.extra read([new Child, 0]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.449: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ l.485: fun read(xs: [Base, Int]) = xs.0.extra //│ ║ ^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.485: fun read(xs: [Base, Int]) = xs.0.extra +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Tuple projections retain contexts through spreads and function returns. @@ -470,10 +509,10 @@ let xs = mut [First(1)] set xs.0 = Second(2) [...xs].0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.471: [...xs].0.first +//│ ║ l.510: [...xs].0.first //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.470: set xs.0 = Second(2) +//│ ║ l.509: set xs.0 = Second(2) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -482,10 +521,10 @@ set xs.0 = Second(2) :ge [1, 2].2 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.483: [1, 2].2 +//│ ║ l.522: [1, 2].2 //│ ║ ^^^^^^^^ //│ ╟── Tuple literal does not contain member '2' -//│ ║ l.483: [1, 2].2 +//│ ║ l.522: [1, 2].2 //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -522,14 +561,14 @@ private fun identity(value) = value fun read(box: Annotated) = identity(box.item).extra read(Child(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.522: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.561: fun read(box: Annotated) = identity(box.item).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.518: class Parent(val item) +//│ ║ l.557: class Parent(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.522: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.561: fun read(box: Annotated) = identity(box.item).extra //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -541,16 +580,16 @@ class Box(val callback) fun invoke(box: Box) = box.callback() invoke(Box(() => 42)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.541: fun invoke(box: Box) = box.callback() +//│ ║ l.580: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.541: fun invoke(box: Box) = box.callback() +//│ ║ l.580: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^ //│ ╟── Constructor parameter 'callback' has no type annotation. -//│ ║ l.540: class Box(val callback) +//│ ║ l.579: class Box(val callback) //│ ║ ^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'callback'. -//│ ║ l.541: fun invoke(box: Box) = box.callback() +//│ ║ l.580: fun invoke(box: Box) = box.callback() //│ ╙── ^^^ //│ = 42 @@ -559,14 +598,14 @@ invoke(Box(() => 42)) class Box[X](val value: [X, X]) fun foo[A](x: Box[A]) = x.value.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.560: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.599: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.560: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.599: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.560: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.599: fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ // Concrete callers cannot hide the annotation responsible for an abstract tuple element. @@ -577,14 +616,14 @@ class Box[X](val value: [X, X]) private fun foo[A](x: Box[A]) = x.value.0.oops foo[Item](Box[Item]([new Item, new Item])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.577: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.616: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.577: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.616: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.577: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.616: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -594,28 +633,28 @@ class Box[X](val value: [X, X]) type Wrapped[T] = Box[T] fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.595: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.634: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.595: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.634: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.595: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.634: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╙── ^ // Tuple annotations do not need a nominal receiver to produce an abstract element. :ge fun foo[A](x: [A, A]) = x.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.609: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.648: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.609: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.648: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.609: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.648: fun foo[A](x: [A, A]) = x.0.oops //│ ╙── ^ // An unannotated field can be reached through a function result annotation and an alias. @@ -624,14 +663,14 @@ class Box(val value) type Wrapped = Box fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.625: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.664: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.623: class Box(val value) +//│ ║ l.662: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.625: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.664: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╙── ^^^^^^^ // TODO: ascription preserves receiver flow for the selected field's missing type. @@ -642,14 +681,14 @@ class Item with class Box(val value) (Box(new Item) as Box).value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.643: (Box(new Item) as Box).value.oops +//│ ║ l.682: (Box(new Item) as Box).value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.642: class Box(val value) +//│ ║ l.681: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.643: (Box(new Item) as Box).value.oops +//│ ║ l.682: (Box(new Item) as Box).value.oops //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -658,14 +697,14 @@ class Box(val value) :ge fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.659: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.698: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.659: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.698: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.659: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.698: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╙── ^ // A concrete annotation supplies the nested member interface without losing tuple precision. @@ -682,14 +721,14 @@ foo(Box[Item]([new Item, new Item])) class Pair[A, B](val value: [A, B]) fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.683: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.722: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.683: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.722: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^ //│ ╟── 'omittedSecond' is exposed by this compilation unit. -//│ ║ l.683: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.722: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // An unfilled omitted alias argument likewise cannot justify an arbitrary member. @@ -697,12 +736,12 @@ fun omittedSecond(x: Pair[Int]) = x.value.1.oops type HalfPair[T] = Pair[Int, T] fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.698: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.737: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.698: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.737: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^ //│ ╟── 'omittedAlias' is exposed by this compilation unit. -//│ ║ l.698: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.737: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 6c2a51250b..731156acdf 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -48,14 +48,17 @@ class Child extends Base with //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.47: [new Child].map[Base]((x, ...) => x).map((x, ...) => x.extra) //│ ║ ^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.47: [new Child].map[Base]((x, ...) => x).map((x, ...) => x.extra) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Imported methods validate explicit type-argument counts too. :ge [1].map[Int, Int]((x, ...) => x) //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.56: [1].map[Int, Int]((x, ...) => x) +//│ ║ l.59: [1].map[Int, Int]((x, ...) => x) //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls @@ -84,9 +87,12 @@ class Methods with fun test(m: Methods) = m.identity[Base](new Child).extra test(new Methods) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.84: fun test(m: Methods) = m.identity[Base](new Child).extra +//│ ║ l.87: fun test(m: Methods) = m.identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.87: fun test(m: Methods) = m.identity[Base](new Child).extra +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Curried declared methods preserve their type arguments across parameter lists. diff --git a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls index ae0075101b..1f008f3d1c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls @@ -51,7 +51,10 @@ first(Pair([Child(7), 0])) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.49: private fun first(pair: Pair[Base]) = pair.value.0.extra //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.49: private fun first(pair: Pair[Base]) = pair.value.0.extra +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // External inputs remain unknown even when local callers supply useful shapes. @@ -61,14 +64,14 @@ class Box[T](val item: T) fun read(box: Box) = box.item.value read(Box(Item(8))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.61: fun read(box: Box) = box.item.value +//│ ║ l.64: fun read(box: Box) = box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── Parameter 'box' admits values of unknown shape. -//│ ║ l.61: fun read(box: Box) = box.item.value +//│ ║ l.64: fun read(box: Box) = box.item.value //│ ║ ^^^^^^^^ //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.61: fun read(box: Box) = box.item.value +//│ ║ l.64: fun read(box: Box) = box.item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -99,16 +102,16 @@ type SomeBox = Box private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] both(Box(First(11)), Box(Second(12))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.99: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] -//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ║ l.102: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'First' does not contain member 'second' -//│ ║ l.100: both(Box(First(11)), Box(Second(12))) +//│ ║ l.103: both(Box(First(11)), Box(Second(12))) //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.99: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] -//│ ║ ^^^^^^^^^^^^^^^ +//│ ║ l.102: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.100: both(Box(First(11)), Box(Second(12))) +//│ ║ l.103: both(Box(First(11)), Box(Second(12))) //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[11, 12]', got: 'undefined' @@ -118,13 +121,13 @@ both(Box(First(11)), Box(Second(12))) class Box[T](val item: T) private fun excess(value: Box[Int, Str]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.119: private fun excess(value: Box[Int, Str]) = () +//│ ║ l.122: private fun excess(value: Box[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^ //│ ╟── Type 'Box' accepts at most 1 type argument, but got 2. -//│ ║ l.119: private fun excess(value: Box[Int, Str]) = () +//│ ║ l.122: private fun excess(value: Box[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. -//│ ║ l.118: class Box[T](val item: T) +//│ ║ l.121: class Box[T](val item: T) //│ ╙── ^ // A type alias checks its own argument list before interpreting the body. @@ -133,13 +136,13 @@ class Box[T](val item: T) type Alias[A] = Box[A] private fun excess(value: Alias[Int, Str]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.134: private fun excess(value: Alias[Int, Str]) = () +//│ ║ l.137: private fun excess(value: Alias[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Type 'Alias' accepts at most 1 type argument, but got 2. -//│ ║ l.134: private fun excess(value: Alias[Int, Str]) = () +//│ ║ l.137: private fun excess(value: Alias[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. -//│ ║ l.133: type Alias[A] = Box[A] +//│ ║ l.136: type Alias[A] = Box[A] //│ ╙── ^^^^^^ // Annotated results do not hide missing input types from exposure checking. @@ -149,11 +152,11 @@ class Box[T](val item: T) fun read(box: Box): Int = box.item.value read(Box(Item(13))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.149: fun read(box: Box): Int = box.item.value +//│ ║ l.152: fun read(box: Box): Int = box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.149: fun read(box: Box): Int = box.item.value +//│ ║ l.152: fun read(box: Box): Int = box.item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -164,14 +167,14 @@ class Box[T](val item: T) fun read(callback: () -> Box) = callback().item.value read(() => Box(Item(14))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.164: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.167: fun read(callback: () -> Box) = callback().item.value //│ ║ ^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── Parameter 'callback' admits values of unknown shape. -//│ ║ l.164: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.167: fun read(callback: () -> Box) = callback().item.value //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.164: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.167: fun read(callback: () -> Box) = callback().item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls index 89e243cd33..831c961e22 100644 --- a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls @@ -128,7 +128,10 @@ xs.0.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.127: xs.0.extra //│ ║ ^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.125: let xs = (mut [] as Array[Base]) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Child { extra: 1 }] @@ -140,10 +143,10 @@ let xs = mut [First(1)] set xs.[0] = Second(2) xs.0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.141: xs.0.first +//│ ║ l.144: xs.0.first //│ ║ ^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.140: set xs.[0] = Second(2) +//│ ║ l.143: set xs.[0] = Second(2) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -155,10 +158,10 @@ let xs = mut [First(1)] xs.fill(Second(2)) xs.0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.156: xs.0.first +//│ ║ l.159: xs.0.first //│ ║ ^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.155: xs.fill(Second(2)) +//│ ║ l.158: xs.fill(Second(2)) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -168,25 +171,25 @@ xs.0.first module Exposed with fun make() = mut [x => x.missing] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.169: fun make() = mut [x => x.missing] +//│ ║ l.172: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^ //│ ╟── Cannot resolve member 'missing' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.169: fun make() = mut [x => x.missing] +//│ ║ l.172: fun make() = mut [x => x.missing] //│ ║ ^ //│ ╟── This value is stored in this array. -//│ ║ l.169: fun make() = mut [x => x.missing] +//│ ║ l.172: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── This value is returned here. -//│ ║ l.169: fun make() = mut [x => x.missing] +//│ ║ l.172: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'make' is accessible here. -//│ ║ l.169: fun make() = mut [x => x.missing] +//│ ║ l.172: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── 'Exposed' is exposed by this compilation unit. -//│ ║ l.168: module Exposed with +//│ ║ l.171: module Exposed with //│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ║ l.169: fun make() = mut [x => x.missing] +//│ ║ l.172: fun make() = mut [x => x.missing] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Explicit constructor type arguments remain opaque when methods receive subtypes. @@ -198,9 +201,12 @@ let xs = new mut Array[Base](0) xs.push(new Child) xs.map((x, ...) => x.extra) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.199: xs.map((x, ...) => x.extra) +//│ ║ l.202: xs.map((x, ...) => x.extra) //│ ║ ^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.200: let xs = new mut Array[Base](0) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Child { extra: 1 }] @@ -220,7 +226,7 @@ let xs = mut [] xs.splice(0, 0, Item(2)) xs.0.value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.221: xs.0.value +//│ ║ l.227: xs.0.value //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls index dc782b2b83..7a23b3d7e8 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls @@ -160,7 +160,10 @@ identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.159: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.159: identity[Base](new Child).extra +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ identity = fun identity @@ -169,10 +172,13 @@ identity[Base](new Child).extra let identity = (x => x) as [A] -> A -> A identity[Int, Int](1) //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.170: identity[Int, Int](1) +//│ ║ l.173: identity[Int, Int](1) //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Function with a declared signature expected 1 type argument, but got 2 -//│ ║ l.169: let identity = (x => x) as [A] -> A -> A +//│ ║ l.172: let identity = (x => x) as [A] -> A -> A +//│ ║ ^^^^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.172: let identity = (x => x) as [A] -> A -> A //│ ╙── ^^^^^^ //│ = 1 //│ identity = fun identity diff --git a/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls b/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls index d663736240..d767fc0fd2 100644 --- a/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls +++ b/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls @@ -61,7 +61,10 @@ private fun read() = make[Base]().extra //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.60: private fun read() = make[Base]().extra //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.60: private fun read() = make[Base]().extra +//│ ╙── ^^^^ // A by-name invocation consumes the quantified scheme at the selection itself. // Its recursive body supplies no concrete candidate. @@ -89,7 +92,7 @@ class Item(val value: Int) fun read: [A restricts Item] -> A -> Int fun read(x) = x.value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.90: fun read(x) = x.value +//│ ║ l.93: fun read(x) = x.value //│ ╙── ^^^^^^^ // Capturing an enclosing binder specializes the bounds as well as the result. @@ -112,14 +115,14 @@ class Provider[T] with fun make = make private fun read(provider: Provider[Item]) = provider.make.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.113: private fun read(provider: Provider[Item]) = provider.make.value +//│ ║ l.116: private fun read(provider: Provider[Item]) = provider.make.value //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.111: fun make: [A restricts T] -> A +//│ ║ l.114: fun make: [A restricts T] -> A //│ ║ ^ //│ ╟── Type parameter 'T' does not specify a member interface. -//│ ║ l.110: class Provider[T] with +//│ ║ l.113: class Provider[T] with //│ ╙── ^ // Without that recursive checking edge, the declared receiver supplies its diff --git a/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls index 0e3dc04dfd..fb0cc4e628 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls @@ -18,6 +18,9 @@ hidden(name: "visible", extra: 1) //│ ║ ^^^^^^^^^^^^ //│ ╟── Record type does not contain member 'extra' //│ ║ l.14: fun hidden(fields: {name: Str}) = fields.extra +//│ ║ ^^^^^^^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.14: fun hidden(fields: {name: Str}) = fields.extra //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -25,14 +28,14 @@ hidden(name: "visible", extra: 1) private fun opaque[A](fields: {value: A}) = fields.value.answer opaque(value: {answer: 42}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.25: private fun opaque[A](fields: {value: A}) = fields.value.answer +//│ ║ l.28: private fun opaque[A](fields: {value: A}) = fields.value.answer //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.25: private fun opaque[A](fields: {value: A}) = fields.value.answer +//│ ║ l.28: private fun opaque[A](fields: {value: A}) = fields.value.answer //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.25: private fun opaque[A](fields: {value: A}) = fields.value.answer +//│ ║ l.28: private fun opaque[A](fields: {value: A}) = fields.value.answer //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -56,12 +59,16 @@ callback(f: x => x.toUpperCase()) fun expose(): {f: Object -> Int} = {f: x => x.answer} expose().f({answer: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.56: fun expose(): {f: Object -> Int} = {f: x => x.answer} +//│ ║ l.59: fun expose(): {f: Object -> Int} = {f: x => x.answer} //│ ║ ^^^^^^^^ //│ ╟── Value of type 'Object' does not contain member 'answer' //│ ║ in ../decls/Prelude.mls //│ ║ l.7: fun toString(): Str -//│ ╙── ^^^ +//│ ║ ^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ in RecordInterfaces.mls:59 +//│ ║ l.59: fun expose(): {f: Object -> Int} = {f: x => x.answer} +//│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :expect "ALIAS" diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index a95fcdfabc..4cda96ecb3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -372,7 +372,10 @@ readBase([new Child]) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.370: fun readBase(xs: Array[Base]) = first(...xs).extra //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'extra' +//│ ╟── Value of type 'Base' does not contain member 'extra' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.370: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Different type arguments keep distinct element interfaces at their call sites. @@ -409,17 +412,23 @@ invokeUnannotatedArray(...unknownCallbacks) private fun firstInArray(xs) = first(...xs) [firstInArray(lefts).left, firstInArray(rights).right] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.410: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.413: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Right' does not contain member 'left' -//│ ║ l.381: class Right(val right: Int) -//│ ╙── ^^^ +//│ ║ l.384: class Right(val right: Int) +//│ ║ ^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.392: let rights = new mut Array[Right](0) +//│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.410: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.413: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Left' does not contain member 'right' -//│ ║ l.380: class Left(val left: Int) -//│ ╙── ^^^ +//│ ║ l.383: class Left(val left: Int) +//│ ║ ^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.391: let lefts = new mut Array[Left](0) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[43, 44]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls index c71cd69c11..aef9893f38 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls @@ -179,18 +179,142 @@ delay(pair)().1.item.value //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ pair = [Left(Item(1)), Right(Item(2))] +// Known nominal interfaces retain the annotation use through aliases and captures. +:ge +class Base +type Alias = Base +fun inspect(value: Alias) = (() => value)().missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.186: fun inspect(value: Alias) = (() => value)().missing +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'missing' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.186: fun inspect(value: Alias) = (() => value)().missing +//│ ╙── ^^^^^ + +// A function result identifies the annotation that hides implementation members. +:ge +class Base +fun make(): Base = new Base +make().missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.199: make().missing +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'missing' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.198: fun make(): Base = new Base +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Structural interfaces explain which written fields restrict a selection. +:ge +fun inspect(value: {present: Int}) = value.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.211: fun inspect(value: {present: Int}) = value.missing +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Record type does not contain member 'missing' +//│ ║ l.211: fun inspect(value: {present: Int}) = value.missing +//│ ║ ^^^^^^^^^^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.211: fun inspect(value: {present: Int}) = value.missing +//│ ╙── ^^^^^^^^^^^^ + +// Calling a nominal value points to the annotation that made it non-callable. +:ge +class Base +fun invoke(value: Base) = value() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.225: fun invoke(value: Base) = value() +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'Base' cannot be called like a function. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.225: fun invoke(value: Base) = value() +//│ ╙── ^^^^ + +// Arity errors on a declared callback identify its function type. +:ge +fun invoke(callback: Int -> Int) = callback() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.236: fun invoke(callback: Int -> Int) = callback() +//│ ║ ^^^^^^^^^^ +//│ ╟── Function with a declared signature expected 1 argument, but got 0 +//│ ║ l.236: fun invoke(callback: Int -> Int) = callback() +//│ ║ ^^^^^^^^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.236: fun invoke(callback: Int -> Int) = callback() +//│ ╙── ^^^^^^^^^^ + +// Type argument arity errors retain the same callback signature witness. +:ge +fun invoke(callback: Int -> Int) = callback[Int](0) +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.249: fun invoke(callback: Int -> Int) = callback[Int](0) +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Function with a declared signature expected 0 type arguments, but got 1 +//│ ║ l.249: fun invoke(callback: Int -> Int) = callback[Int](0) +//│ ║ ^^^^^^^^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.249: fun invoke(callback: Int -> Int) = callback[Int](0) +//│ ╙── ^^^^^^^^^^ + +// A function interface also explains missing members on the callback itself. +:ge +fun inspect(callback: Int -> Int) = callback.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.262: fun inspect(callback: Int -> Int) = callback.missing +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Function with a declared signature does not contain member 'missing' +//│ ║ l.262: fun inspect(callback: Int -> Int) = callback.missing +//│ ║ ^^^^^^^^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.262: fun inspect(callback: Int -> Int) = callback.missing +//│ ╙── ^^^^^^^^^^ + +// Checked indexing reports the annotation that does not expose an array. +:ge +class Base +fun inspect(value: Base) = value.[0] +//│ ╔══[COMPILATION ERROR] Resolution error in array index +//│ ║ l.276: fun inspect(value: Base) = value.[0] +//│ ║ ^^^^^^^^ +//│ ╟── Value of type 'Base' does not support checked array indexing. +//│ ╟── Use '![...]' for a dynamic access. +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.276: fun inspect(value: Base) = value.[0] +//│ ╙── ^^^^ + +// An annotated instance cannot be used as a constructor. +:ge +class Base +val instance: Base = new Base +new instance +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.290: new instance +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Value with a declared type cannot be instantiated with keyword 'new'. +//│ ║ l.289: val instance: Base = new Base +//│ ║ ^^^^ +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.289: val instance: Base = new Base +//│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ║ l.290: new instance +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ instance = Base + // An alias keeps the annotation use as well as the abstract declaration's location. :ge declare type Hidden type Alias = Hidden fun inspect(value: Alias) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.186: fun inspect(value: Alias) = value.member +//│ ║ l.310: fun inspect(value: Alias) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. //│ ╟── This type annotation supplies the value's shape. -//│ ║ l.186: fun inspect(value: Alias) = value.member +//│ ║ l.310: fun inspect(value: Alias) = value.member //│ ║ ^^^^^ //│ ╟── Type 'Hidden' is declared without a member interface. -//│ ║ l.184: declare type Hidden +//│ ║ l.308: declare type Hidden //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls index 8ce1ee48cc..8952900905 100644 --- a/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls @@ -125,7 +125,10 @@ box.item.value //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.124: box.item.value //│ ║ ^^^^^^^^^^^^^^ -//│ ╙── Value of type 'Base' does not contain member 'value' +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ╟── This type annotation supplies the value's shape. +//│ ║ l.121: let receiver = (new Receiver as Receiver[in Child out Base]) +//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Box(Child(8)) //│ receiver = Receiver From d48b60e08eb2a9b29e489695365b43b69ba9c59d Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 21:34:34 +0800 Subject: [PATCH 144/228] Attach type origins directly to resolution error messages --- .../scala/hkmc2/semantics/NewResolver.scala | 26 ++- .../main/scala/hkmc2/semantics/Shape.scala | 107 +++++----- .../src/test/mlscript/newres/Arrays.mls | 32 ++- .../mlscript/newres/ConstructorInstances.mls | 14 +- .../test/mlscript/newres/DeclaredTypes.mls | 185 ++++++++---------- .../test/mlscript/newres/GenericMethods.mls | 8 +- .../test/mlscript/newres/InferenceHoles.mls | 37 ++-- .../mlscript/newres/InterfaceExposure.mls | 135 ++++++------- .../test/mlscript/newres/MutableArrays.mls | 30 ++- .../mlscript/newres/PartialSignatures.mls | 8 +- .../test/mlscript/newres/QuantifiedBounds.mls | 10 +- .../test/mlscript/newres/RecordInterfaces.mls | 19 +- .../src/test/mlscript/newres/ReslScratch.mls | 8 +- .../src/test/mlscript/newres/SpreadCalls.mls | 40 ++-- .../mlscript/newres/TuplePatternBindings.mls | 1 - .../newres/TypeInterfaceDiagnostics.mls | 102 ++++------ .../mlscript/newres/VarianceSubstitution.mls | 1 - .../newres/loose/InterfaceExposure.mls | 22 +-- 18 files changed, 349 insertions(+), 436 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index eeec2ff326..871a5d897b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -935,11 +935,9 @@ class NewResolver: // generic members and nested tuple or function annotations. val annotated = value match case Marked(rigid: RigidTypeShape, marks) => - UnknownValueShape(rigid.source)(rigid.provenance.via( - msg"This type annotation supplies the value's shape." -> tpe.resolution.source.toLoc)).exit(marks) + UnknownValueShape(rigid.source)(rigid.provenance.withTypeOrigin(tpe.resolution.source)).exit(marks) case Marked(unknown: UnknownValueShape, marks) => - UnknownValueShape(unknown.source)(unknown.provenance.via( - msg"This type annotation supplies the value's shape." -> tpe.resolution.source.toLoc)).exit(marks) + UnknownValueShape(unknown.source)(unknown.provenance.withTypeOrigin(tpe.resolution.source)).exit(marks) case _ => value annotated match case value: TermShape => listenInstanceViews(instantiateShape(value, current.instances))(publish) @@ -1614,7 +1612,7 @@ class NewResolver: case NoShape => () case _ => if rstate.arrayIndexErrors.add(new Identity(sel)) then - resolError(sel, (msg"${receiver.describe.capitalize} does not support checked array indexing." -> receiver.toLoc) :: + resolError(sel, (msg"${receiver.describe.capitalize} does not support checked array indexing." -> receiver.diagnosticLocation) :: (msg"Use '![...]' for a dynamic access." -> N) :: receiver.diagnosticNotes) private[semantics] def arrayElementType(array: NominalInstanceView): Opt[DeclaredType] = @@ -1781,7 +1779,7 @@ class NewResolver: if rstate.reportedArities.getOrElseUpdate(reportKey, mutable.Set.empty).add(known) then val count = if unknown then msg"at least ${known}" else msg"${known}" resolError(src, (msg"${callable.describe.capitalize} expected ${expected} ${ - "argument".pluralized(expected)}, but got ${count}" -> callable.toLoc) :: callable.diagnosticNotes) + "argument".pluralized(expected)}, but got ${count}" -> callable.diagnosticLocation) :: callable.diagnosticNotes) else matched((tuple, marks)) case _ => resolError(src, msg"Expected an argument tuple." -> args.toLoc :: Nil) @@ -1802,7 +1800,7 @@ class NewResolver: true def reject(sh: TermShape)(using NewResolverState): Unit = rstate.markError(res) - resolError(res, (msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> sh.toLoc) :: sh.diagnosticNotes) + resolError(res, (msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> sh.diagnosticLocation) :: sh.diagnosticNotes) def classPattern(cls: ClassLikeDef)(using NewResolverState): Unit = if select(cls.sym) then val assoc = res.arguments match case N => Nil @@ -2142,7 +2140,7 @@ class NewResolver: msg"${lhs.describe.capitalize} cannot receive more argument lists." case _ => msg"${lhs.describe.capitalize} cannot be called like a function." - resolError(res, (message -> lhs.toLoc) :: lhs.diagnosticNotes) + resolError(res, (message -> lhs.diagnosticLocation) :: lhs.diagnosticNotes) if sh.isSaturated then def go(body: Term, mss: Ls[Marks])(using NewResolverState) = listenTerm(body): sh => @@ -2246,7 +2244,7 @@ class NewResolver: msg"Cannot resolve member '$name' of a value with unknown shape." case MemberLookup.Uncertainty.RecordOverwrite => msg"Cannot resolve member '$name' across a computed key or unknown record spread." - resolError(host, (message -> N) :: provenance.diagnosticNotes) + resolError(host, (message -> provenance.typeOrigin.flatMap(_.toLoc)) :: provenance.diagnosticNotes) def unresolvedRef(ref: UnresolvedRef)(using NewResolverState): Unit = ref.prefixes.foreach: prefix => @@ -2368,7 +2366,7 @@ class NewResolver: sel.cls match case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") - member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.toLoc, shape.diagnosticNotes, TypeSubstitution.empty) + member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnosticLocation, shape.diagnosticNotes, TypeSubstitution.empty) case S(cls) => listenClass(cls)(ref => val cd = ref.definition @@ -2397,11 +2395,11 @@ class NewResolver: val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation, true) case _ => info - member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, receiver.diagnosticNotes, TypeSubstitution.empty) + member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, Nil, TypeSubstitution.empty) case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc, Nil, TypeSubstitution.empty)) , sh => rstate.markError(sel) - resolError(sel, (msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.toLoc) :: sh.diagnosticNotes) + resolError(sel, (msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.diagnosticLocation) :: sh.diagnosticNotes) ) /** Both constructor references and explicit `new` use the same definition and @@ -2459,7 +2457,7 @@ class NewResolver: , shape => if !rstate.hasError(nw) then rstate.markError(nw) - resolError(nw, (msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.toLoc) :: shape.diagnosticNotes) + resolError(nw, (msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.diagnosticLocation) :: shape.diagnosticNotes) ) def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term)(using NewResolverState): DefineVar = @@ -2781,7 +2779,7 @@ class NewResolver: def checkArity(callee: TermShape, count: Int)(using NewResolverState): Unit = if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then resolError(trm, (msg"${callee.describe.capitalize} expected ${count} type ${ - "argument".pluralized(count)}, but got ${args.length}" -> callee.toLoc) :: callee.diagnosticNotes) + "argument".pluralized(count)}, but got ${args.length}" -> callee.diagnosticLocation) :: callee.diagnosticNotes) def instantiate(value: TermShape)(receive: Listener)(using NewResolverState): Unit = listenInstanceViews(value): viewed => val ShapeParts(actual, captured, supplied) = shapeParts(viewed) if supplied.nonEmpty then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index ea610fab0b..75c8ee1461 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -19,28 +19,32 @@ sealed trait Shape extends ShapeEvent, ShapeLike: def describe: Str /** Origin of the value or symbol described by this shape, independently of its use site. */ def toLoc: Opt[Loc] - /** Explain the interface used by a failing operation, independently of its marks. - * Keep annotation witnesses out of shape equality and evaluate their messages - * only when reporting; diagnostic context must not add inference candidates. + /** Location supporting a description of this value's interface. A nominal + * view's class declaration and the annotation restricting this value are + * different sources: interface errors use the annotation, while toLoc keeps + * identifying the declaration. No diagnostic text is inspected to choose it. */ - final def diagnosticNotes: Ls[(Message, Opt[Loc])] = - def annotation(source: Term): Ls[(Message, Opt[Loc])] = - msg"This type annotation supplies the value's shape." -> source.toLoc :: Nil - this match - case value: TermShape => value.applicationHead._1 match - case view: NominalInstanceView => view.annotation.toList.flatMap(annotation) - case view: RecordTypeShape => annotation(view.source) - // A declaration-backed callable already locates its signature with toLoc; - // its source is the member selection, not a written type annotation. - case view: CallableTypeShape => if view.declaration.isEmpty then annotation(view.source) else Nil - case view: InstanceShape => annotation(view.tpe.resolution.source) - case view: ContextualShape => view.source.diagnosticNotes - case view: SpecializedShape => view.declaration.diagnosticNotes - case unknown: UnknownValueShape => unknown.provenance.diagnosticNotes - case opaque: OpaqueTypeShape => opaque.provenance.diagnosticNotes - case rigid: RigidTypeShape => rigid.provenance.diagnosticNotes - case _ => Nil - case _: SymShape => Nil + final def diagnosticLocation: Opt[Loc] = this match + case value: TermShape => value.applicationHead._1 match + case view: NominalInstanceView => view.annotation.flatMap(_.toLoc).orElse(toLoc) + case view: ContextualShape => view.source.diagnosticLocation + case view: SpecializedShape => view.declaration.diagnosticLocation + case unknown: UnknownValueShape => unknown.provenance.typeOrigin.flatMap(_.toLoc).orElse(toLoc) + case opaque: OpaqueTypeShape => opaque.provenance.typeOrigin.flatMap(_.toLoc).orElse(toLoc) + case _ => toLoc + case _: SymShape => toLoc + /** Additional explanations, separate from the interface location attached to + * the primary message. Annotation witnesses do not change shape equality. + */ + final def diagnosticNotes: Ls[(Message, Opt[Loc])] = this match + case value: TermShape => value.applicationHead._1 match + case view: ContextualShape => view.source.diagnosticNotes + case view: SpecializedShape => view.declaration.diagnosticNotes + case unknown: UnknownValueShape => unknown.provenance.diagnosticNotes + case opaque: OpaqueTypeShape => opaque.provenance.diagnosticNotes + case rigid: RigidTypeShape => rigid.provenance.diagnosticNotes + case _ => Nil + case _: SymShape => Nil def shwDbg(using DebugPrinter): Str = this match // case ds: DefnShape => s"DefnShape(${ds.defn.describe} ${ds.defn.sym.showDbg})" case ds: DefnShape => ds.defn.sym.showDbg @@ -578,26 +582,29 @@ enum TypeInterfaceReason: case Negated(source: Term) case Unavailable(source: Term) - def provenance: ShapeProvenance = ShapeProvenance(this match - case MissingOutput(source) => - (msg"This type argument specifies only an input bound." -> source.toLoc) :: - (msg"Values read through it have no output bound more specific than 'Any'." -> N) :: Nil - case ContravariantParameter(parameter, source) => - (msg"This type argument supplies only an input bound for '${parameter.sym.nme}'." -> source.toLoc) :: - (msg"Type parameter '${parameter.sym.nme}' is declared contravariant here." -> parameter.sym.toLoc) :: - (msg"Values read through it have no output bound more specific than 'Any'." -> N) :: Nil - case Unrestricted(source) => - msg"This type permits values of any type, so no member interface is guaranteed." -> source.toLoc :: Nil - case AbstractDeclaration(symbol, source) => - (msg"This type annotation supplies the value's shape." -> source.toLoc) :: + def provenance: ShapeProvenance = + val result = ShapeProvenance(this match + case MissingOutput(source) => + (msg"This type argument specifies only an input bound." -> source.toLoc) :: + (msg"Values read through it have no output bound more specific than 'Any'." -> N) :: Nil + case ContravariantParameter(parameter, source) => + (msg"This type argument supplies only an input bound for '${parameter.sym.nme}'." -> source.toLoc) :: + (msg"Type parameter '${parameter.sym.nme}' is declared contravariant here." -> parameter.sym.toLoc) :: + (msg"Values read through it have no output bound more specific than 'Any'." -> N) :: Nil + case Unrestricted(source) => + msg"This type permits values of any type, so no member interface is guaranteed." -> source.toLoc :: Nil + case AbstractDeclaration(symbol, _) => (msg"Type '${symbol.nme}' is declared without a member interface." -> symbol.toLoc) :: Nil - case RecursiveAlias(symbol) => - msg"Following type alias '${symbol.nme}' does not expose a member interface." -> symbol.toLoc :: Nil - case Negated(source) => - msg"This negated type does not specify a member interface." -> source.toLoc :: Nil - case Unavailable(source) => - msg"No member interface is known for this type." -> source.toLoc :: Nil - ) + case RecursiveAlias(symbol) => + msg"Following type alias '${symbol.nme}' does not expose a member interface." -> symbol.toLoc :: Nil + case Negated(source) => + msg"This negated type does not specify a member interface." -> source.toLoc :: Nil + case Unavailable(source) => + msg"No member interface is known for this type." -> source.toLoc :: Nil + ) + this match + case AbstractDeclaration(_, source) => result.withTypeOrigin(source) + case _ => result /** A type view that authorizes no member operations. The diagnostic reason is * excluded from equality so alternative witnesses do not add inference flow. @@ -730,12 +737,18 @@ final case class DynShape() extends CoreHeadShape: * locations, and the flattened chain are evaluated only when reporting an error. * Discovery retains one witness per reached shape to bound recursive paths. */ -final class ShapeProvenance(notes: => Ls[(Message, Opt[Loc])]): +final class ShapeProvenance private (notes: => Ls[(Message, Opt[Loc])], val typeOrigin: Opt[Term]): lazy val diagnosticNotes: Ls[(Message, Opt[Loc])] = notes def via(note: => (Message, Opt[Loc])): ShapeProvenance = - ShapeProvenance(note :: diagnosticNotes) + new ShapeProvenance(note :: diagnosticNotes, typeOrigin) + /** The written type restricting this observation belongs on the operation's + * primary message; storage/exposure reasons remain separate diagnostic notes. + */ + def withTypeOrigin(source: Term): ShapeProvenance = + new ShapeProvenance(diagnosticNotes, S(source)) object ShapeProvenance: + def apply(notes: => Ls[(Message, Opt[Loc])]): ShapeProvenance = new ShapeProvenance(notes, N) val empty = ShapeProvenance(Nil) /** An unknown input or the element of an opaque or widened spread can be any @@ -765,12 +778,10 @@ object UnknownValueShape: def at(source: Term): UnknownValueShape = UnknownValueShape(source)(ShapeProvenance(msg"The shape of this value is unknown." -> source.toLoc :: Nil)) def spread(source: Term, value: TermShape): UnknownValueShape = - UnknownValueShape(source)(ShapeProvenance { - val notes = value.applicationHead._1 match - case unknown: UnknownValueShape => unknown.provenance.diagnosticNotes - case _ => Nil - (msg"This spread has no known element shape." -> source.toLoc) :: notes - }) + val provenance = value.applicationHead._1 match + case unknown: UnknownValueShape => unknown.provenance + case _ => ShapeProvenance.empty + UnknownValueShape(source)(provenance.via(msg"This spread has no known element shape." -> source.toLoc)) object TupleShape: def apply(source: Term, elements: Ls[Element])(resolver: NewResolver): TupleShape = diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 08b51450dd..5ed8069c1c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -31,7 +31,6 @@ read([new Child]) //│ ║ l.28: fun read(xs: [Base]) = xs.map((item, ...) => item.extra) //│ ║ ^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.28: fun read(xs: [Base]) = xs.map((item, ...) => item.extra) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -64,10 +63,10 @@ fun singleton(x) = [x] :ge [1].nonexistent //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.65: [1].nonexistent +//│ ║ l.64: [1].nonexistent //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Tuple literal does not contain member 'nonexistent' -//│ ║ l.65: [1].nonexistent +//│ ║ l.64: [1].nonexistent //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -90,8 +89,8 @@ values([Item(4)]).map((item, ...) => item.value) //│ ║ l.162: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. -//│ ║ in Arrays.mls:88 -//│ ║ l.88: [1].map(x => x) +//│ ║ in Arrays.mls:87 +//│ ║ l.87: [1].map(x => x) //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 3 @@ -103,10 +102,10 @@ let xs = mut [Original(1)] set xs.0 = Replacement(2) xs.map((x, ...) => x.original) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.104: xs.map((x, ...) => x.original) +//│ ║ l.103: xs.map((x, ...) => x.original) //│ ║ ^^^^^^^^^^ //│ ╟── Instance of class 'Replacement' does not contain member 'original' -//│ ║ l.103: set xs.0 = Replacement(2) +//│ ║ l.102: set xs.0 = Replacement(2) //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Replacement(2)] @@ -138,7 +137,7 @@ new String("hello") is String :ge new Array[Int](1, 2) //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.139: new Array[Int](1, 2) +//│ ║ l.138: new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls @@ -157,7 +156,7 @@ new Array[Int](1, 2) :ge new Array[Int]() //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.158: new Array[Int]() +//│ ║ l.157: new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls @@ -209,10 +208,10 @@ class Item(val value: Int) class Total(val total: Int) [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.210: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) +//│ ║ l.209: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) //│ ║ ^^^^^^^^^^^ //│ ╟── Instance of class 'Total' does not contain member 'missing' -//│ ║ l.210: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) +//│ ║ l.209: [Item(1)].reduce((acc, item, ...) => acc.missing, Total(0)) //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -221,7 +220,7 @@ class Total(val total: Int) :e [1, 2].reduce((acc, x, ...) => acc + x) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.222: [1, 2].reduce((acc, x, ...) => acc + x) +//│ ║ l.221: [1, 2].reduce((acc, x, ...) => acc + x) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls @@ -261,10 +260,10 @@ let items = mut [Item(4)] class Item(val value: Int) (new Item(1)).[0] //│ ╔══[COMPILATION ERROR] Resolution error in array index -//│ ║ l.262: (new Item(1)).[0] +//│ ║ l.261: (new Item(1)).[0] //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Item' does not support checked array indexing. -//│ ║ l.262: (new Item(1)).[0] +//│ ║ l.261: (new Item(1)).[0] //│ ║ ^^^^^^^^^^^ //│ ╙── Use '![...]' for a dynamic access. //│ ═══[RUNTIME ERROR] TypeError: tmp48.at is not a function @@ -273,9 +272,8 @@ class Item(val value: Int) :e fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.274: fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] +//│ ║ l.273: fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Int' does not contain member 'whatever' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.274: fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] +//│ ║ l.273: fun first(xs: Array[Int]) = [xs![0].whatever, xs.[0].whatever] //│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls index d0ec7fdda3..0bd5099386 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls @@ -173,7 +173,6 @@ callbacks.push(x => x.extra) //│ ║ l.171: callbacks.push(x => x.extra) //│ ║ ^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.170: let callbacks = new mut Array[Base -> Int](0) //│ ╙── ^^^^ //│ = 1 @@ -197,11 +196,10 @@ callbacks.push(x => new Child(20)) private fun first(callback, ...tail) = callback first(...callbacks)(new Base).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.198: first(...callbacks)(new Base).extra +//│ ║ l.197: first(...callbacks)(new Base).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.195: let callbacks = new mut Array[Base -> Base](0) +//│ ║ l.194: let callbacks = new mut Array[Base -> Base](0) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ callbacks = [fun] @@ -218,16 +216,16 @@ let first = new Box(First(21)) let second = new Box(Second(22)) [first.copy().item.first, second.copy().item.second] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.219: [first.copy().item.first, second.copy().item.second] +//│ ║ l.217: [first.copy().item.first, second.copy().item.second] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'First' does not contain member 'second' -//│ ║ l.217: let first = new Box(First(21)) +//│ ║ l.215: let first = new Box(First(21)) //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.219: [first.copy().item.first, second.copy().item.second] +//│ ║ l.217: [first.copy().item.first, second.copy().item.second] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.218: let second = new Box(Second(22)) +//│ ║ l.216: let second = new Box(Second(22)) //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[21, 22]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 44cd97a1ff..62b77e346b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -22,7 +22,6 @@ hidden(new Child) //│ ║ l.19: fun hidden(base: Base) = base.extra //│ ║ ^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.19: fun hidden(base: Base) = base.extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -45,11 +44,10 @@ class Child extends Base with fun make(): Base = new Child make().extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.46: make().extra +//│ ║ l.45: make().extra //│ ║ ^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.45: fun make(): Base = new Child +//│ ║ l.44: fun make(): Base = new Child //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -62,18 +60,16 @@ val box: Base = new Child box.extra (new Child as Base).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.63: (new Child as Base).extra +//│ ║ l.61: (new Child as Base).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.63: (new Child as Base).extra +//│ ║ l.61: (new Child as Base).extra //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.62: box.extra +//│ ║ l.60: box.extra //│ ║ ^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.61: val box: Base = new Child +//│ ║ l.59: val box: Base = new Child //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Child { extra: 42 } @@ -87,14 +83,14 @@ class Box(val item) fun read(box: Box) = box.item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.87: fun read(box: Box) = box.item.extra +//│ ║ l.83: fun read(box: Box) = box.item.extra //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.86: class Box(val item) +//│ ║ l.82: class Box(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.87: fun read(box: Box) = box.item.extra +//│ ║ l.83: fun read(box: Box) = box.item.extra //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -109,11 +105,11 @@ class Box with fun read(box: Box) = box.item().extra read(new Box) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.109: fun read(box: Box) = box.item().extra +//│ ║ l.105: fun read(box: Box) = box.item().extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.109: fun read(box: Box) = box.item().extra +//│ ║ l.105: fun read(box: Box) = box.item().extra //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -160,13 +156,10 @@ read(() => Item(42)) :ge fun unused(factory: () -> Int) = factory(1) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.161: fun unused(factory: () -> Int) = factory(1) +//│ ║ l.157: fun unused(factory: () -> Int) = factory(1) //│ ║ ^^^^^^^^^^ //│ ╟── Function with a declared signature expected 0 arguments, but got 1 -//│ ║ l.161: fun unused(factory: () -> Int) = factory(1) -//│ ║ ^^^^^^^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.161: fun unused(factory: () -> Int) = factory(1) +//│ ║ l.157: fun unused(factory: () -> Int) = factory(1) //│ ╙── ^^^^^^^^^ // A callable annotation also hides extra members of the concrete result. @@ -177,11 +170,10 @@ class Child extends Base with fun read(factory: () -> Base) = factory().extra read(() => new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.177: fun read(factory: () -> Base) = factory().extra +//│ ║ l.170: fun read(factory: () -> Base) = factory().extra //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.177: fun read(factory: () -> Base) = factory().extra +//│ ║ l.170: fun read(factory: () -> Base) = factory().extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -194,11 +186,10 @@ fun hidden: (Base) -> Int fun hidden(base) = base.extra hidden(new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.194: fun hidden(base) = base.extra +//│ ║ l.186: fun hidden(base) = base.extra //│ ║ ^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.193: fun hidden: (Base) -> Int +//│ ║ l.185: fun hidden: (Base) -> Int //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -211,11 +202,10 @@ fun make: () -> Base fun make() = new Child make().extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.212: make().extra +//│ ║ l.203: make().extra //│ ║ ^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.210: fun make: () -> Base +//│ ║ l.201: fun make: () -> Base //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -239,16 +229,16 @@ class Box[T] type Loop[T] = Loop[Box[T]] fun unused(value: Loop[Int]) = value.field //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.239: type Loop[T] = Loop[Box[T]] +//│ ║ l.229: type Loop[T] = Loop[Box[T]] //│ ║ ^^^^^^^^^^^^ //│ ╟── Unguarded recursive type alias 'Loop'. -//│ ║ l.239: type Loop[T] = Loop[Box[T]] +//│ ║ l.229: type Loop[T] = Loop[Box[T]] //│ ║ ^^^^ //│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. -//│ ║ l.239: type Loop[T] = Loop[Box[T]] +//│ ║ l.229: type Loop[T] = Loop[Box[T]] //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] This selection of member 'field' has no resolved target -//│ ║ l.240: fun unused(value: Loop[Int]) = value.field +//│ ║ l.230: fun unused(value: Loop[Int]) = value.field //│ ╙── ^^^^^^^^^^^ // A concrete instantiation cannot authorize calls through an opaque type parameter. @@ -256,16 +246,13 @@ fun unused(value: Loop[Int]) = value.field private fun invoke[T](value: T) = value() invoke[() -> Int](() => 42) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.256: private fun invoke[T](value: T) = value() +//│ ║ l.246: private fun invoke[T](value: T) = value() //│ ║ ^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.256: private fun invoke[T](value: T) = value() -//│ ║ ^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.256: private fun invoke[T](value: T) = value() +//│ ║ l.246: private fun invoke[T](value: T) = value() //│ ║ ^ //│ ╟── Type parameter 'T' does not specify a member interface. -//│ ║ l.256: private fun invoke[T](value: T) = value() +//│ ║ l.246: private fun invoke[T](value: T) = value() //│ ╙── ^ //│ = 42 @@ -278,14 +265,14 @@ class Box(val item) fun read(box: Box) = box.Box#item.extra read(Box(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.278: fun read(box: Box) = box.Box#item.extra +//│ ║ l.265: fun read(box: Box) = box.Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.277: class Box(val item) +//│ ║ l.264: class Box(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.278: fun read(box: Box) = box.Box#item.extra +//│ ║ l.265: fun read(box: Box) = box.Box#item.extra //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -316,11 +303,10 @@ class Child extends Base with fun identity[T](x: T): T = x identity[Base](new Child).extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.317: identity[Base](new Child).extra +//│ ║ l.304: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.317: identity[Base](new Child).extra +//│ ║ l.304: identity[Base](new Child).extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -361,18 +347,16 @@ class Box[T](val value: T) (new Box[Base](new Child)).value.extra Box[Base](new Child).value.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.361: (new Box[Base](new Child)).value.extra +//│ ║ l.347: (new Box[Base](new Child)).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.361: (new Box[Base](new Child)).value.extra +//│ ║ l.347: (new Box[Base](new Child)).value.extra //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.362: Box[Base](new Child).value.extra +//│ ║ l.348: Box[Base](new Child).value.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.362: Box[Base](new Child).value.extra +//│ ║ l.348: Box[Base](new Child).value.extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -415,11 +399,11 @@ class Box with val item = new Item (new Box as Box).Box#item.extra //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.416: (new Box as Box).Box#item.extra +//│ ║ l.400: (new Box as Box).Box#item.extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.416: (new Box as Box).Box#item.extra +//│ ║ l.400: (new Box as Box).Box#item.extra //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -435,11 +419,11 @@ module Types with fun hidden(module types: Types) = types.Item.extra hidden(Types) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.435: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.419: fun hidden(module types: Types) = types.Item.extra //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.435: fun hidden(module types: Types) = types.Item.extra +//│ ║ l.419: fun hidden(module types: Types) = types.Item.extra //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -459,11 +443,10 @@ class Child extends Base with fun apply[A](f: A -> A, x: A): A = f(x) apply[Base](arg => (arg.extra; arg), new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.460: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ l.444: apply[Base](arg => (arg.extra; arg), new Child) //│ ║ ^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.460: apply[Base](arg => (arg.extra; arg), new Child) +//│ ║ l.444: apply[Base](arg => (arg.extra; arg), new Child) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -485,11 +468,10 @@ class Child extends Base with fun read(xs: [Base, Int]) = xs.0.extra read([new Child, 0]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.485: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ l.468: fun read(xs: [Base, Int]) = xs.0.extra //│ ║ ^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.485: fun read(xs: [Base, Int]) = xs.0.extra +//│ ║ l.468: fun read(xs: [Base, Int]) = xs.0.extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -509,10 +491,10 @@ let xs = mut [First(1)] set xs.0 = Second(2) [...xs].0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.510: [...xs].0.first +//│ ║ l.492: [...xs].0.first //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.509: set xs.0 = Second(2) +//│ ║ l.491: set xs.0 = Second(2) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -521,10 +503,10 @@ set xs.0 = Second(2) :ge [1, 2].2 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.522: [1, 2].2 +//│ ║ l.504: [1, 2].2 //│ ║ ^^^^^^^^ //│ ╟── Tuple literal does not contain member '2' -//│ ║ l.522: [1, 2].2 +//│ ║ l.504: [1, 2].2 //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -561,14 +543,14 @@ private fun identity(value) = value fun read(box: Annotated) = identity(box.item).extra read(Child(new Item)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.561: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.543: fun read(box: Annotated) = identity(box.item).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. -//│ ║ l.557: class Parent(val item) +//│ ║ l.539: class Parent(val item) //│ ║ ^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. -//│ ║ l.561: fun read(box: Annotated) = identity(box.item).extra +//│ ║ l.543: fun read(box: Annotated) = identity(box.item).extra //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -580,16 +562,16 @@ class Box(val callback) fun invoke(box: Box) = box.callback() invoke(Box(() => 42)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.580: fun invoke(box: Box) = box.callback() +//│ ║ l.562: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.580: fun invoke(box: Box) = box.callback() +//│ ║ l.562: fun invoke(box: Box) = box.callback() //│ ║ ^^^^^^^^^^^^ //│ ╟── Constructor parameter 'callback' has no type annotation. -//│ ║ l.579: class Box(val callback) +//│ ║ l.561: class Box(val callback) //│ ║ ^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'callback'. -//│ ║ l.580: fun invoke(box: Box) = box.callback() +//│ ║ l.562: fun invoke(box: Box) = box.callback() //│ ╙── ^^^ //│ = 42 @@ -598,14 +580,13 @@ invoke(Box(() => 42)) class Box[X](val value: [X, X]) fun foo[A](x: Box[A]) = x.value.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.599: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.581: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.599: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.581: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.599: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.581: fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ // Concrete callers cannot hide the annotation responsible for an abstract tuple element. @@ -616,14 +597,13 @@ class Box[X](val value: [X, X]) private fun foo[A](x: Box[A]) = x.value.0.oops foo[Item](Box[Item]([new Item, new Item])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.616: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.597: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.616: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.597: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.616: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.597: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -633,28 +613,26 @@ class Box[X](val value: [X, X]) type Wrapped[T] = Box[T] fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.634: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.614: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.634: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.614: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.634: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.614: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╙── ^ // Tuple annotations do not need a nominal receiver to produce an abstract element. :ge fun foo[A](x: [A, A]) = x.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.648: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.627: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.648: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.627: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.648: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.627: fun foo[A](x: [A, A]) = x.0.oops //│ ╙── ^ // An unannotated field can be reached through a function result annotation and an alias. @@ -663,14 +641,14 @@ class Box(val value) type Wrapped = Box fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.664: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.642: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.662: class Box(val value) +//│ ║ l.640: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.664: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.642: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╙── ^^^^^^^ // TODO: ascription preserves receiver flow for the selected field's missing type. @@ -681,14 +659,14 @@ class Item with class Box(val value) (Box(new Item) as Box).value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.682: (Box(new Item) as Box).value.oops +//│ ║ l.660: (Box(new Item) as Box).value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.681: class Box(val value) +//│ ║ l.659: class Box(val value) //│ ║ ^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.682: (Box(new Item) as Box).value.oops +//│ ║ l.660: (Box(new Item) as Box).value.oops //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -697,14 +675,13 @@ class Box(val value) :ge fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.698: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.676: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.698: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.676: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.698: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.676: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╙── ^ // A concrete annotation supplies the nested member interface without losing tuple precision. @@ -721,14 +698,14 @@ foo(Box[Item]([new Item, new Item])) class Pair[A, B](val value: [A, B]) fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.722: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.699: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.722: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.699: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^ //│ ╟── 'omittedSecond' is exposed by this compilation unit. -//│ ║ l.722: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.699: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // An unfilled omitted alias argument likewise cannot justify an arbitrary member. @@ -736,12 +713,12 @@ fun omittedSecond(x: Pair[Int]) = x.value.1.oops type HalfPair[T] = Pair[Int, T] fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.737: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.714: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.737: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.714: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^ //│ ╟── 'omittedAlias' is exposed by this compilation unit. -//│ ║ l.737: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.714: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 731156acdf..f217966084 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -49,7 +49,6 @@ class Child extends Base with //│ ║ l.47: [new Child].map[Base]((x, ...) => x).map((x, ...) => x.extra) //│ ║ ^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.47: [new Child].map[Base]((x, ...) => x).map((x, ...) => x.extra) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -58,7 +57,7 @@ class Child extends Base with :ge [1].map[Int, Int]((x, ...) => x) //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.59: [1].map[Int, Int]((x, ...) => x) +//│ ║ l.58: [1].map[Int, Int]((x, ...) => x) //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls @@ -87,11 +86,10 @@ class Methods with fun test(m: Methods) = m.identity[Base](new Child).extra test(new Methods) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.87: fun test(m: Methods) = m.identity[Base](new Child).extra +//│ ║ l.86: fun test(m: Methods) = m.identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.87: fun test(m: Methods) = m.identity[Base](new Child).extra +//│ ║ l.86: fun test(m: Methods) = m.identity[Base](new Child).extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls index 1f008f3d1c..84b1559ab5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls @@ -52,7 +52,6 @@ first(Pair([Child(7), 0])) //│ ║ l.49: private fun first(pair: Pair[Base]) = pair.value.0.extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.49: private fun first(pair: Pair[Base]) = pair.value.0.extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -64,14 +63,14 @@ class Box[T](val item: T) fun read(box: Box) = box.item.value read(Box(Item(8))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.64: fun read(box: Box) = box.item.value +//│ ║ l.63: fun read(box: Box) = box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── Parameter 'box' admits values of unknown shape. -//│ ║ l.64: fun read(box: Box) = box.item.value +//│ ║ l.63: fun read(box: Box) = box.item.value //│ ║ ^^^^^^^^ //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.64: fun read(box: Box) = box.item.value +//│ ║ l.63: fun read(box: Box) = box.item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -102,16 +101,16 @@ type SomeBox = Box private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] both(Box(First(11)), Box(Second(12))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.102: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ l.101: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'First' does not contain member 'second' -//│ ║ l.103: both(Box(First(11)), Box(Second(12))) +//│ ║ l.102: both(Box(First(11)), Box(Second(12))) //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.102: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] +//│ ║ l.101: private fun both(left: SomeBox, right: SomeBox) = [left.item.first, right.item.second] //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.103: both(Box(First(11)), Box(Second(12))) +//│ ║ l.102: both(Box(First(11)), Box(Second(12))) //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[11, 12]', got: 'undefined' @@ -121,13 +120,13 @@ both(Box(First(11)), Box(Second(12))) class Box[T](val item: T) private fun excess(value: Box[Int, Str]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.122: private fun excess(value: Box[Int, Str]) = () +//│ ║ l.121: private fun excess(value: Box[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^ //│ ╟── Type 'Box' accepts at most 1 type argument, but got 2. -//│ ║ l.122: private fun excess(value: Box[Int, Str]) = () +//│ ║ l.121: private fun excess(value: Box[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. -//│ ║ l.121: class Box[T](val item: T) +//│ ║ l.120: class Box[T](val item: T) //│ ╙── ^ // A type alias checks its own argument list before interpreting the body. @@ -136,13 +135,13 @@ class Box[T](val item: T) type Alias[A] = Box[A] private fun excess(value: Alias[Int, Str]) = () //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.137: private fun excess(value: Alias[Int, Str]) = () +//│ ║ l.136: private fun excess(value: Alias[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Type 'Alias' accepts at most 1 type argument, but got 2. -//│ ║ l.137: private fun excess(value: Alias[Int, Str]) = () +//│ ║ l.136: private fun excess(value: Alias[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. -//│ ║ l.136: type Alias[A] = Box[A] +//│ ║ l.135: type Alias[A] = Box[A] //│ ╙── ^^^^^^ // Annotated results do not hide missing input types from exposure checking. @@ -152,11 +151,11 @@ class Box[T](val item: T) fun read(box: Box): Int = box.item.value read(Box(Item(13))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.152: fun read(box: Box): Int = box.item.value +//│ ║ l.151: fun read(box: Box): Int = box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.152: fun read(box: Box): Int = box.item.value +//│ ║ l.151: fun read(box: Box): Int = box.item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -167,14 +166,14 @@ class Box[T](val item: T) fun read(callback: () -> Box) = callback().item.value read(() => Box(Item(14))) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.167: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.166: fun read(callback: () -> Box) = callback().item.value //│ ║ ^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── Parameter 'callback' admits values of unknown shape. -//│ ║ l.167: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.166: fun read(callback: () -> Box) = callback().item.value //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── 'read' is exposed by this compilation unit. -//│ ║ l.167: fun read(callback: () -> Box) = callback().item.value +//│ ║ l.166: fun read(callback: () -> Box) = callback().item.value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls index 5fe7d2b2f5..170d416d34 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls @@ -24,7 +24,6 @@ foo({a: 1}) //│ ║ l.21: fun foo[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.21: fun foo[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. @@ -37,14 +36,13 @@ foo({a: 1}) private fun privateGeneric[A](x: A) = x.a privateGeneric({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.37: private fun privateGeneric[A](x: A) = x.a +//│ ║ l.36: private fun privateGeneric[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.37: private fun privateGeneric[A](x: A) = x.a +//│ ║ l.36: private fun privateGeneric[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.37: private fun privateGeneric[A](x: A) = x.a +//│ ║ l.36: private fun privateGeneric[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -54,14 +52,13 @@ private class GenericClass[A](value: A) with fun read() = (() => value.a)() (new GenericClass({a: 1})).read() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.54: fun read() = (() => value.a)() +//│ ║ l.52: fun read() = (() => value.a)() //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.53: private class GenericClass[A](value: A) with +//│ ║ l.51: private class GenericClass[A](value: A) with //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.53: private class GenericClass[A](value: A) with +//│ ║ l.51: private class GenericClass[A](value: A) with //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -88,16 +85,16 @@ foo(new A(1)) fun apply(f) = f(1) apply(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.88: fun apply(f) = f(1) +//│ ║ l.85: fun apply(f) = f(1) //│ ║ ^^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.88: fun apply(f) = f(1) +//│ ║ l.85: fun apply(f) = f(1) //│ ║ ^ //│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.88: fun apply(f) = f(1) +//│ ║ l.85: fun apply(f) = f(1) //│ ║ ^ //│ ╟── 'apply' is exposed by this compilation unit. -//│ ║ l.88: fun apply(f) = f(1) +//│ ║ l.85: fun apply(f) = f(1) //│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ = 1 @@ -111,17 +108,17 @@ apply(x => x) :e fun escaped() = x => x.a //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.112: fun escaped() = x => x.a +//│ ║ l.109: fun escaped() = x => x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.112: fun escaped() = x => x.a +//│ ║ l.109: fun escaped() = x => x.a //│ ║ ^ //│ ╟── This value is returned here. -//│ ║ l.112: fun escaped() = x => x.a +//│ ║ l.109: fun escaped() = x => x.a //│ ║ ^^^^^^^^ //│ ╟── 'escaped' is exposed by this compilation unit. -//│ ║ l.112: fun escaped() = x => x.a +//│ ║ l.109: fun escaped() = x => x.a //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ :e @@ -129,17 +126,17 @@ private fun hidden(x) = x.a val escaped = [hidden] hidden({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.128: private fun hidden(x) = x.a +//│ ║ l.125: private fun hidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.128: private fun hidden(x) = x.a +//│ ║ l.125: private fun hidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this tuple. -//│ ║ l.129: val escaped = [hidden] +//│ ║ l.126: val escaped = [hidden] //│ ║ ^^^^^^^^ //│ ╟── 'escaped' is exposed by this compilation unit. -//│ ║ l.129: val escaped = [hidden] +//│ ║ l.126: val escaped = [hidden] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ escaped = [fun hidden] @@ -149,17 +146,17 @@ private fun hidden(x) = x.a val escaped = {f: hidden} hidden({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.148: private fun hidden(x) = x.a +//│ ║ l.145: private fun hidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.148: private fun hidden(x) = x.a +//│ ║ l.145: private fun hidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.149: val escaped = {f: hidden} +//│ ║ l.146: val escaped = {f: hidden} //│ ║ ^ //│ ╟── 'escaped' is exposed by this compilation unit. -//│ ║ l.149: val escaped = {f: hidden} +//│ ║ l.146: val escaped = {f: hidden} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ escaped = {f: fun hidden} @@ -184,20 +181,20 @@ private fun hiddenRecord(x) = x.a fun exportRecord() = {callback: hiddenRecord} hiddenRecord({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.183: private fun hiddenRecord(x) = x.a +//│ ║ l.180: private fun hiddenRecord(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.183: private fun hiddenRecord(x) = x.a +//│ ║ l.180: private fun hiddenRecord(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.184: fun exportRecord() = {callback: hiddenRecord} +//│ ║ l.181: fun exportRecord() = {callback: hiddenRecord} //│ ║ ^^^^^^^^ //│ ╟── This value is returned here. -//│ ║ l.184: fun exportRecord() = {callback: hiddenRecord} +//│ ║ l.181: fun exportRecord() = {callback: hiddenRecord} //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── 'exportRecord' is exposed by this compilation unit. -//│ ║ l.184: fun exportRecord() = {callback: hiddenRecord} +//│ ║ l.181: fun exportRecord() = {callback: hiddenRecord} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -209,16 +206,16 @@ class CopyBox(val value) with fun copy() = CopyBox(value) CopyBox({answer: 42}).copy().value.answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.210: CopyBox({answer: 42}).copy().value.answer +//│ ║ l.207: CopyBox({answer: 42}).copy().value.answer //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. //│ ╟── Parameter 'value' admits values of unknown shape. -//│ ║ l.208: class CopyBox(val value) with +//│ ║ l.205: class CopyBox(val value) with //│ ║ ^^^^^ //│ ╟── 'CopyBox' is exposed by this compilation unit. -//│ ║ l.208: class CopyBox(val value) with +//│ ║ l.205: class CopyBox(val value) with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.209: fun copy() = CopyBox(value) +//│ ║ l.206: fun copy() = CopyBox(value) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -231,17 +228,17 @@ oldHidden({a: 1}) :e val oldEscape = {callback: oldHidden} //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.227: private fun oldHidden(x) = x.a +//│ ║ l.224: private fun oldHidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.227: private fun oldHidden(x) = x.a +//│ ║ l.224: private fun oldHidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.232: val oldEscape = {callback: oldHidden} +//│ ║ l.229: val oldEscape = {callback: oldHidden} //│ ║ ^^^^^^^^ //│ ╟── 'oldEscape' is exposed by this compilation unit. -//│ ║ l.232: val oldEscape = {callback: oldHidden} +//│ ║ l.229: val oldEscape = {callback: oldHidden} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ oldEscape = {callback: fun oldHidden} @@ -250,17 +247,16 @@ val oldEscape = {callback: oldHidden} fun spreadRecord[A](x: A) = ({...x}).a spreadRecord({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.250: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ l.247: fun spreadRecord[A](x: A) = ({...x}).a //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. +//│ ║ l.247: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ ^ //│ ╟── This spread has no known element shape. -//│ ║ l.250: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ l.247: fun spreadRecord[A](x: A) = ({...x}).a //│ ║ ^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.250: fun spreadRecord[A](x: A) = ({...x}).a -//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.250: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ l.247: fun spreadRecord[A](x: A) = ({...x}).a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -269,17 +265,16 @@ spreadRecord({a: 1}) fun spreadTuple[A](x: A) = [...x].0.a spreadTuple([{a: 1}]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.269: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ l.265: fun spreadTuple[A](x: A) = [...x].0.a //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ║ l.265: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ ^ //│ ╟── This spread has no known element shape. -//│ ║ l.269: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ l.265: fun spreadTuple[A](x: A) = [...x].0.a //│ ║ ^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.269: fun spreadTuple[A](x: A) = [...x].0.a -//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.269: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ l.265: fun spreadTuple[A](x: A) = [...x].0.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -294,20 +289,20 @@ private fun recursiveHidden(x) = x.a fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) recursiveHidden({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.293: private fun recursiveHidden(x) = x.a +//│ ║ l.288: private fun recursiveHidden(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.293: private fun recursiveHidden(x) = x.a +//│ ║ l.288: private fun recursiveHidden(x) = x.a //│ ║ ^ //│ ╟── This value is stored in this record field. -//│ ║ l.294: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ l.289: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) //│ ║ ^^^^^^^^ //│ ╟── This value is returned here. -//│ ║ l.294: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ l.289: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── 'recursiveExport' is exposed by this compilation unit. -//│ ║ l.294: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) +//│ ║ l.289: fun recursiveExport(n) = if n == 0 then {callback: recursiveHidden} else recursiveExport(n - 1) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -318,23 +313,23 @@ class Shadowed { } val Shadowed = 0 //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.317: fun bad(x) = x.a +//│ ║ l.312: fun bad(x) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.317: fun bad(x) = x.a +//│ ║ l.312: fun bad(x) = x.a //│ ║ ^ //│ ╟── Member 'bad' is accessible here. -//│ ║ l.317: fun bad(x) = x.a +//│ ║ l.312: fun bad(x) = x.a //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── 'Shadowed' is exposed by this compilation unit. -//│ ║ l.316: class Shadowed { +//│ ║ l.311: class Shadowed { //│ ║ ^^^^^^^^^^^^^^^^ -//│ ║ l.317: fun bad(x) = x.a +//│ ║ l.312: fun bad(x) = x.a //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.318: } +//│ ║ l.313: } //│ ║ ^ -//│ ║ l.319: val Shadowed = 0 +//│ ║ l.314: val Shadowed = 0 //│ ╙── ^^^^^^^^^^^^^^^^ //│ Shadowed = 0 @@ -344,17 +339,17 @@ class Holder(val value) fun inspect(x) = if x is Holder(y) then y.a else 0 inspect(Holder({a: 1})) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.344: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.339: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This constructor field may contain a value of unknown shape. -//│ ║ l.343: class Holder(val value) +//│ ║ l.338: class Holder(val value) //│ ║ ^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.344: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.339: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ║ ^ //│ ╟── 'inspect' is exposed by this compilation unit. -//│ ║ l.344: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.339: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -372,17 +367,17 @@ class GenericHolder[A](val value: A) fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 inspectGeneric(GenericHolder({a: 1})) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.372: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.367: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This constructor field may contain a value of unknown shape. -//│ ║ l.371: class GenericHolder[A](val value: A) +//│ ║ l.366: class GenericHolder[A](val value: A) //│ ║ ^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.372: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.367: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ║ ^ //│ ╟── 'inspectGeneric' is exposed by this compilation unit. -//│ ║ l.372: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.367: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls index 831c961e22..5dd3c4412d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/MutableArrays.mls @@ -129,7 +129,6 @@ xs.0.extra //│ ║ l.127: xs.0.extra //│ ║ ^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.125: let xs = (mut [] as Array[Base]) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -143,10 +142,10 @@ let xs = mut [First(1)] set xs.[0] = Second(2) xs.0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.144: xs.0.first +//│ ║ l.143: xs.0.first //│ ║ ^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.143: set xs.[0] = Second(2) +//│ ║ l.142: set xs.[0] = Second(2) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -158,10 +157,10 @@ let xs = mut [First(1)] xs.fill(Second(2)) xs.0.first //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.159: xs.0.first +//│ ║ l.158: xs.0.first //│ ║ ^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' -//│ ║ l.158: xs.fill(Second(2)) +//│ ║ l.157: xs.fill(Second(2)) //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Second(2)] @@ -171,25 +170,25 @@ xs.0.first module Exposed with fun make() = mut [x => x.missing] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.172: fun make() = mut [x => x.missing] +//│ ║ l.171: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^ //│ ╟── Cannot resolve member 'missing' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.172: fun make() = mut [x => x.missing] +//│ ║ l.171: fun make() = mut [x => x.missing] //│ ║ ^ //│ ╟── This value is stored in this array. -//│ ║ l.172: fun make() = mut [x => x.missing] +//│ ║ l.171: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── This value is returned here. -//│ ║ l.172: fun make() = mut [x => x.missing] +//│ ║ l.171: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'make' is accessible here. -//│ ║ l.172: fun make() = mut [x => x.missing] +//│ ║ l.171: fun make() = mut [x => x.missing] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── 'Exposed' is exposed by this compilation unit. -//│ ║ l.171: module Exposed with +//│ ║ l.170: module Exposed with //│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ║ l.172: fun make() = mut [x => x.missing] +//│ ║ l.171: fun make() = mut [x => x.missing] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Explicit constructor type arguments remain opaque when methods receive subtypes. @@ -201,11 +200,10 @@ let xs = new mut Array[Base](0) xs.push(new Child) xs.map((x, ...) => x.extra) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.202: xs.map((x, ...) => x.extra) +//│ ║ l.201: xs.map((x, ...) => x.extra) //│ ║ ^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.200: let xs = new mut Array[Base](0) +//│ ║ l.199: let xs = new mut Array[Base](0) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ xs = [Child { extra: 1 }] @@ -226,7 +224,7 @@ let xs = mut [] xs.splice(0, 0, Item(2)) xs.0.value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.227: xs.0.value +//│ ║ l.225: xs.0.value //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls index 7a23b3d7e8..59d2691829 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls @@ -161,7 +161,6 @@ identity[Base](new Child).extra //│ ║ l.159: identity[Base](new Child).extra //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.159: identity[Base](new Child).extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -172,13 +171,10 @@ identity[Base](new Child).extra let identity = (x => x) as [A] -> A -> A identity[Int, Int](1) //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.173: identity[Int, Int](1) +//│ ║ l.172: identity[Int, Int](1) //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Function with a declared signature expected 1 type argument, but got 2 -//│ ║ l.172: let identity = (x => x) as [A] -> A -> A -//│ ║ ^^^^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.172: let identity = (x => x) as [A] -> A -> A +//│ ║ l.171: let identity = (x => x) as [A] -> A -> A //│ ╙── ^^^^^^ //│ = 1 //│ identity = fun identity diff --git a/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls b/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls index d767fc0fd2..d83ab1348b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls +++ b/hkmc2/shared/src/test/mlscript/newres/QuantifiedBounds.mls @@ -62,7 +62,6 @@ private fun read() = make[Base]().extra //│ ║ l.60: private fun read() = make[Base]().extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.60: private fun read() = make[Base]().extra //│ ╙── ^^^^ @@ -92,7 +91,7 @@ class Item(val value: Int) fun read: [A restricts Item] -> A -> Int fun read(x) = x.value //│ ╔══[COMPILATION ERROR] This selection of member 'value' has no resolved target -//│ ║ l.93: fun read(x) = x.value +//│ ║ l.92: fun read(x) = x.value //│ ╙── ^^^^^^^ // Capturing an enclosing binder specializes the bounds as well as the result. @@ -115,14 +114,13 @@ class Provider[T] with fun make = make private fun read(provider: Provider[Item]) = provider.make.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.116: private fun read(provider: Provider[Item]) = provider.make.value +//│ ║ l.115: private fun read(provider: Provider[Item]) = provider.make.value //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.114: fun make: [A restricts T] -> A +//│ ║ l.113: fun make: [A restricts T] -> A //│ ║ ^ //│ ╟── Type parameter 'T' does not specify a member interface. -//│ ║ l.113: class Provider[T] with +//│ ║ l.112: class Provider[T] with //│ ╙── ^ // Without that recursive checking edge, the declared receiver supplies its diff --git a/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls index fb0cc4e628..0e948eaa6e 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecordInterfaces.mls @@ -18,9 +18,6 @@ hidden(name: "visible", extra: 1) //│ ║ ^^^^^^^^^^^^ //│ ╟── Record type does not contain member 'extra' //│ ║ l.14: fun hidden(fields: {name: Str}) = fields.extra -//│ ║ ^^^^^^^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.14: fun hidden(fields: {name: Str}) = fields.extra //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -28,14 +25,13 @@ hidden(name: "visible", extra: 1) private fun opaque[A](fields: {value: A}) = fields.value.answer opaque(value: {answer: 42}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.28: private fun opaque[A](fields: {value: A}) = fields.value.answer +//│ ║ l.25: private fun opaque[A](fields: {value: A}) = fields.value.answer //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.28: private fun opaque[A](fields: {value: A}) = fields.value.answer +//│ ║ l.25: private fun opaque[A](fields: {value: A}) = fields.value.answer //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.28: private fun opaque[A](fields: {value: A}) = fields.value.answer +//│ ║ l.25: private fun opaque[A](fields: {value: A}) = fields.value.answer //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -59,15 +55,10 @@ callback(f: x => x.toUpperCase()) fun expose(): {f: Object -> Int} = {f: x => x.answer} expose().f({answer: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.59: fun expose(): {f: Object -> Int} = {f: x => x.answer} +//│ ║ l.55: fun expose(): {f: Object -> Int} = {f: x => x.answer} //│ ║ ^^^^^^^^ //│ ╟── Value of type 'Object' does not contain member 'answer' -//│ ║ in ../decls/Prelude.mls -//│ ║ l.7: fun toString(): Str -//│ ║ ^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ in RecordInterfaces.mls:59 -//│ ║ l.59: fun expose(): {f: Object -> Int} = {f: x => x.answer} +//│ ║ l.55: fun expose(): {f: Object -> Int} = {f: x => x.answer} //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 09865d7813..383da64d4b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -205,7 +205,6 @@ foo({a: 1}) //│ ║ l.202: fun foo[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.202: fun foo[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. @@ -217,14 +216,13 @@ foo({a: 1}) private fun foo[A](x: A) = x.a foo({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.217: private fun foo[A](x: A) = x.a +//│ ║ l.216: private fun foo[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.217: private fun foo[A](x: A) = x.a +//│ ║ l.216: private fun foo[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.217: private fun foo[A](x: A) = x.a +//│ ║ l.216: private fun foo[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index 4cda96ecb3..bfda3672d4 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -237,9 +237,6 @@ invokeUnknown(...opaque([() => 95])) //│ ╟── Value with no known interface cannot be called like a function. //│ ║ l.173: fun opaque(values): Array[Any] = values //│ ║ ^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.173: fun opaque(values): Array[Any] = values -//│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. //│ ║ in ../decls/Prelude.mls //│ ║ l.2: declare type Any @@ -252,10 +249,9 @@ fun inspectUnknown(x) = x.value inspectUnknown(Box(96)) inspectUnknown(...opaque([Box(96)])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.251: fun inspectUnknown(x) = x.value +//│ ║ l.248: fun inspectUnknown(x) = x.value //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.173: fun opaque(values): Array[Any] = values //│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. @@ -268,7 +264,7 @@ inspectUnknown(...opaque([Box(96)])) :e four(...opaque([]), 1, 2, 3, 4, 5) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.269: four(...opaque([]), 1, 2, 3, 4, 5) +//│ ║ l.265: four(...opaque([]), 1, 2, 3, 4, 5) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got at least 5 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -280,14 +276,11 @@ four(...opaque([]), 1, 2, 3, 4, 5) fun invokeRest(f, x, ...tail) = f() invokeRest(...opaque([() => 97]), Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.280: fun invokeRest(f, x, ...tail) = f() +//│ ║ l.276: fun invokeRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value with no known interface cannot be called like a function. //│ ║ l.173: fun opaque(values): Array[Any] = values //│ ║ ^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.173: fun opaque(values): Array[Any] = values -//│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. //│ ║ in ../decls/Prelude.mls //│ ║ l.2: declare type Any @@ -325,16 +318,16 @@ invokeTypedRest(...callbacks, Box(0)) fun invokePublicRest(f, x, ...tail) = f() invokePublicRest(...callbacks, Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.325: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.318: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.325: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.318: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.325: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.318: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── 'invokePublicRest' is exposed by this compilation unit. -//│ ║ l.325: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.318: fun invokePublicRest(f, x, ...tail) = f() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 97 @@ -370,11 +363,10 @@ class Child extends Base with fun readBase(xs: Array[Base]) = first(...xs).extra readBase([new Child]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.370: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ║ l.363: fun readBase(xs: Array[Base]) = first(...xs).extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.370: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ║ l.363: fun readBase(xs: Array[Base]) = first(...xs).extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -412,22 +404,16 @@ invokeUnannotatedArray(...unknownCallbacks) private fun firstInArray(xs) = first(...xs) [firstInArray(lefts).left, firstInArray(rights).right] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.413: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.405: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Right' does not contain member 'left' -//│ ║ l.384: class Right(val right: Int) -//│ ║ ^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.392: let rights = new mut Array[Right](0) +//│ ║ l.384: let rights = new mut Array[Right](0) //│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.413: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.405: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Left' does not contain member 'right' -//│ ║ l.383: class Left(val left: Int) -//│ ║ ^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.391: let lefts = new mut Array[Left](0) +//│ ║ l.383: let lefts = new mut Array[Left](0) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[43, 44]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls b/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls index e7cf4212a5..2884f8bb9a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls @@ -32,7 +32,6 @@ opaque([{name: "hidden"}, 1]) //│ ║ l.29: fun opaque[A](pair: [A, Int]) = if pair is [item, _] then item.name //│ ║ ^^^^^^^^^ //│ ╟── Cannot resolve member 'name' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.29: fun opaque[A](pair: [A, Int]) = if pair is [item, _] then item.name //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls index aef9893f38..893361f3ef 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls @@ -72,7 +72,6 @@ xs.0.value //│ ║ l.70: xs.0.value //│ ║ ^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.69: let xs = (mut [Item(5)] as Array[Any]) //│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. @@ -87,36 +86,35 @@ xs.0.value declare type Hidden fun inspect(value: Hidden) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.88: fun inspect(value: Hidden) = value.member +//│ ║ l.87: fun inspect(value: Hidden) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.88: fun inspect(value: Hidden) = value.member +//│ ║ l.87: fun inspect(value: Hidden) = value.member //│ ║ ^^^^^^ //│ ╟── Type 'Hidden' is declared without a member interface. -//│ ║ l.87: declare type Hidden +//│ ║ l.86: declare type Hidden //│ ╙── ^^^^^^ // Unsupported singleton interfaces must not be described as abstract declarations. :ge fun inspect(value: 1) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.102: fun inspect(value: 1) = value.member +//│ ║ l.100: fun inspect(value: 1) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. //│ ╟── No member interface is known for this type. -//│ ║ l.102: fun inspect(value: 1) = value.member +//│ ║ l.100: fun inspect(value: 1) = value.member //│ ╙── ^ // Negation has a different explanation from an abstract declaration. :ge fun inspect(value: ~Int) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.113: fun inspect(value: ~Int) = value.member +//│ ║ l.111: fun inspect(value: ~Int) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. //│ ╟── This negated type does not specify a member interface. -//│ ║ l.113: fun inspect(value: ~Int) = value.member +//│ ║ l.111: fun inspect(value: ~Int) = value.member //│ ╙── ^^^^ // Calling a value retains the same explanation as selecting its members. @@ -124,13 +122,13 @@ fun inspect(value: ~Int) = value.member class Item(val value: Int) fun invoke(xs: Array[in Item]) = xs.0() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.125: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ l.123: fun invoke(xs: Array[in Item]) = xs.0() //│ ║ ^^^^^^ //│ ╟── Value with no known interface cannot be called like a function. -//│ ║ l.125: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ l.123: fun invoke(xs: Array[in Item]) = xs.0() //│ ║ ^^^^^^^ //│ ╟── This type argument specifies only an input bound. -//│ ║ l.125: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ l.123: fun invoke(xs: Array[in Item]) = xs.0() //│ ║ ^^^^^^^ //│ ╙── Values read through it have no output bound more specific than 'Any'. @@ -143,14 +141,14 @@ type Pair[A] = [Left[A], Right[A]] let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) pair.1.item.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.144: pair.1.item.value +//│ ║ l.142: pair.1.item.value //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This type argument supplies only an input bound for 'R'. -//│ ║ l.143: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +//│ ║ l.141: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) //│ ║ ^^^^ //│ ╟── Type parameter 'R' is declared contravariant here. -//│ ║ l.141: class Right[in R](val item: R) +//│ ║ l.139: class Right[in R](val item: R) //│ ║ ^ //│ ╙── Values read through it have no output bound more specific than 'Any'. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -166,14 +164,14 @@ private fun delay[A](value: A) = () => value let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) delay(pair)().1.item.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.167: delay(pair)().1.item.value +//│ ║ l.165: delay(pair)().1.item.value //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This type argument supplies only an input bound for 'R'. -//│ ║ l.166: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +//│ ║ l.164: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) //│ ║ ^^^^ //│ ╟── Type parameter 'R' is declared contravariant here. -//│ ║ l.163: class Right[in R](val item: R) +//│ ║ l.161: class Right[in R](val item: R) //│ ║ ^ //│ ╙── Values read through it have no output bound more specific than 'Any'. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -185,11 +183,10 @@ class Base type Alias = Base fun inspect(value: Alias) = (() => value)().missing //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.186: fun inspect(value: Alias) = (() => value)().missing +//│ ║ l.184: fun inspect(value: Alias) = (() => value)().missing //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'missing' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.186: fun inspect(value: Alias) = (() => value)().missing +//│ ║ l.184: fun inspect(value: Alias) = (() => value)().missing //│ ╙── ^^^^^ // A function result identifies the annotation that hides implementation members. @@ -198,11 +195,10 @@ class Base fun make(): Base = new Base make().missing //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.199: make().missing +//│ ║ l.196: make().missing //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'missing' -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.198: fun make(): Base = new Base +//│ ║ l.195: fun make(): Base = new Base //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -210,13 +206,10 @@ make().missing :ge fun inspect(value: {present: Int}) = value.missing //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.211: fun inspect(value: {present: Int}) = value.missing +//│ ║ l.207: fun inspect(value: {present: Int}) = value.missing //│ ║ ^^^^^^^^^^^^^ //│ ╟── Record type does not contain member 'missing' -//│ ║ l.211: fun inspect(value: {present: Int}) = value.missing -//│ ║ ^^^^^^^^^^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.211: fun inspect(value: {present: Int}) = value.missing +//│ ║ l.207: fun inspect(value: {present: Int}) = value.missing //│ ╙── ^^^^^^^^^^^^ // Calling a nominal value points to the annotation that made it non-callable. @@ -224,50 +217,40 @@ fun inspect(value: {present: Int}) = value.missing class Base fun invoke(value: Base) = value() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.225: fun invoke(value: Base) = value() +//│ ║ l.218: fun invoke(value: Base) = value() //│ ║ ^^^^^^^ //│ ╟── Value of type 'Base' cannot be called like a function. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.225: fun invoke(value: Base) = value() +//│ ║ l.218: fun invoke(value: Base) = value() //│ ╙── ^^^^ // Arity errors on a declared callback identify its function type. :ge fun invoke(callback: Int -> Int) = callback() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.236: fun invoke(callback: Int -> Int) = callback() +//│ ║ l.228: fun invoke(callback: Int -> Int) = callback() //│ ║ ^^^^^^^^^^ //│ ╟── Function with a declared signature expected 1 argument, but got 0 -//│ ║ l.236: fun invoke(callback: Int -> Int) = callback() -//│ ║ ^^^^^^^^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.236: fun invoke(callback: Int -> Int) = callback() +//│ ║ l.228: fun invoke(callback: Int -> Int) = callback() //│ ╙── ^^^^^^^^^^ // Type argument arity errors retain the same callback signature witness. :ge fun invoke(callback: Int -> Int) = callback[Int](0) //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.249: fun invoke(callback: Int -> Int) = callback[Int](0) +//│ ║ l.238: fun invoke(callback: Int -> Int) = callback[Int](0) //│ ║ ^^^^^^^^^^^^^ //│ ╟── Function with a declared signature expected 0 type arguments, but got 1 -//│ ║ l.249: fun invoke(callback: Int -> Int) = callback[Int](0) -//│ ║ ^^^^^^^^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.249: fun invoke(callback: Int -> Int) = callback[Int](0) +//│ ║ l.238: fun invoke(callback: Int -> Int) = callback[Int](0) //│ ╙── ^^^^^^^^^^ // A function interface also explains missing members on the callback itself. :ge fun inspect(callback: Int -> Int) = callback.missing //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.262: fun inspect(callback: Int -> Int) = callback.missing +//│ ║ l.248: fun inspect(callback: Int -> Int) = callback.missing //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Function with a declared signature does not contain member 'missing' -//│ ║ l.262: fun inspect(callback: Int -> Int) = callback.missing -//│ ║ ^^^^^^^^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.262: fun inspect(callback: Int -> Int) = callback.missing +//│ ║ l.248: fun inspect(callback: Int -> Int) = callback.missing //│ ╙── ^^^^^^^^^^ // Checked indexing reports the annotation that does not expose an array. @@ -275,13 +258,12 @@ fun inspect(callback: Int -> Int) = callback.missing class Base fun inspect(value: Base) = value.[0] //│ ╔══[COMPILATION ERROR] Resolution error in array index -//│ ║ l.276: fun inspect(value: Base) = value.[0] +//│ ║ l.259: fun inspect(value: Base) = value.[0] //│ ║ ^^^^^^^^ //│ ╟── Value of type 'Base' does not support checked array indexing. -//│ ╟── Use '![...]' for a dynamic access. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.276: fun inspect(value: Base) = value.[0] -//│ ╙── ^^^^ +//│ ║ l.259: fun inspect(value: Base) = value.[0] +//│ ║ ^^^^ +//│ ╙── Use '![...]' for a dynamic access. // An annotated instance cannot be used as a constructor. :ge @@ -289,16 +271,13 @@ class Base val instance: Base = new Base new instance //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.290: new instance +//│ ║ l.272: new instance //│ ║ ^^^^^^^^^^^^ //│ ╟── Value with a declared type cannot be instantiated with keyword 'new'. -//│ ║ l.289: val instance: Base = new Base -//│ ║ ^^^^ -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.289: val instance: Base = new Base +//│ ║ l.271: val instance: Base = new Base //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.290: new instance +//│ ║ l.272: new instance //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ instance = Base @@ -309,12 +288,11 @@ declare type Hidden type Alias = Hidden fun inspect(value: Alias) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.310: fun inspect(value: Alias) = value.member +//│ ║ l.289: fun inspect(value: Alias) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.310: fun inspect(value: Alias) = value.member +//│ ║ l.289: fun inspect(value: Alias) = value.member //│ ║ ^^^^^ //│ ╟── Type 'Hidden' is declared without a member interface. -//│ ║ l.308: declare type Hidden +//│ ║ l.287: declare type Hidden //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls index 8952900905..fc727a4cdd 100644 --- a/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls @@ -126,7 +126,6 @@ box.item.value //│ ║ l.124: box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'value' -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.121: let receiver = (new Receiver as Receiver[in Child out Base]) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls index 9eebcd1f72..440b0bfe75 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/InterfaceExposure.mls @@ -19,7 +19,6 @@ generic({a: 1}) //│ ║ l.16: fun generic[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. //│ ║ l.16: fun generic[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. @@ -31,14 +30,13 @@ generic({a: 1}) private fun generic[A](x: A) = x.a generic({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.31: private fun generic[A](x: A) = x.a +//│ ║ l.30: private fun generic[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.31: private fun generic[A](x: A) = x.a +//│ ║ l.30: private fun generic[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.31: private fun generic[A](x: A) = x.a +//│ ║ l.30: private fun generic[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -47,14 +45,13 @@ class Item(val a: Int) private fun generic[A](x: A) = x.a generic[Item](Item(1)) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.47: private fun generic[A](x: A) = x.a +//│ ║ l.45: private fun generic[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.47: private fun generic[A](x: A) = x.a +//│ ║ l.45: private fun generic[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.47: private fun generic[A](x: A) = x.a +//│ ║ l.45: private fun generic[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -68,12 +65,11 @@ fun identity[A](x: A): A = x :e private fun unused[A](x: A) = x.a //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.69: private fun unused[A](x: A) = x.a +//│ ║ l.66: private fun unused[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ╟── This type annotation supplies the value's shape. -//│ ║ l.69: private fun unused[A](x: A) = x.a +//│ ║ l.66: private fun unused[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.69: private fun unused[A](x: A) = x.a +//│ ║ l.66: private fun unused[A](x: A) = x.a //│ ╙── ^ From 618eb34ae1b53401c0d48f12970894917ddc9068 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 22:02:42 +0800 Subject: [PATCH 145/228] Preserve explicit source locations on semantic declarations --- .../test/scala/hkmc2/TypeRelationTest.scala | 8 +- .../src/main/scala/hkmc2/AsyncLowering.scala | 2 +- .../src/main/scala/hkmc2/codegen/Block.scala | 2 +- .../hkmc2/codegen/BlockTransformer.scala | 2 +- .../hkmc2/codegen/BufferableTransform.scala | 4 +- .../codegen/FlowAnalysisBasedRewrite.scala | 2 +- .../src/main/scala/hkmc2/codegen/Lifter.scala | 6 +- .../main/scala/hkmc2/codegen/Lowering.scala | 20 ++-- .../scala/hkmc2/codegen/WorkerWrapper.scala | 2 +- .../hkmc2/codegen/deforest/Rewrite.scala | 2 +- .../scala/hkmc2/semantics/Elaborator.scala | 44 ++++----- .../src/main/scala/hkmc2/semantics/Term.scala | 55 ++++++----- .../hkmc2/semantics/ucs/TermSynthesizer.scala | 2 +- .../hkmc2/semantics/ups/SplitCompiler.scala | 10 +- .../src/test/mlscript/backlog/ToTriage.mls | 2 +- .../test/mlscript/basics/BadTypeClasses.mls | 4 +- .../src/test/mlscript/basics/NestedBlocks.mls | 8 +- .../test/mlscript/codegen/CurriedClasses.mls | 4 +- .../test/mlscript/codegen/ParamClasses.mls | 4 +- .../src/test/mlscript/codegen/Spreads.mls | 4 +- .../mlscript/codegen/ThisCallVariations.mls | 2 +- .../mlscript/ctx/CascadingTypeClasses.mls | 12 +-- .../src/test/mlscript/ctx/ClassCtxParams.mls | 8 +- .../src/test/mlscript/ctx/EtaExpansion.mls | 2 +- .../src/test/mlscript/ctx/ExplicitlySpec.mls | 10 +- .../mlscript/ctx/ForwardTypeClassUses.mls | 4 +- hkmc2/shared/src/test/mlscript/ctx/Summon.mls | 8 +- .../src/test/mlscript/lifter/ClassInFun.mls | 2 +- .../test/mlscript/newres/CallSiteShapes.mls | 4 +- .../newres/ConstructorFieldRecovery.mls | 6 +- .../mlscript/newres/DeclarationLocations.mls | 95 +++++++++++++++++++ .../test/mlscript/newres/DeclaredTypes.mls | 12 +-- .../test/mlscript/newres/InferenceHoles.mls | 4 +- .../mlscript/newres/InterfaceExposure.mls | 2 +- .../src/test/mlscript/newres/Projections.mls | 2 +- .../mlscript/newres/RecursiveEnvironment.mls | 6 +- .../src/test/mlscript/objbuf/BasicsObjBuf.mls | 22 ++--- .../src/test/mlscript/parser/Handler.mls | 16 ++-- .../test/mlscript/ucs/patterns/Parameters.mls | 20 ++-- .../ups/fixpoint/UnsupportedShapes.mls | 8 +- .../ups/parametric/HigherOrderPattern.mls | 4 +- .../test/mlscript/ups/parametric/Nullable.mls | 2 +- .../mlscript/ups/syntax/WrongArguments.mls | 4 +- .../test/mlscript/ups/syntax/WrongArity.mls | 4 +- 44 files changed, 276 insertions(+), 170 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/DeclarationLocations.mls diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 8c1c6481ac..39a984b147 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -93,8 +93,8 @@ class TypeRelationTest extends AnyFunSuite: val (formal, ft) = h.parameter("X") val symbol = TypeAliasSymbol(Tree.Ident("Identity")) val member = BlockMemberSymbol("Identity", Nil) - symbol.defn = S(TypeDef(symbol, member, List(TyParam(FldFlags.empty, N, formal)), - S(ft.resolution.source), N, Nil)) + symbol.defn = S(TypeDef(symbol, member, List(TyParam(FldFlags.empty, N, formal)(N)), + S(ft.resolution.source), N, Nil)(N)) val alias = h.tpe(TypeShape.Alias(symbol, S(ft.resolution))) val (argument, at) = h.parameter("A") val application = h.tpe(TypeShape.Applied(alias.resolution, List(at.resolution))) @@ -121,8 +121,8 @@ class TypeRelationTest extends AnyFunSuite: val (unused, _) = h.parameter("Unused") val symbol = TypeAliasSymbol(Tree.Ident("Identity")) val member = BlockMemberSymbol("Identity", Nil) - symbol.defn = S(TypeDef(symbol, member, List(TyParam(FldFlags.empty, N, formal), TyParam(FldFlags.empty, N, unused)), - S(ft.resolution.source), N, Nil)) + symbol.defn = S(TypeDef(symbol, member, List(TyParam(FldFlags.empty, N, formal)(N), TyParam(FldFlags.empty, N, unused)(N)), + S(ft.resolution.source), N, Nil)(N)) val alias = h.tpe(TypeShape.Alias(symbol, S(ft.resolution))) val scope = TermSymbol(Fun, N, Tree.Ident("owner")) val owner = ResolutionBoundary(scope) diff --git a/hkmc2/shared/src/main/scala/hkmc2/AsyncLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/AsyncLowering.scala index d055686337..fcee534a8e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/AsyncLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/AsyncLowering.scala @@ -39,7 +39,7 @@ class AsyncLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx, Config): pl.allParams.map: p => val v = p.sym val nv = VarSymbol(v.id, erasedType = v.erasedType) - (p, p.copy(sym = nv)) + (p, p.copy(sym = nv)(p.toLoc)) val symMap = outerParams.iterator.map(p => p._1.sym -> p._2.sym).toMap[SimpleSymbol, SimpleSymbol] val thisVar = VarSymbol(Tree.Ident("this"), erasedType = fun.owner.flatMap(_.asThis.erasedValueType)) val thisParam = fun.owner.map(_ => Param.simple(thisVar)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 29287e2aec..d66fdd8da4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -586,7 +586,7 @@ object HandleBlock: val handlerMtds = handlers.map: handler => val sym = BlockMemberSymbol(cls.nme + handler.sym.nme, Nil, true) val fDef = FunDefn.withFreshSymbol( - N, sym, PlainParamList(Param(FldFlags.empty, handler.resumeSym, N, Modulefulness.none) :: Nil) :: Nil, + N, sym, PlainParamList(Param.simple(handler.resumeSym) :: Nil) :: Nil, handler.body )(N, annotations = Nil) val rSym = TempSymbol(N, erasedType = N, "suspendRes") diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index c3a45db9bf..8f3588070a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -300,7 +300,7 @@ class BlockTransformer(subst: SymbolSubst): def applyParamList(pl: ParamList): ParamList = def applyParam(p: Param): Param = val sym2 = p.sym.subst - if sym2 is p.sym then p else p.copy(sym = sym2) + if sym2 is p.sym then p else p.copy(sym = sym2)(p.toLoc) val params2 = pl.params.mapConserve(applyParam) val rest2 = pl.restParam.mapConserve(applyParam) if (params2 is pl.params) && (rest2 is pl.restParam) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala index 863777278e..aa9b81d710 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala @@ -34,7 +34,7 @@ class BufferableTransform()(using State, Raise): (sym, VarSymbol(sym.id, erasedType = N)) .toMap def mapParam(p: Param) = - Param(p.flags, varMap(p.sym), p.sign, p.modulefulness) + Param(p.flags, varMap(p.sym), p.sign, p.modulefulness)(p.toLoc) (params.map(pl => ParamList(pl.flags, pl.params.map(mapParam), pl.restParam.map(mapParam))), varMap.toMap) def mkFieldReplacer(buf: VarSymbol, baseIdx: VarSymbol, symMap: Map[SimpleSymbol, SimpleSymbol]) = def getOffset(off: Int)(k: Path => Block): Block = @@ -79,7 +79,7 @@ class BufferableTransform()(using State, Raise): val (newParams, symMap) = mkSymbolReplacer(f.params) val blk = mkFieldReplacer(buf, idx, symMap).applyBlock(f.body) FunDefn(f.owner, f.sym, TermSymbol(f.dSym.k, f.dSym.owner, f.dSym.id, erasedType = N), PlainParamList( - Param(FldFlags.empty, buf, N, Modulefulness.none) :: Param(FldFlags.empty, idx, N, Modulefulness.none) :: Nil) :: newParams, + Param.simple(buf) :: Param.simple(idx) :: Nil) :: newParams, if isCtor then Begin(blk, Return(idx.asSimpleRef)) else blk)(configOverride = f.configOverride, annotations = f.annotations) val fakeCtor = transformFunDefn(FunDefn.withFreshSymbol( S(companionSym), diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala index a6d86e0b4a..958d35d332 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala @@ -262,7 +262,7 @@ class FlowAnalysisBasedRewrite( def refreshParam(p: Param): Param = val newSym = new VarSymbol(Tree.Ident(p.sym.name), erasedType = p.sym.erasedType) refreshParamMap(p.sym) = newSym - Param(p.flags, newSym, p.sign, p.modulefulness) + Param(p.flags, newSym, p.sign, p.modulefulness)(p.toLoc) val refreshedParams = rewrittenParams.map: case ParamList(flags, params, restParam) => ParamList(flags, params.map(refreshParam), restParam.map(refreshParam)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index aadfae1ddb..b5acdc0016 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -582,7 +582,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): val fldSym = BlockMemberSymbol(nme, Nil) val tSym = TermSymbol(syntax.MutVal, S(clsSym), ident, erasedType = capturedType) - val p = Param(FldFlags.empty.copy(isVal = true), varSym, N, Modulefulness.none) + val p = Param(FldFlags.empty.copy(isVal = true), varSym, N, Modulefulness.none)(varSym.toLoc) varSym.decl = S(p) // * Currently this is only accessed to create the class' toString method val vd = ValDefn( @@ -909,7 +909,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): else b // some helpers - private def dupParam(p: Param): Param = p.copy(sym = VarSymbol(Tree.Ident(p.sym.nme), erasedType = p.sym.erasedType)) + private def dupParam(p: Param): Param = p.copy(sym = VarSymbol(Tree.Ident(p.sym.nme), erasedType = p.sym.erasedType))(p.toLoc) private def dupParams(plist: List[Param]): List[Param] = plist.map(dupParam) private def dupParamList(plist: ParamList): ParamList = plist.copy(params = dupParams(plist.params), restParam = plist.restParam.map(dupParam)) @@ -1043,7 +1043,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): lazy val auxParams: List[Param] = (reqDefnsOrdered.map(defnSymsMap_) ::: capturesOrdered.map(capSymsMap_) ::: passedSymsOrdered.map(passedSymsMap_)) .map: s => - val decl = Param(FldFlags.empty.copy(isVal = false), s, N, Modulefulness.none) + val decl = Param(FldFlags.empty.copy(isVal = false), s, N, Modulefulness.none)(s.toLoc) s.decl = S(decl) decl diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 2a8d45ba89..7eb97e0813 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -325,21 +325,21 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu stagedAnnots, Nil, N, - ) + )(mod.toLoc) case _ => _defn reportAnnotations(defn, defn.extraAnnotations) val bufferableAnnots = defn.annotations.flatMap: case Annot.Trm(trm: SynthSel) => if trm.sym.contains(ctx.builtins.annotations.buffered) then - S(false) + S(false -> trm) else if trm.sym.contains(ctx.builtins.annotations.bufferable) then - S(true) + S(true -> trm) else N case _ => N if bufferableAnnots.length > 1 then raise(ErrorReport( - msg"Only one of bufferable annotation is allowed." -> defn.toLoc :: Nil, + msg"Only one of bufferable annotation is allowed." -> Loc(bufferableAnnots.map(_._2)) :: Nil, source = Diagnostic.Source.Compilation )) if bufferableAnnots.length >= 1 then @@ -348,7 +348,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu msg"No companion class is allowed with @buffered or @bufferable." -> defn.toLoc :: Nil, source = Diagnostic.Source.Compilation )) - val bufferable = bufferableAnnots.headOption + val bufferable = bufferableAnnots.headOption.map(_._1) // Forbid @buffered classes from having a main parameter list bufferable.foreach: isBufferable => if !isBufferable && defn.paramsOpt.isDefined then @@ -560,7 +560,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case ps :: rest => val freshSyms = ps.params.map(p => new VarSymbol(new Tree.Ident(p.sym.nme), erasedType = N)) softTODO(ps.restParam.isEmpty, "Eta expanding rest parameters in constructor definitions is not yet supported") - val freshParams = (ps.params zip freshSyms).map((p, s) => Param(p.flags, s, N, p.modulefulness)) + val freshParams = (ps.params zip freshSyms).map((p, s) => Param(p.flags, s, N, p.modulefulness)(p.toLoc)) val freshParamList = ParamList(ps.flags, freshParams, N) val freshArgs = freshSyms.map(s => Arg(N, s.asSimpleRef)) Lambda(freshParamList, Return(etaExpand(rest, accArgss :+ freshArgs)))(Nil) @@ -791,8 +791,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu if sym.binary then val t1 = new Tree.Ident("arg1") val t2 = new Tree.Ident("arg2") - val p1 = Param(FldFlags.empty, VarSymbol(t1, erasedType = N), N, Modulefulness.none) - val p2 = Param(FldFlags.empty, VarSymbol(t2, erasedType = N), N, Modulefulness.none) + val p1 = Param(FldFlags.empty, VarSymbol(t1, erasedType = N), N, Modulefulness.none)(t1.toLoc) + val p2 = Param(FldFlags.empty, VarSymbol(t2, erasedType = N), N, Modulefulness.none)(t2.toLoc) val ps = PlainParamList(p1 :: p2 :: Nil) val bod = st.App(ref, st.Tup(List(st.Ref(p1.sym)(t1, N).resolve, st.Ref(p2.sym)(t2, N).resolve)) (Tree.Tup(Nil // FIXME should not be required (using dummy value) @@ -807,7 +807,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu return k(Lambda(paramLists.head, bodyBlock)(Nil).withLocOf(ref)) if sym.unary then val t1 = new Tree.Ident("arg") - val p1 = Param(FldFlags.empty, VarSymbol(t1, erasedType = N), N, Modulefulness.none) + val p1 = Param(FldFlags.empty, VarSymbol(t1, erasedType = N), N, Modulefulness.none)(t1.toLoc) val ps = PlainParamList(p1 :: Nil) val bod = st.App(ref, st.Tup(List(st.Ref(p1.sym)(t1, N).resolve)) (Tree.Tup(Nil // FIXME should not be required (using dummy value) @@ -896,7 +896,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu Modulefulness.none, Annot.RaiseEffects :: Nil, N, - ) + )(trm.toLoc) val rewritten = st.App( State.runtimeSymbol.ref().synthSel(State.toJsAsyncSymbol), st.Tup(PlainFld(st.Blk(td :: Nil, bms.ref(ident).resolved(dsym))) :: Nil)(Tree.DummyTup) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala index 80a83cf88d..0ee5b1028b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala @@ -47,7 +47,7 @@ class WorkerWrapper private def freshParam(param: Param, mapping: collection.mutable.Map[Symbol, Symbol]): Param = val freshSym = new VarSymbol(param.sym.id, erasedType = param.sym.erasedType) mapping(param.sym) = freshSym - Param(param.flags, freshSym, param.sign, param.modulefulness).withSignTypeOf(param) + Param(param.flags, freshSym, param.sign, param.modulefulness)(param.toLoc).withSignTypeOf(param) private def flattenParams(params: Ls[ParamList]): (ParamList, Map[Symbol, Symbol]) = val mapping = collection.mutable.LinkedHashMap.empty[Symbol, Symbol] diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala index 56adf7a882..12bcfab9af 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala @@ -487,7 +487,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise) pl.params.map: p => val newSym = new VarSymbol(Tree.Ident(p.sym.name), erasedType = p.sym.erasedType) refreshParamMap(p.sym) = newSym - Param(p.flags, newSym, p.sign, p.modulefulness), + Param(p.flags, newSym, p.sign, p.modulefulness)(p.toLoc), pl.restParam) val bodyWithCorrectSymbols = refreshExtractedBody(refreshParamMap.toMap, rewrittenBody) FunDefn( diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 9631b32fff..753e6d73c5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -661,7 +661,7 @@ object Elaborator: val id = new Ident("NonLocalReturn") val sym = ClassSymbol(DummyTypeDef(syntax.Cls), id) val bsym = BlockMemberSymbol("ret", Nil, true) - val defn = ClassDef(N, syntax.Cls, sym, bsym, N, Nil, Nil, N, ObjBody(Blk(Nil, Term.Lit(UnitLit(false)))), Nil, N, auxCtorParams = Nil) + val defn = ClassDef(N, syntax.Cls, sym, bsym, N, Nil, Nil, N, ObjBody(Blk(Nil, Term.Lit(UnitLit(false)))), Nil, N, auxCtorParams = Nil)(N) sym.defn = S(defn) Term.SynthSel(runtimeSymbol.ref(), id)(S(sym), FlowSymbol.synthSel(id.name), N, N) val nonLocalRet = @@ -846,7 +846,7 @@ extends Importer: Fun, mtdSym, tsym, - PlainParamList(valueSym.fold(Nil)(sym => Param(FldFlags.empty, sym, N, Modulefulness.none) :: Nil)) :: Nil, + PlainParamList(valueSym.fold(Nil)(sym => Param.simple(sym) :: Nil)) :: Nil, N, N, S(spec.methodBody(valueSym)), @@ -854,7 +854,7 @@ extends Importer: Modulefulness.none, Nil, N, - ) + )(N) mtdSym.tsym = S(tsym) tsym.defn = S(td) HandlerTermDefinition(resumeSym, td) @@ -1192,7 +1192,7 @@ extends Importer: given UnderCtx = new UnderCtx(S(unders)) val st = subterm(tree, interp) val params = unders.iterator.map: sym => - Param(FldFlags.empty, sym, N, Modulefulness.none) + Param.simple(sym) .toList if params.isEmpty then st else Term.Lam(PlainParamList(params), st) @@ -1343,7 +1343,7 @@ extends Importer: derivedClsSym.defn = S(ClassDef( N, syntax.Cls, derivedClsSym, BlockMemberSymbol(derivedClsSym.name, Nil), N, - Nil, Nil, N, ObjBody(Blk(Nil, Term.Lit(Tree.UnitLit(false)))), Nil, N, auxCtorParams = Nil)) + Nil, Nil, N, ObjBody(Blk(Nil, Term.Lit(Tree.UnitLit(false)))), Nil, N, auxCtorParams = Nil)(hd.toLoc)) val elabed = ctx.nestInner(derivedClsSym).givenIn: block(sts_, hasResult = false)._1 @@ -1358,7 +1358,7 @@ extends Importer: case ParamList(_, value :: Nil, _) :: newParams => if newParams.isEmpty then raise(ErrorReport(msg"Handler function cannot be a getter" -> td.toLoc :: Nil)) - val newTd = TermDefinition(Fun, sym, tsym, newParams.reverse, tparams, sign, body, flags, mf, annotations, comp) + val newTd = TermDefinition(Fun, sym, tsym, newParams.reverse, tparams, sign, body, flags, mf, annotations, comp)(td.toLoc) S(HandlerTermDefinition(value.sym, newTd)) case _ => raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil)) @@ -1413,7 +1413,7 @@ extends Importer: val boundVars = mutable.HashMap.empty[Str, VarSymbol] def genSym(id: Tree.Ident) = val sym = VarSymbol(id, erasedType = N) - sym.decl = S(TyParam(FldFlags.empty, N, sym)) // TODO vce + sym.decl = S(TyParam(FldFlags.empty, N, sym)(id.toLoc)) // TODO vce boundVars += id.name -> sym sym val syms = (tvs.collect: @@ -1618,9 +1618,9 @@ extends Importer: val self = VarSymbol(Ident("self"), erasedType = N) val args = VarSymbol(Ident("args"), erasedType = N) val ps = ParamList(ParamListFlags.empty, - Param(FldFlags.empty, self, N, Modulefulness.none) :: Nil, + Param.simple(self) :: Nil, S: - Param(FldFlags.empty, args, N, Modulefulness.none) + Param.simple(args) ) val rs = FlowSymbol.app() Term.Lam(ps, @@ -1694,7 +1694,7 @@ extends Importer: case tree @ Case(kw, _) => val scrut = VarSymbol(Ident("caseScrut"), erasedType = N) val body = caseSplit(scrut, tree) - val params = Param(FldFlags.empty, scrut, N, Modulefulness.none) :: Nil + val params = Param.simple(scrut) :: Nil Term.Lam(PlainParamList(params), body).mkLocWith(kw) case PrefixApp(kw @ Keywrd(Keyword.`return`), body) => ctx.getRetHandler match @@ -2251,7 +2251,7 @@ extends Importer: // Retain the source signature's meaning; Erasure decides which arrows are physical parameters. if newResolution then s.foreach(registerSignature) val tdf = TermDefinition(k, sym, tsym, pss, tps, s, body, - TermDefFlags.empty.copy(isMethod = isMethod, hasResultAnnotation = td.annotatedResultType.isDefined), mfn, annotations, N).withLocOf(td) + TermDefFlags.empty.copy(isMethod = isMethod, hasResultAnnotation = td.annotatedResultType.isDefined), mfn, annotations, N)(td.toLoc) sym.tsym = S(tsym) tsym.defn = S(tdf) if newResolution then checkDeclaredResult(tdf) @@ -2304,7 +2304,7 @@ extends Importer: ts.tys.flatMap: targ => def mk(id: Ident, vce: Opt[Bool]): Ls[TyParam] = val vs = VarSymbol(id, erasedType = N) - val res = TyParam(FldFlags.empty, vce, vs) + val res = TyParam(FldFlags.empty, vce, vs)(targ.toLoc) vs.decl = S(res) res :: Nil targ match @@ -2377,7 +2377,7 @@ extends Importer: p.modulefulness, if isPublicField then Nil else Annot.Private :: Nil, N, - ).withLocOf(p) + )(p.toLoc) assert(p.fldSym.isEmpty) p.fldSym = S(fsym) fsym.tsym = S(tsym) @@ -2450,7 +2450,7 @@ extends Importer: assert(body.isEmpty) val d = given Ctx = newCtx - semantics.TypeDef(alsSym, sym, tps, rhs.map(term(_, Tpe)), N, annotations) + semantics.TypeDef(alsSym, sym, tps, rhs.map(term(_, Tpe)), N, annotations)(td.toLoc) alsSym.defn = S(d) d case Pat => @@ -2505,7 +2505,7 @@ extends Importer: // `paramsOpt` is set to `N` because we don't want parameters to // appear in the generated class's constructor. val pd = PatternDef(owner, patSym, sym, tps, allParams, - patternParams, extractionParams, pat, annotations) + patternParams, extractionParams, pat, annotations)(td.toLoc) patSym.defn = S(pd) pd case k: (Mod.type | Obj.type) => @@ -2521,7 +2521,7 @@ extends Importer: val md = val (bod, c) = mkBody(Nil) ModuleOrObjectDef(owner, modSym, sym, - tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations)(outerCtx.scope) + tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations)(outerCtx.scope, td.toLoc) modSym.defn = S(md) md case Cls => @@ -2557,7 +2557,7 @@ extends Importer: sym, ctsym, allCtorPss, - S(tps.map(tp => Param(FldFlags.empty, tp.sym, N, Modulefulness.none))), + S(tps.map(tp => Param(FldFlags.empty, tp.sym, N, Modulefulness.none)(tp.toLoc))), S(clsSym.ref()), N, TermDefFlags.empty, @@ -2566,7 +2566,7 @@ extends Importer: case a @ Annot.Modifier(Keyword.`declare`) => a , S(clsSym), - ) + )(td.toLoc) if pss.nonEmpty then sym.tsym = S(ctsym) ctsym.defn = S(ctdef) // Note: do NOT set sym.tsym for constructor(...) classes; they are not callable as functions. @@ -2574,7 +2574,7 @@ extends Importer: else N val cd = val (bod, c) = mkBody(auxCtorPss) - ClassDef(owner, Cls, clsSym, sym, tsym, tps, pss, newOf(td), ObjBody(bod), annotations, comp, auxCtorParams = auxCtorPss) + ClassDef(owner, Cls, clsSym, sym, tsym, tps, pss, newOf(td), ObjBody(bod), annotations, comp, auxCtorParams = auxCtorPss)(td.toLoc) clsSym.defn = S(cd) cd case Trt | Mxn => lastWords(s"Unexpected type definition kind here: $k") @@ -2683,7 +2683,7 @@ extends Importer: if newResolution then sig.foreach(registerSignature) val sym = VarSymbol(canonicalId) sym.sourceAliases = aliases - val p = Param(flg, sym, sig, mfn) + val p = Param(flg, sym, sig, mfn)(t.toLoc) sym.decl = S(p) if newResolution then sig.foreach: sign => listenTypeInstances(sign): shape => @@ -2966,8 +2966,8 @@ extends Importer: val vs = ps.flatMap: case id: Ident => val sym = VarSymbol(id, erasedType = N) - sym.decl = S(TyParam(FldFlags.empty, N, sym)) - Param(FldFlags.empty, sym, N, Modulefulness.none) :: Nil + sym.decl = S(TyParam(FldFlags.empty, N, sym)(id.toLoc)) + Param.simple(sym) :: Nil case t => raise(ErrorReport(msg"Unsupported type parameter ${t.describe}" -> t.toLoc :: Nil)) Nil diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index edb98f6edb..4e3128a226 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -282,7 +282,7 @@ object Resolvable: Nil, if defn.tparams.isEmpty then N - else S(defn.tparams.map(tp => Param(FldFlags.empty, tp.sym, N, Modulefulness.none))), + else S(defn.tparams.map(tp => Param(FldFlags.empty, tp.sym, N, Modulefulness.none)(tp.toLoc))), defn.rhs, TermDefFlags.empty, // TODO: handle class-like definitions with flags Modulefulness.none, // TODO: handle modulefulness for class-like definitions @@ -293,7 +293,7 @@ object Resolvable: defn.paramsOpt.toList ::: defn.auxParams, if defn.tparams.isEmpty then N - else S(defn.tparams.map(tp => Param(FldFlags.empty, tp.sym, N, Modulefulness.none))), + else S(defn.tparams.map(tp => Param(FldFlags.empty, tp.sym, N, Modulefulness.none)(tp.toLoc))), N, // TODO: handle class-like definitions with signatures TermDefFlags.empty, // TODO: handle class-like definitions with flags Modulefulness.none, // TODO: handle modulefulness for class-like definitions @@ -382,7 +382,7 @@ sealed trait UnresolvedRefImpl extends NewResolvableImpl: var dynamicPrefixes: Ls[Term] = Nil -enum Term extends Statement, ShapePublisher: +enum Term extends Statement, AutoLocated, ShapePublisher: /** Filled by the type interpreter during elaboration; erasure validates the completed result. */ private[hkmc2] var typeInterpretation: Opt[TypeResolution] = N @@ -822,7 +822,7 @@ trait Describable: def describe: Str -sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: +sealed trait Statement extends Located, ProductWithExtraInfo, Describable: def mkClone(using State, Erasure): Statement = this match case t: Term => lastWords(s"overridden implementation") @@ -1284,13 +1284,13 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo, Describable: case LeadingDotSel(nme) => s"_?_.${nme.name}" case SetConfig(_) => "#config(...)" -final case class LetDecl(sym: LocalVarSymbol | TermSymbol, annotations: Ls[Annot]) extends Statement +final case class LetDecl(sym: LocalVarSymbol | TermSymbol, annotations: Ls[Annot]) extends Statement, AutoLocated /** The symbol identifies the property, independently of any local binding used * to evaluate its value. Computed keys also have an identity, but cannot be * selected statically until their key is known. Cloning preserves this identity. */ -final case class RcdField(field: Term, rhs: Term, sym: BlockMemberSymbol) extends Statement: +final case class RcdField(field: Term, rhs: Term, sym: BlockMemberSymbol) extends Statement, AutoLocated: // The two-argument RcdField.apply sets sym.tsym and its definition before // constructing this node. Lowering clones reuse the same initialized symbols. val tsym: TermSymbol = sym.tsym.get @@ -1316,16 +1316,16 @@ object RcdField: // directly to its written type: projection does not invoke a value definition. tsym.defn = S(TermDefinition(RecordField, sym, tsym, Nil, N, if signature then S(rhs) else N, - if signature then N else S(Term.Capture(rhs, tsym)), TermDefFlags.empty, Modulefulness.none, Nil, N)) + if signature then N else S(Term.Capture(rhs, tsym)), TermDefFlags.empty, Modulefulness.none, Nil, N)(field.toLoc)) sym.complete() RcdField(field, rhs, sym) -final case class RcdSpread(rcd: Term) extends Statement +final case class RcdSpread(rcd: Term) extends Statement, AutoLocated -final case class DefineVar(sym: LocalSymbol | TermSymbol, rhs: Term) extends Statement +final case class DefineVar(sym: LocalSymbol | TermSymbol, rhs: Term) extends Statement, AutoLocated /** A global configuration change directive (`#config(...)`). * Records a function that modifies the current compiler configuration. */ -final case class SetConfig(modify: hkmc2.Config => hkmc2.Config) extends Statement: +final case class SetConfig(modify: hkmc2.Config => hkmc2.Config) extends Statement, AutoLocated: override def toString: String = "#config(...)" enum Visibility: @@ -1391,7 +1391,7 @@ final case class TermDefinition( modulefulness: Modulefulness, annotations: Ls[Annot], companion: Opt[CompanionSymbol], -) extends CompanionValue: +)(val toLoc: Opt[Loc]) extends CompanionValue: require(k is tsym.k) def bsym: BlockMemberSymbol = sym val owner = tsym.owner @@ -1487,10 +1487,15 @@ end ObjBody * in which case it is a `BlockMemberSymbol` when importing files explicitly * and a `TermSymbol` when the import is made implicitly by the compiler (eg, importing "Predef"). * Note that the `file` Path may not represent a real file; eg when importing "fs". */ -case class Import(sym: ImportSymbol, str: Str, file: io.Path) extends Statement +case class Import(sym: ImportSymbol, str: Str, file: io.Path) extends Statement, AutoLocated -sealed abstract class Declaration: +/** Declaration spans come from the syntax that introduced them, never from semantic + * subterms: those omit names and may contain synthesized or unrelated source terms. + * Rewrites preserve the original span; declarations without source syntax use `N`. + */ +sealed abstract class Declaration extends Located: + val toLoc: Opt[Loc] val sym: Symbol /** Whether this declares a class, a pattern, an object, or a pattern @@ -1570,7 +1575,8 @@ case class ModuleOrObjectDef( companion: Opt[ModuleCompanionSymbol], annotations: Ls[Annot], )( - val path: SrcScope + val path: SrcScope, + val toLoc: Opt[Loc], ) extends ClassLikeDef, CompanionValue: val ctorSym: Option[ClassCtorSymbol] = N @@ -1592,7 +1598,7 @@ case class PatternDef( */ pattern: Pattern, annotations: Ls[Annot], -) extends ClassLikeDef: +)(val toLoc: Opt[Loc]) extends ClassLikeDef: self => val kind: ClsLikeKind = Pat val ext: Opt[New] = N @@ -1638,14 +1644,14 @@ object ClassDef: annotations: Ls[Annot], comp: Opt[ClassCompanionSymbol], auxCtorParams: Ls[ParamList], - ): ClassDef = + )(toLoc: Opt[Loc]): ClassDef = params match case ps :: pss => Parameterized(owner, kind, sym.asInstanceOf// TODO: improve , bsym, S(ctorSym.getOrElse(lastWords("Parameterized classes should have a ctor symbol."))) - , tparams, ps, pss ::: auxCtorParams, ext, body, comp, annotations) + , tparams, ps, pss ::: auxCtorParams, ext, body, comp, annotations)(toLoc) case Nil => Plain(owner, kind, sym.asInstanceOf// TODO: improve , bsym - , tparams, ext, body, comp, annotations, auxParams = auxCtorParams, ctorSym = ctorSym) + , tparams, ext, body, comp, annotations, auxParams = auxCtorParams, ctorSym = ctorSym)(toLoc) def unapply(cls: ClassDef): Opt[(ClassSymbol, Ls[TyParam], Opt[ParamList], ObjBody)] = S((cls.sym, cls.tparams, cls.paramsOpt, cls.body)) @@ -1663,7 +1669,7 @@ object ClassDef: body: ObjBody, companion: Opt[ClassCompanionSymbol], annotations: Ls[Annot], - ) extends ClassDef: + )(val toLoc: Opt[Loc]) extends ClassDef: val paramsOpt: Opt[ParamList] = S(params) case class Plain( @@ -1678,7 +1684,7 @@ object ClassDef: annotations: Ls[Annot], auxParams: List[ParamList], ctorSym: Opt[ClassCtorSymbol], - ) extends ClassDef: + )(val toLoc: Opt[Loc]) extends ClassDef: val paramsOpt: Opt[ParamList] = N end ClassDef @@ -1691,7 +1697,7 @@ case class TypeDef( rhs: Opt[Term], companion: Opt[CompanionValue], annotations: Ls[Annot], -) extends TypeLikeDef: +)(val toLoc: Opt[Loc]) extends TypeLikeDef: val kind: ObjDefKind = Als @@ -1743,7 +1749,7 @@ final case class Spd(k: SpreadKind, term: Term) extends Elem: def showDbg(using DebugPrinter): Str = k.str + term.showDbg def children: Vector[Located] = Vector.single(term) -final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol) extends Declaration: +final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol)(val toLoc: Opt[Loc]) extends Declaration: // * For variance analysis var isCovariant: Bool = true @@ -1759,10 +1765,10 @@ final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol) extend object Param: - def simple(sym: VarSymbol) = Param(FldFlags.empty, sym, N, Modulefulness.none) + def simple(sym: VarSymbol) = Param(FldFlags.empty, sym, N, Modulefulness.none)(sym.toLoc) final case class Param(flags: FldFlags, sym: VarSymbol, sign: Opt[Term], modulefulness: Modulefulness) -extends Declaration, AutoLocated: +(val toLoc: Opt[Loc]) extends Declaration: var fldSym: Opt[MemberSymbol] = N val flow: FlowSymbol = sym @@ -1777,7 +1783,6 @@ extends Declaration, AutoLocated: def subTerms: Ls[Term] = sign.toList - override protected def children: Vector[Located] = sym +: sign.toVector def show(using Scope, ShowCfg, Raise): Document = doc"${flags.show(true)}${sym.showName}${sign.fold(doc"")(": " :: _.show)}" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala index c07d4032f4..0132b578a8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala @@ -33,7 +33,7 @@ trait TermSynthesizer(using State): protected final def splitLet(sym: LocalVarSymbol, term: Term)(inner: Split): Split = Split.Let(sym, term, inner) - protected final def param = Param(FldFlags.empty, _, N, Modulefulness.none) + protected final def param = Param.simple protected final def paramList(params: Param*) = PlainParamList(params.toList) private lazy val runtimeRef: Term.Ref = State.runtimeSymbol.ref().resolve diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala index 0fd438b6b1..9b42947856 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala @@ -827,7 +827,7 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise val symbols = pattern.variables.symbols val params = parameters.map: case (_, parameterSymbol) => - Param(FldFlags.empty, parameterSymbol, N, Modulefulness.none) + Param.simple(parameterSymbol) val lambdaSymbol = new TempSymbol(N, erasedType = N, "transform") // Next, we need to elaborate the pattern into a split. Note that // `makeMatchSplit` returns a function that takes a split as the @@ -1106,7 +1106,7 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise val symbols = pattern.variables.symbols val params = parameters.map: case (_, parameterSymbol) => - Param(FldFlags.empty, parameterSymbol, N, Modulefulness.none) + Param.simple(parameterSymbol) val lambdaSymbol = new TempSymbol(N, erasedType = N, "transform") (makeConsequent, alternative) => Split.Let( sym = lambdaSymbol, @@ -1282,9 +1282,9 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise ): (BlockMemberSymbol, ParamList, Split) = val sym = BlockMemberSymbol(name, Nil) // Pattern parameters are passed as objects. - val patternInputs = patternParameters.map(_.copy(flags = FldFlags.empty)) + val patternInputs = patternParameters.map(p => p.copy(flags = FldFlags.empty)(p.toLoc)) // The last parameter is the scrutinee. - val scrutParam = Param(FldFlags.empty, scrut, N, Modulefulness.none) + val scrutParam = Param.simple(scrut) val ps = PlainParamList(patternInputs :+ scrutParam) (sym, ps, topmost) @@ -1354,7 +1354,7 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise ): Ls[Statement] = val fieldSymbol = TempSymbol(N, erasedType = N, name) val decl = LetDecl(fieldSymbol, Nil) - val param = Param(FldFlags.empty, scrut, N, Modulefulness.none) + val param = Param.simple(scrut) val paramList = PlainParamList(param :: Nil) val lambda = Term.Lam(paramList, Term.SynthIf(topmost)) val defineVar = DefineVar(fieldSymbol, lambda) diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index 03290acbff..f29dd4cf14 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -240,7 +240,7 @@ id(Foo)(1) data class FooSpd(...args) //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. //│ ║ l.240: data class FooSpd(...args) -//│ ╙── ^^^^ +//│ ╙── ^^^^^^^ FooSpd(1, 2, 3).args //│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 3 diff --git a/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls b/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls index 727dcefe98..47ed1cb990 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls @@ -16,7 +16,7 @@ M.f //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.10: fun f(using Int) -//│ ║ ^^^ +//│ ║ ^^^^^^^^^ //│ ╙── Missing instance: Expected: Int; Available: ‹none available› :todo // support literal type @@ -78,7 +78,7 @@ f //│ ║ ^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.74: fun f[T](using t: T) = t -//│ ║ ^^^^ +//│ ║ ^^^^^^^^^^ //│ ╙── Illegal query for an unspecified type variable T. :e diff --git a/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls index c74f97215f..ee9c6b358a 100644 --- a/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls +++ b/hkmc2/shared/src/test/mlscript/basics/NestedBlocks.mls @@ -37,13 +37,15 @@ object Oops with //│ ║ l.35: print(this.fakeField) // shouldn't work! //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Module/object definition 'Oops' does not contain member 'fakeField' +//│ ║ l.31: object Oops with +//│ ║ ^^^^^^^^^^^^^^^^ //│ ║ l.32: if true then -//│ ║ ^^^^^^^^^ +//│ ║ ^^^^^^^^^^^^^^ //│ ║ l.33: val fakeField = 1 //│ ║ ^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.34: print(fakeField) //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.35: print(this.fakeField) // shouldn't work! +//│ ║ l.35: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ > 1 //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -52,7 +54,7 @@ object Oops with :re Oops.fakeField //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.53: Oops.fakeField +//│ ║ l.55: Oops.fakeField //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Object 'Oops' does not contain member 'fakeField' //│ ║ l.31: object Oops with diff --git a/hkmc2/shared/src/test/mlscript/codegen/CurriedClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/CurriedClasses.mls index 301fa0d67e..bf065ed09e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CurriedClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CurriedClasses.mls @@ -107,10 +107,10 @@ class A(x1, x2, ...xs)(y1, ...ys) with fun fields = [x1, x2, xs, y1, ys] //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. //│ ║ l.106: class A(x1, x2, ...xs)(y1, ...ys) with -//│ ╙── ^^ +//│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. //│ ║ l.106: class A(x1, x2, ...xs)(y1, ...ys) with -//│ ╙── ^^ +//│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'xs' //│ ║ l.107: fun fields = [x1, x2, xs, y1, ys] //│ ║ ^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls index f1edfe839f..91640435b1 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls @@ -171,10 +171,10 @@ class Foo(val z, val z) //│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'z'. //│ ╟── Defined at: //│ ║ l.170: class Foo(val z, val z) -//│ ║ ^ +//│ ║ ^^^^^ //│ ╟── Defined at: //│ ║ l.170: class Foo(val z, val z) -//│ ╙── ^ +//│ ╙── ^^^^^ Foo(1, 2) //│ = Foo(undefined, undefined) diff --git a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls index 1b291e5d24..a2e51df7f9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Spreads.mls @@ -68,7 +68,7 @@ let x = new A(1, 2, 3) x.r //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. //│ ║ l.66: data class A(...r) -//│ ╙── ^ +//│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.67: let x = new A(1, 2, 3) //│ ║ ^^^^^^^^^^^^^^ @@ -92,7 +92,7 @@ data class A(...r) with new A(1, 2, 3).getR //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. //│ ║ l.90: data class A(...r) with -//│ ╙── ^ +//│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation //│ ║ l.92: new A(1, 2, 3).getR //│ ║ ^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls b/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls index e49546f47e..326b088f81 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls @@ -61,7 +61,7 @@ data class Example2(val a) with //│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'a'. //│ ╟── Defined at: //│ ║ l.58: data class Example2(val a) with -//│ ║ ^ +//│ ║ ^^^^^ //│ ╟── Defined at: //│ ║ l.59: val a = 1 //│ ╙── ^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ctx/CascadingTypeClasses.mls b/hkmc2/shared/src/test/mlscript/ctx/CascadingTypeClasses.mls index 02ec58b79f..53b3292247 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/CascadingTypeClasses.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/CascadingTypeClasses.mls @@ -61,7 +61,7 @@ foo //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.55: fun foo[A](using Foo[A])(using A) = 42 -//│ ║ ^ +//│ ║ ^^^^^^^ //│ ╙── Illegal query for an unspecified type variable A. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -75,7 +75,7 @@ foo //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.69: fun foo[A](using A)(using Foo[A]) = 42 -//│ ║ ^ +//│ ║ ^^^^^^^ //│ ╙── Illegal query for an unspecified type variable A. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -90,7 +90,7 @@ foo //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.69: fun foo[A](using A)(using Foo[A]) = 42 -//│ ║ ^ +//│ ║ ^^^^^^^ //│ ╙── Illegal query for an unspecified type variable A. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -104,7 +104,7 @@ foo //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.69: fun foo[A](using A)(using Foo[A]) = 42 -//│ ║ ^ +//│ ║ ^^^^^^^ //│ ╙── Illegal query for an unspecified type variable A. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -118,7 +118,7 @@ foo //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.69: fun foo[A](using A)(using Foo[A]) = 42 -//│ ║ ^ +//│ ║ ^^^^^^^ //│ ╙── Illegal query for an unspecified type variable A. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -131,7 +131,7 @@ foo //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.69: fun foo[A](using A)(using Foo[A]) = 42 -//│ ║ ^ +//│ ║ ^^^^^^^ //│ ╙── Illegal query for an unspecified type variable A. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ctx/ClassCtxParams.mls b/hkmc2/shared/src/test/mlscript/ctx/ClassCtxParams.mls index cb6e047e12..3f23709bb0 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/ClassCtxParams.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/ClassCtxParams.mls @@ -36,7 +36,7 @@ Bar //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.30: class Bar(using val s: Str) -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Str; Available: Int //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -49,7 +49,7 @@ Bar //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.43: class Bar(using s: Str) -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Str; Available: Int //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -87,7 +87,7 @@ Foo //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.79: class Foo(using Int)(using Str) with -//│ ║ ^^^ +//│ ║ ^^^^^^^^^ //│ ╙── Missing instance: Expected: Str; Available: Int //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -128,7 +128,7 @@ x => x.Foo#S //│ ║ ^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.101: class Foo(using T) with -//│ ║ ^ +//│ ║ ^^^^^^^ //│ ╙── Missing instance: Expected: T; Available: Int //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let lambda; lambda = (undefined, function (x) { return x.S }); return lambda diff --git a/hkmc2/shared/src/test/mlscript/ctx/EtaExpansion.mls b/hkmc2/shared/src/test/mlscript/ctx/EtaExpansion.mls index 8590db03b1..7c484266cd 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/EtaExpansion.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/EtaExpansion.mls @@ -16,7 +16,7 @@ baz //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.9: fun baz(x)(using Int)(y)(using Num) = [x, use[Int], y, use[Num]] -//│ ║ ^^^ +//│ ║ ^^^^^^^^^ //│ ╙── Missing instance: Expected: Num; Available: Int //│ = fun diff --git a/hkmc2/shared/src/test/mlscript/ctx/ExplicitlySpec.mls b/hkmc2/shared/src/test/mlscript/ctx/ExplicitlySpec.mls index 97f2bf0ce6..c0577a3a0f 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/ExplicitlySpec.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/ExplicitlySpec.mls @@ -32,14 +32,14 @@ f(using 32) //│ ║ ^^^^^^^^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.25: fun f(using i: Int)(using j: Int)(using k: Int): Int = i + j + k -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Int; Available: ‹none available› //│ ╔══[COMPILATION ERROR] Cannot query instance of type Int for call: //│ ║ l.29: f(using 32) //│ ║ ^^^^^^^^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.25: fun f(using i: Int)(using j: Int)(using k: Int): Int = i + j + k -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Int; Available: ‹none available› //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -51,7 +51,7 @@ f(using 32)(using 8) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.25: fun f(using i: Int)(using j: Int)(using k: Int): Int = i + j + k -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Int; Available: ‹none available› //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -70,7 +70,7 @@ f(using 32) //│ ║ ^^^^^^^^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.63: fun f(using i: Int)(j: Int)(using k: Int)(l: Num): Int = i + j + k + l -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Int; Available: ‹none available› //│ = fun @@ -82,7 +82,7 @@ f(using 32)(8) //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.63: fun f(using i: Int)(j: Int)(using k: Int)(l: Num): Int = i + j + k + l -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Int; Available: ‹none available› //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ctx/ForwardTypeClassUses.mls b/hkmc2/shared/src/test/mlscript/ctx/ForwardTypeClassUses.mls index 46e5ff8e47..d709788d87 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/ForwardTypeClassUses.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/ForwardTypeClassUses.mls @@ -35,7 +35,7 @@ test //│ ║ ^^^^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.29: fun mtd()(using n: Int) = n -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Int; Available: Str //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -53,7 +53,7 @@ test //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.47: fun mtd()(using n: Int) = n -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Int; Available: Str //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ctx/Summon.mls b/hkmc2/shared/src/test/mlscript/ctx/Summon.mls index 1e4afcc9a4..c992ee90aa 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/Summon.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/Summon.mls @@ -21,7 +21,7 @@ use[Num] //│ ╟── Required by contextual parameter declaration: //│ ║ in ../../mlscript-compile/Predef.mls //│ ║ l.133: fun use[T](using instance: T) = instance -//│ ║ ^^^^^^^^^^^ +//│ ║ ^^^^^^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Num (type parameter T); Available: Int, Str //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -58,7 +58,7 @@ fun f(using Int) = use[Num] //│ ╟── Required by contextual parameter declaration: //│ ║ in ../../mlscript-compile/Predef.mls //│ ║ l.133: fun use[T](using instance: T) = instance -//│ ║ ^^^^^^^^^^^ +//│ ║ ^^^^^^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Num (type parameter T); Available: Int, Str @@ -88,7 +88,7 @@ use([Some[Int]].value) //│ ╟── Required by contextual parameter declaration: //│ ║ in ../../mlscript-compile/Predef.mls //│ ║ l.133: fun use[T](using instance: T) = instance -//│ ║ ^^^^^^^^^^^ +//│ ║ ^^^^^^^^^^^^^^^^^ //│ ╙── Illegal query for an unspecified type variable T. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -107,7 +107,7 @@ f //│ ║ ^ //│ ╟── Required by contextual parameter declaration: //│ ║ l.97: fun f[T](using T) = use[T] -//│ ║ ^ +//│ ║ ^^^^^^^ //│ ╙── Illegal query for an unspecified type variable T. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls index 13fff6a256..dc13b50f26 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls @@ -247,7 +247,7 @@ res.a res.b //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. //│ ║ l.241: class A(...r) with -//│ ╙── ^ +//│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'r' //│ ║ l.242: fun a = r.push_back(x) //│ ║ ^ diff --git a/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls index a0d9d5adf5..d7ee7c474a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls @@ -70,7 +70,7 @@ restIdentity(0, new A).get() + restIdentity(0, new B).get() //│ ╟── Cannot resolve member 'get' of a value with unknown shape. //│ ╟── Rest parameter 'args' admits elements of unknown shape. //│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) -//│ ║ ^^^^ +//│ ║ ^^^^^^^ //│ ╟── 'restIdentity' is exposed by this compilation unit. //│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -80,7 +80,7 @@ restIdentity(0, new A).get() + restIdentity(0, new B).get() //│ ╟── Cannot resolve member 'get' of a value with unknown shape. //│ ╟── Rest parameter 'args' admits elements of unknown shape. //│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) -//│ ║ ^^^^ +//│ ║ ^^^^^^^ //│ ╟── 'restIdentity' is exposed by this compilation unit. //│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls index d8ee085a91..eccbcaad1c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorFieldRecovery.mls @@ -10,7 +10,7 @@ if Broken(1, 2) is Broken(x, _) then x else 0 //│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'clash'. //│ ╟── Defined at: //│ ║ l.6: class Broken(val missing, val clash) { -//│ ║ ^^^^^ +//│ ║ ^^^^^^^^^ //│ ╟── Defined at: //│ ║ l.7: val clash = 0 //│ ╙── ^^^^^^^^^^^^^ @@ -26,7 +26,7 @@ if Replaced({answer: 1}) is Replaced(x) then x.answer else 0 //│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'field'. //│ ╟── Defined at: //│ ║ l.22: class Replaced(val field) { -//│ ║ ^^^^^ +//│ ║ ^^^^^^^^^ //│ ╟── Defined at: //│ ║ l.23: val field = {other: 0} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ @@ -44,7 +44,7 @@ recover(new Child) //│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'clash'. //│ ╟── Defined at: //│ ║ l.39: class BadBase(val missing, val clash) { -//│ ║ ^^^^^ +//│ ║ ^^^^^^^^^ //│ ╟── Defined at: //│ ║ l.40: val clash = 0 //│ ╙── ^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclarationLocations.mls b/hkmc2/shared/src/test/mlscript/newres/DeclarationLocations.mls new file mode 100644 index 0000000000..de1cdffb91 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/DeclarationLocations.mls @@ -0,0 +1,95 @@ +:. + +// An empty declaration still has a source span. +:e +module Empty +Empty.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.6: Empty.missing +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Module 'Empty' does not contain member 'missing' +//│ ║ l.5: module Empty +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A member annotation must not become the enclosing module's location. +:e +module Container with + declare fun text(): Str +Container.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.19: Container.missing +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Module 'Container' does not contain member 'missing' +//│ ║ l.17: module Container with +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Nested declarations retain their own spans. +:e +module Outer with + module Inner with + declare fun text(): Str +Outer.Inner.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.33: Outer.Inner.missing +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Module 'Inner' does not contain member 'missing' +//│ ║ l.31: module Inner with +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A nominal annotation continues to point at the use of the type. +:e +class Base with + declare fun text(): Str +fun hidden(value: Base) = value.missing +hidden(new Base) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.46: fun hidden(value: Base) = value.missing +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'missing' +//│ ║ l.46: fun hidden(value: Base) = value.missing +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// The receiver inside a class uses the declaration, including its name and body. +:e +class Receiver with + declare fun text(): Str + fun inspect() = this.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.60: fun inspect() = this.missing +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Class definition 'Receiver' does not contain member 'missing' +//│ ║ l.58: class Receiver with +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.59: declare fun text(): Str +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.60: fun inspect() = this.missing +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// Parameterized classes likewise use the written declaration, not only its fields. +:e +class Parameterized(value: Int) with + fun inspect() = this.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.75: fun inspect() = this.missing +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Class definition 'Parameterized' does not contain member 'missing' +//│ ║ l.74: class Parameterized(value: Int) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.75: fun inspect() = this.missing +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// A class without fields has a declaration location even in pattern diagnostics. +:e +class Empty +if new Empty is Empty(_) then 1 else 0 +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.88: if new Empty is Empty(_) then 1 else 0 +//│ ║ ^^^^^^^^ +//│ ╟── Class definition 'Empty' does not take pattern arguments. +//│ ║ l.87: class Empty +//│ ╙── ^^^^^^^^^^^ +//│ = 0 diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 62b77e346b..2fb3a7f9c5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -88,7 +88,7 @@ read(Box(new Item)) //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. //│ ║ l.82: class Box(val item) -//│ ║ ^^^^ +//│ ║ ^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. //│ ║ l.83: fun read(box: Box) = box.item.extra //│ ╙── ^^^ @@ -270,7 +270,7 @@ read(Box(new Item)) //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. //│ ║ l.264: class Box(val item) -//│ ║ ^^^^ +//│ ║ ^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. //│ ║ l.265: fun read(box: Box) = box.Box#item.extra //│ ╙── ^^^ @@ -548,7 +548,7 @@ read(Child(new Item)) //│ ╟── Cannot resolve member 'extra' of a value with unknown shape. //│ ╟── Constructor parameter 'item' has no type annotation. //│ ║ l.539: class Parent(val item) -//│ ║ ^^^^ +//│ ║ ^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'item'. //│ ║ l.543: fun read(box: Annotated) = identity(box.item).extra //│ ╙── ^^^^^^^^^ @@ -569,7 +569,7 @@ invoke(Box(() => 42)) //│ ║ ^^^^^^^^^^^^ //│ ╟── Constructor parameter 'callback' has no type annotation. //│ ║ l.561: class Box(val callback) -//│ ║ ^^^^^^^^ +//│ ║ ^^^^^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'callback'. //│ ║ l.562: fun invoke(box: Box) = box.callback() //│ ╙── ^^^ @@ -646,7 +646,7 @@ fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. //│ ║ l.640: class Box(val value) -//│ ║ ^^^^^ +//│ ║ ^^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. //│ ║ l.642: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╙── ^^^^^^^ @@ -664,7 +664,7 @@ class Box(val value) //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. //│ ║ l.659: class Box(val value) -//│ ║ ^^^^^ +//│ ║ ^^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. //│ ║ l.660: (Box(new Item) as Box).value.oops //│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls index 84b1559ab5..9ed49d802a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls @@ -127,7 +127,7 @@ private fun excess(value: Box[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. //│ ║ l.120: class Box[T](val item: T) -//│ ╙── ^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ // A type alias checks its own argument list before interpreting the body. :ge @@ -142,7 +142,7 @@ private fun excess(value: Alias[Int, Str]) = () //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── The type parameters are declared here. //│ ║ l.135: type Alias[A] = Box[A] -//│ ╙── ^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ // Annotated results do not hide missing input types from exposure checking. :ge diff --git a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls index 170d416d34..18ebd33ed7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls @@ -211,7 +211,7 @@ CopyBox({answer: 42}).copy().value.answer //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. //│ ╟── Parameter 'value' admits values of unknown shape. //│ ║ l.205: class CopyBox(val value) with -//│ ║ ^^^^^ +//│ ║ ^^^^^^^^^ //│ ╟── 'CopyBox' is exposed by this compilation unit. //│ ║ l.205: class CopyBox(val value) with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/Projections.mls b/hkmc2/shared/src/test/mlscript/newres/Projections.mls index 19caf972cc..eae70322b5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Projections.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Projections.mls @@ -53,7 +53,7 @@ fun missing(a) = a.Later#missing() //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Class 'Later' does not contain member 'missing' //│ ║ l.7: class Later(val x) { fun get() = x } -//│ ╙── ^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :e val NotClass = 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls index 8a5516b7f0..67ad9ac462 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveEnvironment.mls @@ -40,7 +40,7 @@ Box({answer: 42}).copy().value.answer //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. //│ ╟── Parameter 'value' admits values of unknown shape. //│ ║ l.34: class Box(val value) with -//│ ║ ^^^^^ +//│ ║ ^^^^^^^^^ //│ ╟── 'Box' is exposed by this compilation unit. //│ ║ l.34: class Box(val value) with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -77,7 +77,7 @@ opened({answer: 44}) //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. //│ ╟── Parameter 'value' admits values of unknown shape. //│ ║ l.66: class OpenBox(val value) -//│ ║ ^^^^^ +//│ ║ ^^^^^^^^^ //│ ╟── This value is stored in this record field. //│ ║ l.67: let makers = {create: OpenBox} //│ ║ ^^^^^^ @@ -104,7 +104,7 @@ let pair = finish({answer: 46}) //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. //│ ╟── Parameter 'second' admits values of unknown shape. //│ ║ l.96: class Pair(val first)(val second) -//│ ║ ^^^^^^ +//│ ║ ^^^^^^^^^^ //│ ╟── This value is exposed by this compilation unit. //│ ║ l.98: let finish = partial({answer: 45}) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls index 953ed3a1c6..7b6e979b76 100644 --- a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls +++ b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls @@ -48,8 +48,8 @@ class A2(x) with @buffered class B0(x) //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.48: @buffered -//│ ╙── ^^^^^^^^^ +//│ ║ l.49: class B0(x) +//│ ╙── ^^^^^^^^^^^ :ge @buffered @@ -61,38 +61,34 @@ class B1(x) //│ ║ l.56: @buffered //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.55: @buffered -//│ ║ ^^^^^^^^^ -//│ ║ l.56: @buffered -//│ ╙── ^^^^^^^^^ +//│ ║ l.57: class B1(x) +//│ ╙── ^^^^^^^^^^^ :ge @buffered @bufferable class B2(x) //│ ╔══[COMPILATION ERROR] Only one of bufferable annotation is allowed. -//│ ║ l.70: @buffered +//│ ║ l.68: @buffered //│ ║ ^^^^^^^^^ -//│ ║ l.71: @bufferable +//│ ║ l.69: @bufferable //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.70: @buffered -//│ ║ ^^^^^^^^^ -//│ ║ l.71: @bufferable +//│ ║ l.70: class B2(x) //│ ╙── ^^^^^^^^^^^ :w @bufferable module B3 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.85: @bufferable +//│ ║ l.81: @bufferable //│ ╙── ^^^^^^^^^^^ :w @bufferable object B3 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.92: @bufferable +//│ ║ l.88: @bufferable //│ ╙── ^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 58c06a2ee2..e424c7aa11 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -89,8 +89,10 @@ foo(h) //│ ║ l.84: val x = 24 //│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Only function definitions are allowed in handler blocks +//│ ║ l.85: class Test with +//│ ║ ^^^^^^^^^^^^^^^ //│ ║ l.86: fun x() = x -//│ ╙── ^ +//│ ╙── ^^^^^^^^^^^^^^^ :e handle h = Eff with @@ -104,19 +106,19 @@ handle h = Eff with foo(h) foo(h) //│ ╔══[COMPILATION ERROR] Handler function is missing resumption parameter -//│ ║ l.100: fun f = r(0) +//│ ║ l.102: fun f = r(0) //│ ╙── ^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Handler function is missing resumption parameter -//│ ║ l.101: fun g() = r(0) +//│ ║ l.103: fun g() = r(0) //│ ╙── ^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Handler function is missing resumption parameter -//│ ║ l.102: fun h()() = r(1) +//│ ║ l.104: fun h()() = r(1) //│ ╙── ^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Handler function is missing resumption parameter -//│ ║ l.103: fun h2()(a, b) = r(1) +//│ ║ l.105: fun h2()(a, b) = r(1) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: h -//│ ║ l.104: foo(h) +//│ ║ l.106: foo(h) //│ ╙── ^ :w @@ -127,6 +129,6 @@ handle h = Eff with 12345 foo(h) //│ ╔══[WARNING] Terms in handler block do nothing -//│ ║ l.127: 12345 +//│ ║ l.129: 12345 //│ ╙── ^^^^^ //│ Elab: { handle h = Ref(member:Eff)() List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,term:Handler$h$/f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None)), HandlerTermDefinition(r,TermDefinition(Fun,member:g,term:Handler$h$/g,List(ParamList(‹›,List(Param(‹›,a,None,Modulefulness(None))),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls index cb150862a5..da06f644c5 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls @@ -110,14 +110,16 @@ fun test(x) = if x is Test(_, _, _) then "ok" //│ ╔══[COMPILATION ERROR] Resolution error in constructor //│ ║ l.109: fun test(x) = if x is Test(_, _, _) then "ok" //│ ║ ^^^^^^^^^^^^^ -//│ ╙── Class definition 'Test' expected 6 pattern arguments, but got 3 +//│ ╟── Class definition 'Test' expected 6 pattern arguments, but got 3 +//│ ║ l.53: class Test(a, b, c, d, e, f) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fun test(x) = if x is Test(_, _, _, _, _, _) then "ok" :e fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.118: fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" +//│ ║ l.120: fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `d` is not publicly accessible //│ ║ l.53: class Test(a, b, c, d, e, f) @@ -143,18 +145,20 @@ object A :e fun foo(x) = if x is A() then 0 //│ ╔══[COMPILATION ERROR] `A` is an object. -//│ ║ l.141: object A +//│ ║ l.143: object A //│ ║ ^ //│ ╟── Its pattern cannot have an argument list. -//│ ║ l.144: fun foo(x) = if x is A() then 0 +//│ ║ l.146: fun foo(x) = if x is A() then 0 //│ ╙── ^^^ :e fun foo(x) = if x is A(y) then y //│ ╔══[COMPILATION ERROR] Resolution error in constructor -//│ ║ l.153: fun foo(x) = if x is A(y) then y +//│ ║ l.155: fun foo(x) = if x is A(y) then y //│ ║ ^^^^ -//│ ╙── Module/object definition 'A' does not take pattern arguments. +//│ ╟── Module/object definition 'A' does not take pattern arguments. +//│ ║ l.143: object A +//│ ╙── ^^^^^^^^ 0 is Foo //│ = false @@ -208,7 +212,7 @@ if p is Crazy(a, b, c, d, e, f, g) then :re if p is Crazy(a, b, c, d, e, f) then 42 //│ ╔══[COMPILATION ERROR] Expected seven extraction arguments, but found only six arguments. -//│ ║ l.209: if p is Crazy(a, b, c, d, e, f) then 42 +//│ ║ l.213: if p is Crazy(a, b, c, d, e, f) then 42 //│ ╙── ^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -216,7 +220,7 @@ if p is Crazy(a, b, c, d, e, f) then 42 :re if p is Crazy(all) then 42 //│ ╔══[COMPILATION ERROR] Expected seven extraction arguments, but found only one argument. -//│ ║ l.217: if p is Crazy(all) then 42 +//│ ║ l.221: if p is Crazy(all) then 42 //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls index 67a3452581..19f9088941 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls @@ -249,10 +249,12 @@ fun arityGo(x) = if x is @compile ArityGo(r) then r else "" //│ ╔══[COMPILATION ERROR] Resolution error in constructor //│ ║ l.246: pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) //│ ║ ^^^^^^^^^^^^^^^^^ -//│ ╙── Class definition 'Pair' expected 2 pattern arguments, but got 1 +//│ ╟── Class definition 'Pair' expected 2 pattern arguments, but got 1 +//│ ║ l.14: data class Pair(val first, val second) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Class `Pair` has 2 parameters. //│ ║ l.14: data class Pair(val first, val second) -//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ //│ ╟── But 1 arguments were provided. //│ ║ l.246: pattern ArityCtx(pattern H) = H | Pair(ArityCtx(H)) //│ ╙── ^^^^^^^^^^^ @@ -270,7 +272,7 @@ pattern OrderCtx(pattern H) = H | Pair(OrderCtx(H), _) | Lit(_) pattern OrderGo = OrderCtx(OrderSum) as OrderGo | _ fun orderGo(x) = if x is @compile OrderGo(r) then r else "" //│ ╔══[WARNING] Redex alternatives must precede all recursive context alternatives. -//│ ║ l.270: pattern OrderGo = OrderCtx(OrderSum) as OrderGo | _ +//│ ║ l.272: pattern OrderGo = OrderCtx(OrderSum) as OrderGo | _ //│ ╙── ^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls index db10276797..10d64202aa 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls @@ -11,7 +11,7 @@ object A null is @compile Nullable //│ ╔══[COMPILATION ERROR] Pattern `Nullable` has one pattern parameter. //│ ║ l.6: pattern Nullable(pattern T) = null | T -//│ ║ ^ +//│ ║ ^^^^^^^^^ //│ ╟── But zero pattern arguments were provided. //│ ║ l.11: null is @compile Nullable //│ ╙── ^^^^^^^^ @@ -21,7 +21,7 @@ null is @compile Nullable null is @compile Nullable(A, A) //│ ╔══[COMPILATION ERROR] Pattern `Nullable` has one pattern parameter. //│ ║ l.6: pattern Nullable(pattern T) = null | T -//│ ║ ^ +//│ ║ ^^^^^^^^^ //│ ╟── But two pattern arguments were provided. //│ ║ l.21: null is @compile Nullable(A, A) //│ ╙── ^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls index 6c4992cef1..4ff8a290cd 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls @@ -61,7 +61,7 @@ null is Optional(Int) as Some(42) fun toIntOption(x) = if x is (@compile Optional) as o then o //│ ╔══[COMPILATION ERROR] Pattern `Optional` has one pattern parameter. //│ ║ l.48: pattern Optional(pattern T) = (null => None) | ((T as x) => Some(x)) -//│ ║ ^ +//│ ║ ^^^^^^^^^ //│ ╟── But zero pattern arguments were provided. //│ ║ l.61: fun toIntOption(x) = if x is (@compile Optional) as o then o //│ ╙── ^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls index 9da8f34786..3f455cdb04 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls @@ -17,7 +17,7 @@ pattern Baz(pattern A) = A pattern Baz(pattern A) = A() //│ ╔══[COMPILATION ERROR] `A` is a pattern parameter. //│ ║ l.17: pattern Baz(pattern A) = A() -//│ ║ ^ +//│ ║ ^^^^^^^^^ //│ ╟── It cannot be applied. //│ ║ l.17: pattern Baz(pattern A) = A() //│ ╙── ^^^ @@ -26,7 +26,7 @@ pattern Baz(pattern A) = A() pattern Baz(pattern A) = A(1, 2, 3) //│ ╔══[COMPILATION ERROR] `A` is a pattern parameter. //│ ║ l.26: pattern Baz(pattern A) = A(1, 2, 3) -//│ ║ ^ +//│ ║ ^^^^^^^^^ //│ ╟── It cannot be applied to any arguments. //│ ║ l.26: pattern Baz(pattern A) = A(1, 2, 3) //│ ╙── ^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls index 1fdfa8fac8..0a86a89fb7 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls @@ -10,7 +10,7 @@ pattern LessArity = Pair("meow") //│ ║ ^^^^^^^^^^^^ //│ ╟── Class definition 'Pair' expected 2 pattern arguments, but got 1 //│ ║ l.4: data class Pair[A, B](val first: A, val second: B) -//│ ╙── ^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :e pattern MoreArity = Pair("meow", "woof", "moo") @@ -19,4 +19,4 @@ pattern MoreArity = Pair("meow", "woof", "moo") //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class definition 'Pair' expected 2 pattern arguments, but got 3 //│ ║ l.4: data class Pair[A, B](val first: A, val second: B) -//│ ╙── ^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 23065bf0d81a3003b25cf3d3d46aff08d2ee92e0 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 22:28:46 +0800 Subject: [PATCH 146/228] Make annotation, statement, parameter-list and IR locations explicit --- .../src/main/scala/hkmc2/AsyncLowering.scala | 12 +- .../src/main/scala/hkmc2/CompilerCtx.scala | 4 +- .../src/main/scala/hkmc2/codegen/Block.scala | 158 ++++++------- .../scala/hkmc2/codegen/BlockSimplifier.scala | 16 +- .../hkmc2/codegen/BlockTransformer.scala | 18 +- .../hkmc2/codegen/BufferableTransform.scala | 18 +- .../hkmc2/codegen/ClassParamFlattener.scala | 14 +- .../FirstClassFunctionTransformer.scala | 12 +- .../codegen/FlowAnalysisBasedRewrite.scala | 24 +- .../scala/hkmc2/codegen/HandlerLowering.scala | 52 ++--- .../src/main/scala/hkmc2/codegen/Lifter.scala | 72 +++--- .../main/scala/hkmc2/codegen/Lowering.scala | 210 +++++++++--------- .../codegen/ReflectionInstrumenter.scala | 20 +- .../main/scala/hkmc2/codegen/TailRecOpt.scala | 36 +-- .../scala/hkmc2/codegen/WorkerWrapper.scala | 10 +- .../hkmc2/codegen/deforest/Rewrite.scala | 18 +- .../scala/hkmc2/codegen/js/JSBuilder.scala | 10 +- .../hkmc2/codegen/wasm/text/WatBuilder.scala | 6 +- .../main/scala/hkmc2/invalml/InvalML.scala | 2 +- .../scala/hkmc2/semantics/Elaborator.scala | 82 +++---- .../main/scala/hkmc2/semantics/Importer.scala | 12 +- .../hkmc2/semantics/InterfaceExposure.scala | 2 +- .../scala/hkmc2/semantics/NewResolver.scala | 4 +- .../src/main/scala/hkmc2/semantics/Term.scala | 101 ++++----- .../hkmc2/semantics/ucs/Normalization.scala | 20 +- .../hkmc2/semantics/ucs/TermSynthesizer.scala | 2 +- .../scala/hkmc2/semantics/ups/Compiler.scala | 8 +- .../semantics/ups/FixedPointCompiler.scala | 20 +- .../hkmc2/semantics/ups/SplitCompiler.scala | 12 +- .../src/main/scala/hkmc2/utils/utils.scala | 2 +- .../src/test/mlscript/basics/DataClass.mls | 2 + .../test/mlscript/block-staging/Functions.mls | 2 +- .../mlscript/codegen/BadConfigDirective.mls | 8 +- .../test/mlscript/codegen/BadGenerators.mls | 10 +- .../codegen/FirstClassFunctionTransform.mls | 6 +- .../test/mlscript/codegen/SourceLocations.mls | 43 ++++ .../src/test/mlscript/tailrec/Annots.mls | 42 +++- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- .../src/test/mlscript/wasm/ClassMethods.mls | 2 +- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 2 +- 40 files changed, 580 insertions(+), 518 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/codegen/SourceLocations.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/AsyncLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/AsyncLowering.scala index fcee534a8e..9118221ce6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/AsyncLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/AsyncLowering.scala @@ -56,22 +56,22 @@ class AsyncLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx, Config): ) ) val vars = fun.params.flatMap(_.paramSyms) - val noAsync = fun.annotations.filterNot(_ is Annot.Async) + val noAsync = fun.annotations.filterNot(_.isInstanceOf[Annot.Async]) val transformer = new BlockTransformer(SymbolSubst.Id): override def applySimpleSymbol(sym: SimpleSymbol): SimpleSymbol = symMap.getOrElse(sym, sym) override def applyValue(v: Value)(k: Value => Block): Block = v match case Value.This(sym) if fun.owner.contains(sym) => - k(Value.SimpleRef(thisVar)) + k(Value.SimpleRef(thisVar)(v.toLoc)) case _ => super.applyValue(v)(k) val newBody = transformer.applyBlock(wrapAwait(true)(applyFunBodyLikeBlock(fun.body))) - collectedFunDefn += FunDefn(N, outerBms, outerDsym, PlainParamList((thisParam.iterator ++ outerParams.iterator.map(_._2)).toList) :: PlainParamList(Nil) :: Nil, newBody)(fun.configOverride, noAsync) - val callArgs = (fun.owner.iterator.map(s => Arg(N, Value.This(s))) ++ fun.params.iterator.flatMap(_.allParams.iterator.map(p => Arg(N, Value.SimpleRef(p.sym))))).toList - val outerCall = Call(Value.MemberRef(outerBms, outerDsym), callArgs ne_:: Nil)(CallMetadata.mlsFunWithEffect) + collectedFunDefn += FunDefn(N, outerBms, outerDsym, PlainParamList((thisParam.iterator ++ outerParams.iterator.map(_._2)).toList)(N) :: PlainParamList(Nil)(N) :: Nil, newBody)(fun.configOverride, noAsync) + val callArgs = (fun.owner.iterator.map(s => Arg(N, Value.This(s)(N))) ++ fun.params.iterator.flatMap(_.allParams.iterator.map(p => Arg(N, Value.SimpleRef(p.sym)(N))))).toList + val outerCall = Call(Value.MemberRef(outerBms, outerDsym)(N), callArgs ne_:: Nil)(CallMetadata.mlsFunWithEffect, N) val tmp = TempSymbol(N, erasedType = outerCall.erasedValueType, "tmp") val wrapperBody = blockBuilder .assignScoped(tmp, outerCall) - .ret(Call(Value.SimpleRef(State.runtimeSymbol).selSN("toJsAsync"), (tmp.asSimpleRef.asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun)) + .ret(Call(Value.SimpleRef(State.runtimeSymbol)(N).selSN("toJsAsync"), (tmp.asSimpleRef.asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, N)) FunDefn(fun.owner, fun.sym, fun.dSym, fun.params, wrapperBody)(fun.configOverride, noAsync) override def applyMainBlock(main: Block): Block = diff --git a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala index 067c5e6d30..4f556c4e54 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/CompilerCtx.scala @@ -126,10 +126,10 @@ class CompilerCtx( case _ => t.subTerms.exists(findQuote) val hasQuote = findQuote(blk0) val blk = new Term.Blk( - Import(State.runtimeSymbol, paths.runtimeFile.toString, paths.runtimeFile) :: + Import(State.runtimeSymbol, paths.runtimeFile.toString, paths.runtimeFile)(N) :: // Only import `Term.mls` when necessary. (if hasQuote then - Import(State.termSymbol, paths.termFile.toString, paths.termFile) :: blk0.stats + Import(State.termSymbol, paths.termFile.toString, paths.termFile)(N) :: blk0.stats else blk0.stats), blk0.res diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index d66fdd8da4..d9c5fe174c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -409,8 +409,8 @@ object Throw: def error(msg: Str)(using State): Throw = Throw(Instantiate( mut = false, State.globalThisSymbol.asThis.selN(Tree.Ident("Error")), - (Value.Lit(Tree.StrLit(msg)).asArg :: Nil) :: Nil, - )(InstantiateMetadata.empty)) + (Value.Lit(Tree.StrLit(msg))(N).asArg :: Nil) :: Nil, + )(InstantiateMetadata.empty, N)) case class Label(label: LabelSymbol, loop: Bool, body: Block, rest: Block) extends Block with NonBlockTail with ProductWithTail @@ -563,11 +563,11 @@ object HandleBlock: def suspend(tag: Path, handlerFun: Path)(using Elaborator.Ctx): Result = val bms = Elaborator.ctx.builtins.runtime.suspend - Call(bms.asMemberRef(bms.asPrincipal.get), (tag.asArg :: handlerFun.asArg :: Nil) ne_:: Nil)(CallMetadata.mlsFunWithEffect) + Call(bms.asMemberRef(bms.asPrincipal.get), (tag.asArg :: handlerFun.asArg :: Nil) ne_:: Nil)(CallMetadata.mlsFunWithEffect, N) def handleSuspension(tag: Path, bodyFun: Path)(using Elaborator.Ctx): Result = val bms = Elaborator.ctx.builtins.runtime.handle_suspension - Call(bms.asMemberRef(bms.asPrincipal.get), (tag.asArg :: bodyFun.asArg :: Nil) ne_:: Nil)(CallMetadata.mlsFunWithEffect) + Call(bms.asMemberRef(bms.asPrincipal.get), (tag.asArg :: bodyFun.asArg :: Nil) ne_:: Nil)(CallMetadata.mlsFunWithEffect, N) private def create( lhs: LocalVarSymbol, @@ -581,12 +581,12 @@ object HandleBlock: )(using Elaborator.State, Elaborator.Ctx) = val sym = new BlockMemberSymbol("handleBlock$", Nil, false) - val bodyDefn = FunDefn.withFreshSymbol(N, sym, PlainParamList(Nil) :: Nil, body)(N, annotations = Nil) + val bodyDefn = FunDefn.withFreshSymbol(N, sym, PlainParamList(Nil)(N) :: Nil, body)(N, annotations = Nil) val handlerMtds = handlers.map: handler => val sym = BlockMemberSymbol(cls.nme + handler.sym.nme, Nil, true) val fDef = FunDefn.withFreshSymbol( - N, sym, PlainParamList(Param.simple(handler.resumeSym) :: Nil) :: Nil, + N, sym, PlainParamList(Param.simple(handler.resumeSym) :: Nil)(N) :: Nil, handler.body )(N, annotations = Nil) val rSym = TempSymbol(N, erasedType = N, "suspendRes") @@ -607,7 +607,7 @@ object HandleBlock: N, Nil, S(par), handlerMtds, Nil, Nil, // Apparently, the lifter is not happy with any assignment in the preCtor... - Assign(NoSymbol, Call(State.builtinOpsMap("super").asSimpleRef, args.map(_.asArg) ne_:: Nil)(CallMetadata.mlsFunWithEffect), End()), + Assign(NoSymbol, Call(State.builtinOpsMap("super").asSimpleRef, args.map(_.asArg) ne_:: Nil)(CallMetadata.mlsFunWithEffect, N), End()), End(), N, N, @@ -616,7 +616,7 @@ object HandleBlock: blockBuilder .scopedVars(Set(clsDefn.sym, sym)) .define(clsDefn) - .assign(lhs, Instantiate(mut = true, clsDefn.sym.asMemberRef(cls), Nil :: Nil)(InstantiateMetadata.empty)) + .assign(lhs, Instantiate(mut = true, clsDefn.sym.asMemberRef(cls), Nil :: Nil)(InstantiateMetadata.empty, N)) .define(bodyDefn) .assign(res, handleSuspension(lhs.asSimpleRef, bodyDefn.sym.asMemberRef(bodyDefn.dSym))) .rest(rest) @@ -710,11 +710,11 @@ final case class FunDefn( ) extends Defn: val defnSym = S(dSym) val asPath = sym.asMemberRef(dSym) - lazy val tailRec: Bool = annotations.contains(Annot.TailRec) - lazy val inline: Bool = annotations.contains(Annot.Inline) - lazy val noInline: Bool = annotations.contains(Annot.NoInline) || generator || async - lazy val generator: Bool = annotations.contains(Annot.Generator) - lazy val async: Bool = annotations.contains(Annot.Async) + lazy val tailRec: Bool = annotations.exists(_.isInstanceOf[Annot.TailRec]) + lazy val inline: Bool = annotations.exists(_.isInstanceOf[Annot.Inline]) + lazy val noInline: Bool = annotations.exists(_.isInstanceOf[Annot.NoInline]) || generator || async + lazy val generator: Bool = annotations.exists(_.isInstanceOf[Annot.Generator]) + lazy val async: Bool = annotations.exists(_.isInstanceOf[Annot.Async]) lazy val affineInfo: Ls[Int] = annotations.collect: case Annot.Affine(whichParamList) => whichParamList @@ -994,7 +994,14 @@ enum Case: sealed trait TrivialResult extends Result -sealed abstract class Result extends AutoLocated, HasErasedType: +/** The source expression that produced an IR result is independent of its operands. + * Rewrites preserve this span; generated results with no source expression use `N`. + */ +sealed abstract class Result extends Located, HasErasedType: + val toLoc: Opt[Loc] + /** Return a result with explicit provenance without mutating a shared IR node. */ + def withLoc(loc: Opt[Loc]): Result + def withLocOf(source: Located): Result = withLoc(source.toLoc) // // * Used for debugging locations: // sealed abstract class Result extends AutoLocated with ProductWithExtraInfo: // def extraInfo: Str = toLoc.toString @@ -1044,24 +1051,6 @@ sealed abstract class Result extends AutoLocated, HasErasedType: // case Instantiate(mut, cls, args) => // TODO? case _ => false - // * Note: this function is used to piece together a location; - // * for the location to be valid, we should NOT have it include children whose location - // * is from some different place (with a different Origin), such as the location attached to symbols. - // * That's why for example, we're not adding the `l` of `Value.Ref` to the children list. - protected def children: Vector[Located] = this match - case Call(fun, argss) => fun +: argss.iterator.flatten.map(_.value).toVector - case Instantiate(mut, cls, argss) => cls +: argss.iterator.flatten.map(_.value).toVector - case Cast(value, target, _) => Vector.single(value) - case Select(qual, name) => Vector.double(qual, name) - case DynSelect(qual, fld, arrayIdx) => Vector.double(qual, fld) - case Lambda(params, body) => Vector.single(params) - case Tuple(mut, elems) => elems.iterator.map(_.value).toVector - case Record(mut, elems) => elems.iterator.map(_.value).toVector - case Value.SimpleRef(l) => Vector.empty - case Value.MemberRef(bms, disamb) => Vector.empty - case Value.This(sym) => Vector.empty - case Value.Lit(lit) => Vector.single(lit) - // TODO rm Lam from values and thus the need for this method def subBlocks: Ls[Block] = this match case Call(fun, argss) => fun.subBlocks ::: argss.flatten.flatMap(_.value.subBlocks) @@ -1169,7 +1158,7 @@ sealed abstract class Result extends AutoLocated, HasErasedType: val target = expected match case ft: ErasedFuncType => ErasedType.Function(ft.rsc) case v: ErasedValueType => v - Cast(this, target, config.checkCasts) + Cast(this, target, config.checkCasts)(loc) case N => // * An `Incompatible` side is not an unrelated type but an unrepresentable one, so it gets its own message. def membersOf(et: CanonicalErasedType): Opt[(CanonicalErasedValueType, CanonicalErasedValueType)] = et match @@ -1191,7 +1180,7 @@ case class CallMetadata( mayRaiseEffects: Bool, annotations: Ls[Annot], ): - lazy val explicitTailCall: Bool = annotations.contains(Annot.TailCall) + lazy val explicitTailCall: Bool = annotations.exists(_.isInstanceOf[Annot.TailCall]) object CallMetadata: val defaultMlsFun = CallMetadata(true, false, Nil) @@ -1199,7 +1188,8 @@ object CallMetadata: val mlsFunWithEffect = CallMetadata(true, true, Nil) -case class Call(fun: Path, argss: NELs[Ls[Arg]])(val metadata: CallMetadata) extends Result: +case class Call(fun: Path, argss: NELs[Ls[Arg]])(val metadata: CallMetadata, val toLoc: Opt[Loc]) extends Result: + def withLoc(loc: Opt[Loc]): Call = if loc == toLoc then this else copy()(metadata, loc) lazy val isKnownUnsaturatedCall: Bool = fun.targetSymbol match case S(ts: TermSymbol) => @@ -1220,20 +1210,20 @@ case class Call(fun: Path, argss: NELs[Ls[Arg]])(val metadata: CallMetadata) ext object Call: - def raw(fun: Path, argss: NELs[Ls[Arg]])(metadata: CallMetadata): Call = - new Call(fun, argss)(metadata) + def raw(fun: Path, argss: NELs[Ls[Arg]])(metadata: CallMetadata, toLoc: Opt[Loc]): Call = + new Call(fun, argss)(metadata, toLoc) - def apply(fun: Path, argss: NELs[Ls[Arg]])(metadata: CallMetadata): Result = + def apply(fun: Path, argss: NELs[Ls[Arg]])(metadata: CallMetadata, toLoc: Opt[Loc]): Result = fun match case Value.SimpleRef(sym: BuiltinSymbol) => argss match case (Arg(N, arg1: Value) :: Arg(N, arg2: Value) :: Nil) :: Nil => - evalBuiltin(sym, arg1, arg2)(return _) + evalBuiltin(sym, arg1, arg2)(value => return value.withLoc(toLoc)) case (Arg(N, arg1: Value) :: Nil) :: Nil => - evalBuiltin(sym, arg1)(return _) + evalBuiltin(sym, arg1)(value => return value.withLoc(toLoc)) case _ => case _ => - raw(fun, argss)(metadata) + raw(fun, argss)(metadata, toLoc) private def literalArgValues(args: Ls[Arg]): Opt[Ls[Value]] = args.foldRight[Opt[Ls[Value]]](S(Nil)): @@ -1244,27 +1234,27 @@ object Call: private inline def evalBuiltin(sym: BuiltinSymbol, arg1: Value, arg2: Value)(inline k: Value => Unit): Unit = (sym.nme, arg1, arg2) match - case ("+", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 + v2))) - case ("+", Lit(Tree.StrLit(v1)), Lit(Tree.StrLit(v2))) => k(Lit(Tree.StrLit(v1 + v2))) - case ("-", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 - v2))) - case ("*", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 * v2))) + case ("+", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 + v2))(N)) + case ("+", Lit(Tree.StrLit(v1)), Lit(Tree.StrLit(v2))) => k(Lit(Tree.StrLit(v1 + v2))(N)) + case ("-", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 - v2))(N)) + case ("*", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 * v2))(N)) // * For "/", should check for 0 and return a DecLit. - case ("%", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) if v2 =/= 0 => k(Lit(Tree.IntLit(v1 % v2))) - case ("===", Lit(l1), Lit(l2)) => k(Lit(Tree.BoolLit(l1 == l2))) - case ("!==", Lit(l1), Lit(l2)) => k(Lit(Tree.BoolLit(l1 != l2))) - case ("<", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 < v2))) - case ("<=", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 <= v2))) - case (">", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 > v2))) - case (">=", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 >= v2))) - case ("&&", Lit(Tree.BoolLit(v1)), Lit(Tree.BoolLit(v2))) => k(Lit(Tree.BoolLit(v1 && v2))) - case ("||", Lit(Tree.BoolLit(v1)), Lit(Tree.BoolLit(v2))) => k(Lit(Tree.BoolLit(v1 || v2))) + case ("%", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) if v2 =/= 0 => k(Lit(Tree.IntLit(v1 % v2))(N)) + case ("===", Lit(l1), Lit(l2)) => k(Lit(Tree.BoolLit(l1 == l2))(N)) + case ("!==", Lit(l1), Lit(l2)) => k(Lit(Tree.BoolLit(l1 != l2))(N)) + case ("<", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 < v2))(N)) + case ("<=", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 <= v2))(N)) + case (">", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 > v2))(N)) + case (">=", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 >= v2))(N)) + case ("&&", Lit(Tree.BoolLit(v1)), Lit(Tree.BoolLit(v2))) => k(Lit(Tree.BoolLit(v1 && v2))(N)) + case ("||", Lit(Tree.BoolLit(v1)), Lit(Tree.BoolLit(v2))) => k(Lit(Tree.BoolLit(v1 || v2))(N)) case _ => private inline def evalBuiltin(sym: BuiltinSymbol, arg1: Value)(inline k: Value => Unit): Unit = (sym.nme, arg1) match - case ("+", Lit(Tree.IntLit(v1))) => k(Lit(Tree.IntLit(v1))) - case ("-", Lit(Tree.IntLit(v1))) => k(Lit(Tree.IntLit(-v1))) - case ("!", Lit(Tree.BoolLit(v))) => k(Lit(Tree.BoolLit(!v))) + case ("+", Lit(Tree.IntLit(v1))) => k(Lit(Tree.IntLit(v1))(N)) + case ("-", Lit(Tree.IntLit(v1))) => k(Lit(Tree.IntLit(-v1))(N)) + case ("!", Lit(Tree.BoolLit(v))) => k(Lit(Tree.BoolLit(!v))(N)) case _ => end Call @@ -1277,7 +1267,8 @@ case class InstantiateMetadata( object InstantiateMetadata: def empty: InstantiateMetadata = InstantiateMetadata(Nil) -case class Instantiate(mut: Bool, cls: Path, argss: Ls[Ls[Arg]])(val metadata: InstantiateMetadata) extends Result +case class Instantiate(mut: Bool, cls: Path, argss: Ls[Ls[Arg]])(val metadata: InstantiateMetadata, val toLoc: Opt[Loc]) extends Result: + def withLoc(loc: Opt[Loc]): Instantiate = if loc == toLoc then this else copy()(metadata, loc) /** A coercion of `value` to `target`. * @@ -1295,7 +1286,8 @@ case class Instantiate(mut: Bool, cls: Path, argss: Ls[Ls[Arg]])(val metadata: I * - `value` is not a `Cast`. * - `target` must be a proper subtype of `value`'s erased type. */ -case class Cast private(value: Result, target: ErasedValueType, check: Bool) extends Path +case class Cast private(value: Result, target: ErasedValueType, check: Bool)(val toLoc: Opt[Loc]) extends Path: + def withLoc(loc: Opt[Loc]): Cast = if loc == toLoc then this else copy()(loc) object Cast: /** Builds a cast while collapsing a nested cast. @@ -1309,23 +1301,28 @@ object Cast: * * Note that explicitly-checked casts are never lost to preserve the semantics of eagerly failing when casts fail. */ - def apply(value: Result, target: ErasedValueType, check: Bool): Cast = + def apply(value: Result, target: ErasedValueType, check: Bool)(toLoc: Opt[Loc]): Cast = value match - case Cast(inner, _, innerCheck) => new Cast(inner, target, check || innerCheck) - case _ => new Cast(value, target, check) + case Cast(inner, _, innerCheck) => new Cast(inner, target, check || innerCheck)(toLoc) + case _ => new Cast(value, target, check)(toLoc) -case class Lambda(params: ParamList, body: Block)(val annot: Ls[Annot]) extends Result: +case class Lambda(params: ParamList, body: Block)(val annot: Ls[Annot], val toLoc: Opt[Loc]) extends Result: + def withLoc(loc: Opt[Loc]): Lambda = if loc == toLoc then this else copy()(annot, loc) lazy val affine: Bool = annot.exists(_.isInstanceOf[Annot.Affine]) -case class Tuple(mut: Bool, elems: Ls[Arg]) extends Result +case class Tuple(mut: Bool, elems: Ls[Arg])(val toLoc: Opt[Loc]) extends Result: + def withLoc(loc: Opt[Loc]): Tuple = if loc == toLoc then this else copy()(loc) -case class Record(mut: Bool, elems: Ls[RcdArg]) extends Result +case class Record(mut: Bool, elems: Ls[RcdArg])(val toLoc: Opt[Loc]) extends Result: + def withLoc(loc: Opt[Loc]): Record = if loc == toLoc then this else copy()(loc) sealed abstract class Path extends TrivialResult: - def selN(id: Tree.Ident): Path = Select(this, id)(N)(false) - def sel(id: Tree.Ident, sym: DefinitionSymbol[?]): Path = Select(this, id)(S(sym))(false) + override def withLoc(loc: Opt[Loc]): Path + override def withLocOf(source: Located): Path = withLoc(source.toLoc) + def selN(id: Tree.Ident): Path = Select(this, id)(N, N)(false) + def sel(id: Tree.Ident, sym: DefinitionSymbol[?]): Path = Select(this, id)(S(sym), N)(false) def selSN(id: Str): Path = selN(new Tree.Ident(id)) def asArg = Arg(spread = N, this) def targetSymbol: Opt[DefinitionSymbol[?]] = this match @@ -1336,19 +1333,28 @@ sealed abstract class Path extends TrivialResult: /** * @param symbol The symbol representing the definition that the selection refers to, if known. */ -case class Select(qual: Path, name: Tree.Ident)(val symbol: Opt[DefinitionSymbol[?]])(val sanitize: Boolean) extends Path with ProductWithExtraInfo: +case class Select(qual: Path, name: Tree.Ident)(val symbol: Opt[DefinitionSymbol[?]], val toLoc: Opt[Loc])(val sanitize: Boolean) extends Path with ProductWithExtraInfo: + def withLoc(loc: Opt[Loc]): Select = if loc == toLoc then this else copy()(symbol, loc)(sanitize) def extraInfo(using DebugPrinter): Str = symbol.map(s => s"sym=${s.showAsPlain}").mkString -case class DynSelect(qual: Path, fld: Path, arrayIdx: Bool) extends Path +case class DynSelect(qual: Path, fld: Path, arrayIdx: Bool)(val toLoc: Opt[Loc]) extends Path: + def withLoc(loc: Opt[Loc]): DynSelect = if loc == toLoc then this else copy()(loc) enum Value extends Path with ProductWithExtraInfo: - case SimpleRef(sym: SimpleSymbol) + case SimpleRef(sym: SimpleSymbol)(val toLoc: Opt[Loc]) /** * @param disamb The symbol disambiguating the definition that the reference refers to. */ - case MemberRef(bms: BlockMemberSymbol, disamb: DefinitionSymbol[?]) - case This(sym: InnerSymbol) - case Lit(lit: Literal) + case MemberRef(bms: BlockMemberSymbol, disamb: DefinitionSymbol[?])(val toLoc: Opt[Loc]) + case This(sym: InnerSymbol)(val toLoc: Opt[Loc]) + case Lit(lit: Literal)(val toLoc: Opt[Loc]) + + override def withLoc(loc: Opt[Loc]): Value = if loc == toLoc then this else this match + case v: SimpleRef => v.copy()(loc) + case v: MemberRef => v.copy()(loc) + case v: This => v.copy()(loc) + case v: Lit => v.copy()(loc) + override def withLocOf(source: Located): Value = withLoc(source.toLoc) override def extraInfo(using DebugPrinter): Str = this match case MemberRef(bms, disamb) => s"disamb=${disamb.showAsPlain}" @@ -1437,13 +1443,13 @@ extension (k: Block => Block) def blockBuilder: Block => Block = identity extension (s: SimpleSymbol) - inline def asSimpleRef: Value.SimpleRef = Value.SimpleRef(s) + inline def asSimpleRef: Value.SimpleRef = Value.SimpleRef(s)(N) extension (bms: BlockMemberSymbol) - inline def asMemberRef(disamb: DefinitionSymbol[?]): Value.MemberRef = Value.MemberRef(bms, disamb) + inline def asMemberRef(disamb: DefinitionSymbol[?]): Value.MemberRef = Value.MemberRef(bms, disamb)(N) extension (sym: InnerSymbol) - inline def asThis: Value.This = Value.This(sym) + inline def asThis: Value.This = Value.This(sym)(N) extension (l: ValueSymbol) // TODO(Derppening): Inline `Value.Ref.apply` into this function once that function is removed diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index 1ecdad3993..fab852731a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -289,7 +289,7 @@ class BlockSimplifier case Value.SimpleRef(loc: LocalVarSymbol) if localVars.contains(loc) && !definedVars.contains(loc) => registerChange(s"${loc.showDbg} is never assigned; replacing read with undefined") // if !symbolsToPreserve(loc) then removedLocals += loc - k(Value.Lit(syntax.Tree.UnitLit(false))) + k(Value.Lit(syntax.Tree.UnitLit(false))(v.toLoc)) case _ => super.applyValue(v)(k) override def applyBlock(b: Block): Block = b match @@ -863,7 +863,7 @@ class BlockSimplifier registerChange(s"immediate assigned call prefix ${lhs.showDbg} ~> ${path.showDbg}") applyPath(path): path2 => val lhs2 = recordAssignmentFact(lhs, path2, ass) - val combined = Call(path2, argss)(call.metadata).withLocOf(call) + val combined = Call(path2, argss)(call.metadata, call.toLoc) val res = applyBlock(Assign(nextLhs, combined, rst)) // * Note that it is incorrect to eliminate the `lhs` assignment even if `!rst.freeVars(lhs)`, // * because the assignment may be visible from an outer block @@ -878,7 +878,7 @@ class BlockSimplifier registerChange(s"immediate returned call prefix ${lhs.showDbg} ~> ${path.showDbg}") applyPath(path): path2 => val lhs2 = recordAssignmentFact(lhs, path2, ass) - val combined = Call(path2, argss)(call.metadata).withLocOf(call) + val combined = Call(path2, argss)(call.metadata, call.toLoc) val res = applyBlock(Return(combined)) if symbolsToPreserve(lhs) then Assign(lhs2, path2, res) else res @@ -1157,7 +1157,7 @@ class BlockSimplifier analysis.litValue match case true => registerChange(s"${loc.showDbg} ~> undefined") - return k(Value.Lit(syntax.Tree.UnitLit(false))) + return k(Value.Lit(syntax.Tree.UnitLit(false))(v.toLoc)) case lit: (Value | Cast) => registerChange(s"${loc.showDbg} ~> ${lit.showDbg}") return k(lit) @@ -1203,7 +1203,7 @@ class BlockSimplifier prefix.metadata.mayRaiseEffects || c.metadata.mayRaiseEffects, prefix.metadata.annotations ++ c.metadata.annotations, ), - ).withLocOf(c) + c.toLoc) super.applyResult(combined)(k) case N => super.applyResult(r)(k) @@ -1474,7 +1474,7 @@ class BlockSimplifier if args.size < params.params.size then return N val (fixedArgs, restArgs) = args.splitAt(params.params.size) S(fixedArgs.zip(params.params).map((arg, param) => (param.sym, arg.value)) ++ - List((params.restParam.get.sym, Tuple(true, restArgs)))) + List((params.restParam.get.sym, Tuple(true, restArgs)(N)))) /** Match multiple argument lists against multiple parameter lists. * Returns None if any arg list fails to match its corresponding param list, @@ -1917,8 +1917,8 @@ class BlockSimplifier acc(Scoped(Set(resSym), newBlk( k(Call(resSym.asSimpleRef, extraArgss.ne_!)( call.metadata.copy( - annotations = call.metadata.annotations.filterNot(_ == Annot.TailCall), - )))))) + annotations = call.metadata.annotations.filterNot(_.isInstanceOf[Annot.TailCall]), + ), call.toLoc))))) case (sym, value) :: argRest => val newSym = VarSymbol(sym.id, erasedType = sym.erasedType) go(acc.assignScoped(newSym, value), argRest, mapping + (sym -> newSym)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index 8f3588070a..0f863beadc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -164,33 +164,33 @@ class BlockTransformer(subst: SymbolSubst): applyPath(fun): fun2 => applyListOf(argss, (args, k2) => applyArgs(args)(k2)): argss2 => k(if (fun2 is fun) && (argss2 is argss) then r - else Call(fun2, argss2.ne_!)(r.metadata).withLocOf(r)) + else Call(fun2, argss2.ne_!)(r.metadata, r.toLoc)) case r @ Instantiate(mut, cls, argss) => applyPath(cls): cls2 => applyListOf(argss, (args, k2) => applyArgs(args)(k2)): argss2 => k(if (cls2 is cls) && (argss2 is argss) then r - else Instantiate(mut, cls2, argss2)(r.metadata).withLocOf(r)) + else Instantiate(mut, cls2, argss2)(r.metadata, r.toLoc)) case l: Lambda => k(applyLam(l)) case Tuple(mut, elems) => applyArgs(elems): elems2 => - k(if (elems2 is elems) then r else Tuple(mut, elems2).withLocOf(r)) + k(if (elems2 is elems) then r else Tuple(mut, elems2)(r.toLoc)) case Record(mut, fields) => applyRcdArgs(fields): fields2 => - k(if fields2 is fields then r else Record(mut, fields2).withLocOf(r)) + k(if fields2 is fields then r else Record(mut, fields2)(r.toLoc)) case p: Path => applyPath(p)(k) def applyPath(p: Path)(k: Path => Block): Block = p match case DynSelect(qual, fld, arrayIdx) => applyPath(qual): qual2 => applyPath(fld): fld2 => - k(if (qual2 is qual) && (fld2 is fld) then p else DynSelect(qual2, fld2, arrayIdx).withLocOf(p)) + k(if (qual2 is qual) && (fld2 is fld) then p else DynSelect(qual2, fld2, arrayIdx)(p.toLoc)) case p @ Select(qual, name) => applyPath(qual): qual2 => val sym2 = p.symbol.mapConserve(_.subst) - k(if (qual2 is qual) && (sym2 is p.symbol) then p else Select(qual2, name)(sym2)(p.sanitize).withLocOf(p)) + k(if (qual2 is qual) && (sym2 is p.symbol) then p else Select(qual2, name)(sym2, p.toLoc)(p.sanitize)) case c @ Cast(value, target, check) => applyResult(value): value2 => - k(if value2 is value then c else Cast(value2, target, check).withLocOf(c)) + k(if value2 is value then c else Cast(value2, target, check)(c.toLoc)) case v: Value => applyValue(v)(k) def applyValue(v: Value)(k: Value => Block) = v match @@ -304,7 +304,7 @@ class BlockTransformer(subst: SymbolSubst): val params2 = pl.params.mapConserve(applyParam) val rest2 = pl.restParam.mapConserve(applyParam) if (params2 is pl.params) && (rest2 is pl.restParam) - then pl else ParamList(pl.flags, params2, rest2) + then pl else ParamList(pl.flags, params2, rest2)(pl.toLoc) def applyCase(cse: Case)(k: Case => Block): Block = cse match case Case.Lit(lit) => k(cse) @@ -327,7 +327,7 @@ class BlockTransformer(subst: SymbolSubst): def applyLam(lam: Lambda): Lambda = val params2 = applyParamList(lam.params) val body2 = applyFunBodyLikeBlock(lam.body) - if (params2 is lam.params) && (body2 is lam.body) then lam else Lambda(params2, body2)(lam.annot) + if (params2 is lam.params) && (body2 is lam.body) then lam else Lambda(params2, body2)(lam.annot, lam.toLoc) def applyListOf[A](ls: List[A], f: (A, (A => Block)) => Block)(k: List[A] => Block): Block = def rec(ls: List[A], k: List[A] => Block): Block = ls match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala index aa9b81d710..800f5ff664 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BufferableTransform.scala @@ -35,15 +35,15 @@ class BufferableTransform()(using State, Raise): .toMap def mapParam(p: Param) = Param(p.flags, varMap(p.sym), p.sign, p.modulefulness)(p.toLoc) - (params.map(pl => ParamList(pl.flags, pl.params.map(mapParam), pl.restParam.map(mapParam))), varMap.toMap) + (params.map(pl => ParamList(pl.flags, pl.params.map(mapParam), pl.restParam.map(mapParam))(pl.toLoc)), varMap.toMap) def mkFieldReplacer(buf: VarSymbol, baseIdx: VarSymbol, symMap: Map[SimpleSymbol, SimpleSymbol]) = - def getOffset(off: Int)(k: Path => Block): Block = + def getOffset(off: Int, loc: Opt[Loc])(k: Path => Block): Block = val idxSymbol = new TempSymbol(N, erasedType = S(ErasedType.Int), "idx") - Scoped(Set.single(idxSymbol), Assign(idxSymbol, Call(State.builtinOpsMap("+").asSimpleRef, (baseIdx.asSimpleRef.asArg :: Value.Lit(Tree.IntLit(off)).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun), - k(DynSelect(buf.asSimpleRef.selSN("buf"), idxSymbol.asSimpleRef, true)))) + Scoped(Set.single(idxSymbol), Assign(idxSymbol, Call(State.builtinOpsMap("+").asSimpleRef, (baseIdx.asSimpleRef.asArg :: Value.Lit(Tree.IntLit(off))(N).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, N), + k(DynSelect(buf.asSimpleRef.selSN("buf"), idxSymbol.asSimpleRef, true)(loc)))) def assignToOffset(off: Int, r: Result, rst: Block) = val idxSymbol = new TempSymbol(N, erasedType = S(ErasedType.Int), "idx") - Scoped(Set.single(idxSymbol), Assign(idxSymbol, Call(State.builtinOpsMap("+").asSimpleRef, (baseIdx.asSimpleRef.asArg :: Value.Lit(Tree.IntLit(off)).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun), + Scoped(Set.single(idxSymbol), Assign(idxSymbol, Call(State.builtinOpsMap("+").asSimpleRef, (baseIdx.asSimpleRef.asArg :: Value.Lit(Tree.IntLit(off))(N).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, N), AssignDynField(buf.asSimpleRef.selSN("buf"), idxSymbol.asSimpleRef, true, r, applyBlock(rst)))) new BlockTransformer(SymbolSubst.Id): override def applySimpleSymbol(sym: SimpleSymbol): SimpleSymbol = symMap.getOrElse(sym, sym) @@ -66,11 +66,11 @@ class BufferableTransform()(using State, Raise): case sel: Select => sel.symbol.fold(super.applyPath(p)(k)): sym => fieldMap.get(sym).orElse(pubFieldMap.get(sym).flatMap(fieldMap.get(_))).fold(super.applyPath(p)(k)): off => - getOffset(off): res => + getOffset(off, p.toLoc): res => k(res) case r: Value.Ref => fieldMap.get(r.symbol).fold(super.applyPath(p)(k)): off => - getOffset(off): res => + getOffset(off, p.toLoc): res => k(res) case _ => super.applyPath(p)(k) def transformFunDefn(f: FunDefn, isCtor: Bool): FunDefn = @@ -79,7 +79,7 @@ class BufferableTransform()(using State, Raise): val (newParams, symMap) = mkSymbolReplacer(f.params) val blk = mkFieldReplacer(buf, idx, symMap).applyBlock(f.body) FunDefn(f.owner, f.sym, TermSymbol(f.dSym.k, f.dSym.owner, f.dSym.id, erasedType = N), PlainParamList( - Param.simple(buf) :: Param.simple(idx) :: Nil) :: newParams, + Param.simple(buf) :: Param.simple(idx) :: Nil)(N) :: newParams, if isCtor then Begin(blk, Return(idx.asSimpleRef)) else blk)(configOverride = f.configOverride, annotations = f.annotations) val fakeCtor = transformFunDefn(FunDefn.withFreshSymbol( S(companionSym), @@ -92,7 +92,7 @@ class BufferableTransform()(using State, Raise): fakeCtor :: cls.methods.map(transformFunDefn(_, false)), Nil, clsSizeSym -> clsSizeTermSym :: Nil, - Define(ValDefn(clsSizeTermSym, clsSizeSym, Value.Lit(Tree.IntLit(fields.size)))(N, Nil), End()), + Define(ValDefn(clsSizeTermSym, clsSizeSym, Value.Lit(Tree.IntLit(fields.size))(N))(N, Nil), End()), annotations = Nil, ) k: diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassParamFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassParamFlattener.scala index caa313e9aa..20bc2f7942 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassParamFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassParamFlattener.scala @@ -30,14 +30,14 @@ class ClassParamFlattener(using State) extends BlockTransformer(SymbolSubst.Id): val flags = paramss.headOption.fold(ParamListFlags.empty)(_.flags) val (init, last) = paramss.splitAt(paramss.length - 1) val params = init.flatMap(_.allParams) ::: last.flatMap(_.params) - ParamList(flags, params, last.flatMap(_.restParam).headOption) + ParamList(flags, params, last.flatMap(_.restParam).headOption)(Loc(paramss)) /** Normalize class params so that `paramsOpt = N` and `auxParams` has exactly one element. */ private def flattenClsParams(cls: ClsLikeDefn): ClsLikeDefn = if cls.paramsOpt.isEmpty && cls.auxParams.sizeIs == 1 then return cls val paramss = cls.paramsOpt.toList ::: cls.auxParams val flatAux = paramss match - case Nil => PlainParamList(Nil) + case Nil => PlainParamList(Nil)(N) case single :: Nil => single case _ => flattenParamLists(paramss) cls.copy(paramsOpt = N, auxParams = flatAux :: Nil)( @@ -46,8 +46,8 @@ class ClassParamFlattener(using State) extends BlockTransformer(SymbolSubst.Id): ) private def classPathFor(fun: Path, cls: ClassSymbol): Opt[Path] = fun match - case Value.MemberRef(bms, _) => S(bms.asMemberRef(cls)) - case s @ Select(qual, name) => S(Select(qual, name)(S(cls))(s.sanitize)) + case Value.MemberRef(bms, _) => S(bms.asMemberRef(cls).withLoc(fun.toLoc)) + case s @ Select(qual, name) => S(Select(qual, name)(S(cls), s.toLoc)(s.sanitize)) case _ => N private def saturatedCurriedClassCall(fun: Path, argss: NELs[Ls[Arg]]): Opt[Path] = @@ -77,7 +77,7 @@ class ClassParamFlattener(using State) extends BlockTransformer(SymbolSubst.Id): else argss2 k: if flatArgss is argss then c - else Call(r, flatArgss)(c.metadata).withLocOf(c) + else Call(r, flatArgss)(c.metadata, c.toLoc) case call @ Call(fun, argss) => saturatedCurriedClassCall(fun, argss) match case S(cls) => @@ -86,7 +86,7 @@ class ClassParamFlattener(using State) extends BlockTransformer(SymbolSubst.Id): val flatArgss = if argss2.lengthCompare(1) > 0 then argss2.flatten ne_:: Nil else argss2 - k(Instantiate(false, cls2, flatArgss)(InstantiateMetadata(call.metadata.annotations)).withLocOf(call)) + k(Instantiate(false, cls2, flatArgss)(InstantiateMetadata(call.metadata.annotations), call.toLoc)) case N => super.applyResult(r)(k) case inst @ Instantiate(mut, cls, argss) => @@ -97,7 +97,7 @@ class ClassParamFlattener(using State) extends BlockTransformer(SymbolSubst.Id): else argss2 k: if (cls2 is cls) && (flatArgss is argss) then inst - else Instantiate(mut, cls2, flatArgss)(inst.metadata).withLocOf(inst) + else Instantiate(mut, cls2, flatArgss)(inst.metadata, inst.toLoc) case _ => super.applyResult(r)(k) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala index 8849ad1e4c..a2a6ae484f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala @@ -30,11 +30,11 @@ class FirstClassFunctionTransformer val args = params.params.map(_.sym.asSimpleRef.asArg) ::: params.restParam.toList.map(p => Arg(S(SpreadKind.Eager), p.sym.asSimpleRef)) val callDef = FunDefn.withFreshSymbol(Some(clsSym), new BlockMemberSymbol("call", Nil, true), params :: Nil, - Return(Call(p, args ne_:: Nil)(CallMetadata.defaultMlsFun)))(N, annotations = Nil) + Return(Call(p, args ne_:: Nil)(CallMetadata.defaultMlsFun, N)))(N, annotations = Nil) ClsLikeDefn(None, clsSym, defSym, None, syntax.Cls, None, Nil, - Some(Select(State.globalThisSymbol.asThis, Tree.Ident("Function"))(Some(ctx.builtins.Function))(false)), + Some(Select(State.globalThisSymbol.asThis, Tree.Ident("Function"))(Some(ctx.builtins.Function), N)(false)), callDef :: Nil, Nil, Nil, Assign.discard( - Call(State.builtinOpsMap("super").asSimpleRef, Nil ne_:: Nil)(CallMetadata.defaultFun), + Call(State.builtinOpsMap("super").asSimpleRef, Nil ne_:: Nil)(CallMetadata.defaultFun, N), End()), End(), None, None)(N, annotations = Nil) private def getParamList(l: BlockMemberSymbol): Option[ParamList] = funDefns.get(l) match @@ -63,7 +63,7 @@ class FirstClassFunctionTransformer val clsDef = generateFCFunctionClass(p, params) val tmp = new TempSymbol(None, erasedType = S(ErasedType.ValueLike(rsc = S(false), clsDef.isym.asClsOrMod.get))) val cls = clsDef.sym.asMemberRef(clsDef.isym) - Scoped(Set(clsDef.sym, tmp), Define(clsDef, Assign(tmp, Instantiate(false, cls, Nil :: Nil)(InstantiateMetadata.empty), k(tmp.asSimpleRef)))) + Scoped(Set(clsDef.sym, tmp), Define(clsDef, Assign(tmp, Instantiate(false, cls, Nil :: Nil)(InstantiateMetadata.empty, p.toLoc), k(tmp.asSimpleRef.withLoc(p.toLoc))))) override def applyPath(p: Path)(k: Path => Block): Block = p match case ref @ Value.MemberRef(l, disamb) => disamb match @@ -80,7 +80,7 @@ class FirstClassFunctionTransformer ErrorReport(msg"Cannot get ${s.nme}'s parameter list." -> sel.toLoc :: Nil, source = Diagnostic.Source.Compilation) - PlainParamList(Nil) + PlainParamList(Nil)(N) etaExpandPath(sel, params)(k) case Some(_) => k(p) case _ => @@ -94,7 +94,7 @@ class FirstClassFunctionTransformer def call(f: Path) = if (f is fun) && (argss is argss2) then c - else Call(f, argss2.ne_!)(c.metadata) + else Call(f, argss2.ne_!)(c.metadata, c.toLoc) fun match case ref @ Value.SimpleRef(sym) => sym match case _: VarSymbol | _: TempSymbol => k(call(ref.selSN("call"))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala index 958d35d332..1e87748365 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/FlowAnalysisBasedRewrite.scala @@ -41,7 +41,7 @@ class FlowAnalysisBasedRewrite( case (param, i) => if eliminable(i) then removed.add(param.sym) else keptParams.append(param) - ParamList(pl.flags, keptParams.toList, pl.restParam) -> removed.toSet + ParamList(pl.flags, keptParams.toList, pl.restParam)(pl.toLoc) -> removed.toSet private def filterFunParams(funSym: TermSymbol, params: Ls[ParamList]): (Ls[ParamList], Set[VarSymbol]) = val removed = MutSet.empty[VarSymbol] @@ -94,7 +94,7 @@ class FlowAnalysisBasedRewrite( .toList val restParam = Option.when(targets.hasRestParam)(etaParam("rest")) EtaParamList( - ParamList(ParamListFlags.empty, params, restParam), + ParamList(ParamListFlags.empty, params, restParam)(N), params.map(p => Arg(N, p.sym.asSimpleRef)) ::: restParam.toList.map(p => Arg(S(SpreadKind.Eager), p.sym.asSimpleRef)), ) @@ -102,7 +102,7 @@ class FlowAnalysisBasedRewrite( lastWords("not the same shape?") private def etaCall(base: Path): Result = - Call(base, activeEtaArgss.ne_!)(CallMetadata.mlsFunWithEffect) + Call(base, activeEtaArgss.ne_!)(CallMetadata.mlsFunWithEffect, N) override def rewriteFunDefn(fun: FunDefn): RewrittenFunDefn = @@ -123,7 +123,7 @@ class FlowAnalysisBasedRewrite( override def applyValue(v: Value)(k: Value => Block): Block = v match case ref@Value.SimpleRef(l: VarSymbol) if activeEliminatedParams(l) => - k(Value.Lit(Tree.UnitLit(false)).withLocOf(ref)) + k(Value.Lit(Tree.UnitLit(false))(ref.toLoc)) case _ => super.applyValue(v)(k) override def applyBlock(b: Block): Block = b match @@ -138,7 +138,7 @@ class FlowAnalysisBasedRewrite( Return(etaCall(p).withLocOf(res2)) case c @ Call(fun, argss) => Return( - Call(fun, (argss ++ activeEtaArgss).ne_!)(c.metadata)) + Call(fun, (argss ++ activeEtaArgss).ne_!)(c.metadata, c.toLoc)) case _ => val tmp = TempSymbol(N, erasedType = N, "eta$res") Scoped( @@ -174,7 +174,7 @@ class FlowAnalysisBasedRewrite( if deadConstructorElimSolver.deadCtors.contains(ConcreteId(ctorSite.uid, instId)) => (args.reverseIterator.map(_.value) ++ selectedFrom) .filterNot(_.isPure) - .foldLeft(k(Value.Lit(Tree.UnitLit(false)).withLocOf(ctorSite))): (rest, p) => + .foldLeft(k(Value.Lit(Tree.UnitLit(false))(ctorSite.toLoc))): (rest, p) => applyPath(p)(Assign.discard(_, rest)) case c@Call(fun, args :: restArgss) if args.forall(_.spread.isEmpty) => val eliminable = deadParamElimSolver.eliminableCallSiteArgs(ConcreteId(c.uid, instId)) @@ -182,7 +182,7 @@ class FlowAnalysisBasedRewrite( rewriteArgs(args, eliminable): args2 => k( if (fun2 is fun) && (args2 is args) then c - else Call(fun2, args2 ne_:: restArgss)(c.metadata).withLocOf(c) + else Call(fun2, args2 ne_:: restArgss)(c.metadata, c.toLoc) ) case i@Instantiate(mut, cls, args :: restArgss) if args.forall(_.spread.isEmpty) => val eliminable = deadParamElimSolver.eliminableCallSiteArgs(ConcreteId(i.uid, instId)) @@ -190,7 +190,7 @@ class FlowAnalysisBasedRewrite( rewriteArgs(args, eliminable): args2 => k( if (cls2 is cls) && (args2 is args) then i - else Instantiate(mut, cls2, args2 :: restArgss)(i.metadata).withLocOf(i) + else Instantiate(mut, cls2, args2 :: restArgss)(i.metadata, i.toLoc) ) case _ => super.applyResult(r)(k) @@ -219,9 +219,9 @@ class FlowAnalysisBasedRewrite( withEtaArgss(etaParams.map(_.args)): applyFunBodyLikeBlock(lam.body) val wrappedBody = etaParams.map(_.params).foldRight(body2): (params, body) => - Return(Lambda(params, body)(Nil)) + Return(Lambda(params, body)(Nil, N)) if (params2 is lam.params) && (wrappedBody is lam.body) then lam - else Lambda(params2, wrappedBody)(lam.annot).withLocOf(lam) + else Lambda(params2, wrappedBody)(lam.annot, lam.toLoc) end Rewriter @@ -264,8 +264,8 @@ class FlowAnalysisBasedRewrite( refreshParamMap(p.sym) = newSym Param(p.flags, newSym, p.sign, p.modulefulness)(p.toLoc) val refreshedParams = rewrittenParams.map: - case ParamList(flags, params, restParam) => - ParamList(flags, params.map(refreshParam), restParam.map(refreshParam)) + case pl @ ParamList(flags, params, restParam) => + ParamList(flags, params.map(refreshParam), restParam.map(refreshParam))(pl.toLoc) FunDefn( N, bms, tSym, refreshedParams, new RefreshSymbol(refreshParamMap.toMap).apply(rewrittenBody))( diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index cab9354767..1837096030 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -24,12 +24,12 @@ object HandlerLowering: private val nextIdent: Tree.Ident = Tree.Ident("next") private val lastIdent: Tree.Ident = Tree.Ident("last") private val contTraceIdent: Tree.Ident = Tree.Ident("contTrace") - private def unit = Value.Lit(Tree.UnitLit(true)) - private def intLit(i: BigInt) = Value.Lit(Tree.IntLit(i)) + private def unit = Value.Lit(Tree.UnitLit(true))(N) + private def intLit(i: BigInt) = Value.Lit(Tree.IntLit(i))(N) private def locToStr(loc: Loc) = val (line, _, col) = loc.origin.fph.getLineColAt(loc.spanStart) - Value.Lit(Tree.StrLit(s"${loc.origin.fileName.last}:${line + loc.origin.startLineNum - 1}:$col")) + Value.Lit(Tree.StrLit(s"${loc.origin.fileName.last}:${line + loc.origin.startLineNum - 1}:$col"))(N) extension (p: Path) def pc = p.selN(pcIdent) @@ -71,7 +71,7 @@ object HandlerLowering: resumeInfo.argLists ++: (intLit(restoreList.length) :: restoreList.map(_.asPath)) - ).map(_.asArg) ne_:: Nil)(CallMetadata.mlsFunWithEffect)) + ).map(_.asArg) ne_:: Nil)(CallMetadata.mlsFunWithEffect, N)) // argLists: length-encoded argument list used for resumption. // currentLocals: All locals to be saved and reloaded, this cannot include any variables in outer scopes @@ -131,7 +131,7 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R private def rtThrowMsg(msg: Str) = Throw.error(msg) object PureCall: - def apply(fun: Path, args: List[Path]) = Call(fun, args.map(Arg(N, _)) ne_:: Nil)(CallMetadata.defaultMlsFun) + def apply(fun: Path, args: List[Path]) = Call(fun, args.map(Arg(N, _)) ne_:: Nil)(CallMetadata.defaultMlsFun, N) def unapply(res: Result) = res match case Call(fun, args :: Nil) => args.foldRight[Opt[List[Path]]](S(Nil)): (arg, acc) => acc.flatMap: acc => @@ -144,7 +144,7 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R object StateTransition: private val transitionSymbol = freshTmp(erasedType = N, "transition") def apply(uid: StateId) = - Return(PureCall(transitionSymbol.asSimpleRef, List(Value.Lit(Tree.IntLit(uid))))) + Return(PureCall(transitionSymbol.asSimpleRef, List(Value.Lit(Tree.IntLit(uid))(N)))) def unapply(blk: Block) = blk match case Return(PureCall(Value.SimpleRef(`transitionSymbol`), List(Value.Lit(Tree.IntLit(uid))))) => S(uid) @@ -153,7 +153,7 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R object Unwind: private val unwindSymbol = freshTmp(erasedType = N, "unwind") def apply(uid: StateId, loc: Value) = - Return(PureCall(unwindSymbol.asSimpleRef, List(Value.Lit(Tree.IntLit(uid)), loc))) + Return(PureCall(unwindSymbol.asSimpleRef, List(Value.Lit(Tree.IntLit(uid))(N), loc))) def unapply(blk: Block) = blk match case Return(PureCall(Value.SimpleRef(`unwindSymbol`), List(Value.Lit(Tree.IntLit(uid)), loc: Value))) => S(uid, loc) @@ -537,9 +537,9 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R val varList = scopedVars.collect: case sym: LocalVarSymbol => sym val sortedVars = varList.toList.sortBy(_.uid) - val debugInfo = Value.Lit(Tree.StrLit(debugNme)).asArg :: sortedVars.zipWithIndex.filter(_._1.isInstanceOf[VarSymbol]) + val debugInfo = Value.Lit(Tree.StrLit(debugNme))(N).asArg :: sortedVars.zipWithIndex.filter(_._1.isInstanceOf[VarSymbol]) .flatMap: (sym, idx) => - List(intLit(idx), Value.Lit(Tree.StrLit(sym.nme))) + List(intLit(idx), Value.Lit(Tree.StrLit(sym.nme))(N)) .map(_.asArg) val debugInfoSym = freshTmp(erasedType = S(ErasedType.Array), s"$debugNme$$debugInfo") // TODO: properly support spread argument by calculating the correct length. @@ -556,16 +556,16 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R val preTransform = new BlockTransformer(SymbolSubst.Id): override def applyResult(r: Result)(k: Result => Block): Block = r match case Call(Value.MemberRef(sym, _), args) if sym is Elaborator.ctx.builtins.runtime.suspend => - k(Call(paths.mkEffectPath, args)(CallMetadata.mlsFunWithEffect)) + k(Call(paths.mkEffectPath, args)(CallMetadata.mlsFunWithEffect, N)) case Call(Value.MemberRef(sym, _), args) if sym is Elaborator.ctx.builtins.runtime.handle_suspension => - k(Call(paths.enterHandleBlockPath, args)(CallMetadata.mlsFunWithEffect)) + k(Call(paths.enterHandleBlockPath, args)(CallMetadata.mlsFunWithEffect, N)) case _ => super.applyResult(r)(k) override def applyDefn(defn: Defn)(k: Defn => Block): Block = defn match case fun: FunDefn => if h.currentBlockIsTrulyNested && opt.isDefined then raise(lifterReport(msg"Unexpected nested function: lambdas may not function correctly." -> fun.sym.toLoc :: Nil)) val (debugInfoSym, debugInfo, fun2) = translateFunLike(fun, fun.sym.asMemberRef(fun.dSym), N, fun.sym.nme) - if debugEnabled then Scoped(Set.single(debugInfoSym), Assign(debugInfoSym, Tuple(false, debugInfo), k(fun2))) else k(fun2) + if debugEnabled then Scoped(Set.single(debugInfoSym), Assign(debugInfoSym, Tuple(false, debugInfo)(N), k(fun2))) else k(fun2) case defn @ ClsLikeDefn(owner, isym, sym, ctorSym, kind, paramsOpt, auxParams, parentPath, methods, privateFields, publicFields, preCtor, ctor, companion, bufferable) => if h.currentBlockIsTrulyNested && opt.isDefined then raise(lifterReport(msg"Unexpected nested class: lambdas may not function correctly." -> isym.toLoc :: Nil)) @@ -602,7 +602,7 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R defn.copy(methods = newMtds, preCtor = newPreCtor, ctor = newCtor, companion = companion2)(defn.configOverride, defn.annotations) if debugEnabled then Scoped(debugInfos.map(_._1).toSet, debugInfos.foldRight(k(c2)): (elem, blk) => - Assign(elem._1, Tuple(false, elem._2), blk)) + Assign(elem._1, Tuple(false, elem._2)(N), blk)) else k(c2) case _ => super.applyDefn(defn)(k) val b = preTransform.applyBlock(blk) @@ -655,14 +655,14 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R val transform = postTransform: uid => assert(uid === nextState) if isSimple then - Assign(pcVar, Value.Lit(Tree.IntLit(uid)), End()) + Assign(pcVar, Value.Lit(Tree.IntLit(uid))(N), End()) else Break(lblSym) val transformed = transform.applyBlock(blk.blk) if isSimple then transformed else Label( lblSym, false, transformed, - Assign(pcVar, Value.Lit(Tree.IntLit(nextState)), End()) + Assign(pcVar, Value.Lit(Tree.IntLit(nextState))(N), End()) ) line match case head :: next => @@ -701,18 +701,18 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R val getSavedTmp = freshTmp(erasedType = S(ErasedType.Int), "saveOffset") def getSaved(off: BigInt): (Block => Block, Path) = if off == 0 then - return (id, DynSelect(paths.runtimePath.selSN("resumeArr"), paths.runtimePath.selSN("resumeIdx"), true)) - val addOne = Assign(getSavedTmp, Call(State.builtinOpsMap("+").asSimpleRef, (paths.runtimePath.selSN("resumeIdx").asArg :: intLit(off).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultFun), _) - (addOne, DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asSimpleRef, true)) + return (id, DynSelect(paths.runtimePath.selSN("resumeArr"), paths.runtimePath.selSN("resumeIdx"), true)(N)) + val addOne = Assign(getSavedTmp, Call(State.builtinOpsMap("+").asSimpleRef, (paths.runtimePath.selSN("resumeIdx").asArg :: intLit(off).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultFun, N), _) + (addOne, DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asSimpleRef, true)(N)) - val resumeArrIndexed = DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asSimpleRef, true) + val resumeArrIndexed = DynSelect(paths.runtimePath.selSN("resumeArr"), getSavedTmp.asSimpleRef, true)(N) val plus = State.builtinOpsMap("+").asSimpleRef val preRestore = blockBuilder .assign(pcVar, paths.resumePc) .scopedVars(Set(getSavedTmp)) val restoreVars = vars.zipWithIndex.foldLeft(preRestore): case (builder, (local, idx)) => builder - .assign(getSavedTmp, if idx == 0 then paths.resumeIdx else Call(plus, (getSavedTmp.asSimpleRef.asArg :: intLit(1).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultFun)) + .assign(getSavedTmp, if idx == 0 then paths.resumeIdx else Call(plus, (getSavedTmp.asSimpleRef.asArg :: intLit(1).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultFun, N)) .assign(local, resumeArrIndexed) if needsStackSafety then @@ -721,7 +721,7 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R .ifthen(paths.curEffect, Case.Lit(Tree.UnitLit(true)), End(), S( ctx.doUnwind(ctx.resumeInfo.currentStackSafetySym.fold(_.toLoc, _.toLoc).fold(unit)(locToStr(_)), if oneState then intLit(-1) else pcVar.asSimpleRef, vars)(using paths))) - .assign(curDepth, Call(plus, (paths.stackDepthPath.asArg :: intLit(1).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultFun)) + .assign(curDepth, Call(plus, (paths.stackDepthPath.asArg :: intLit(1).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultFun, N)) .rest(mainBody) if !oneState then @@ -747,10 +747,10 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R */ private def postTranslateTopLevelCtx(b: Block)(using HandlerCtx): Block = - postTranslateIllegalEffectCtx(b, Call.raw(paths.topLevelEffectPath, (Value.Lit(Tree.BoolLit(debugEnabled)).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun), stackSafety.map(_.stackLimit)) + postTranslateIllegalEffectCtx(b, Call.raw(paths.topLevelEffectPath, (Value.Lit(Tree.BoolLit(debugEnabled))(N).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, N), stackSafety.map(_.stackLimit)) private def postTranslateIllegalEffectCtx(b: Block, reason: Str)(using HandlerCtx): Block = - postTranslateIllegalEffectCtx(b, Call.raw(paths.illegalEffectPath, (Value.Lit(Tree.StrLit(reason)).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun), N) + postTranslateIllegalEffectCtx(b, Call.raw(paths.illegalEffectPath, (Value.Lit(Tree.StrLit(reason))(N).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, N), N) /** * Translate the block and apply stack safety wrapper if needed. If needsStackSafety is true, @@ -761,11 +761,11 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R val withStackSafe = needsStackSafety match case S(stackLimit) => val bodSym = BlockMemberSymbol("‹stack safe body›", Nil, false) - val bodFun = FunDefn.withFreshSymbol(N, bodSym, ParamList(ParamListFlags.empty, Nil, N) :: Nil, Ret(r))(configOverride = N, annotations = Nil) + val bodFun = FunDefn.withFreshSymbol(N, bodSym, ParamList(ParamListFlags.empty, Nil, N)(N) :: Nil, Ret(r))(configOverride = N, annotations = Nil) blockBuilder .scopedVars(Set.single(bodSym)) .define(bodFun) - .assign(l, Call(paths.runStackSafePath, (intLit(stackLimit).asArg :: Value.MemberRef(bodSym, bodFun.dSym).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun)) + .assign(l, Call(paths.runStackSafePath, (intLit(stackLimit).asArg :: Value.MemberRef(bodSym, bodFun.dSym)(N).asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, N)) case N => blockBuilder.assign(l, r) withStackSafe @@ -795,7 +795,7 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R val transformed = blockBuilder .staticif( opt.fold(false)(!_.doNotInstrumentTopLevelModCtor), - _.assign(NoSymbol, Call(paths.resetEffects, Nil ne_:: Nil)(CallMetadata.defaultMlsFun)) + _.assign(NoSymbol, Call(paths.resetEffects, Nil ne_:: Nil)(CallMetadata.defaultMlsFun, N)) ) .rest(translateBlock(prog.main, ctx, Set.empty)) if transformed is prog.main then prog diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index b5acdc0016..ea7bc9afb8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -125,7 +125,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): case Sym(l) => l.asSimpleRef case ThisPath(sym) => sym.asThis case BmsRef(l, d) => l.asMemberRef(d) - case Field(path, field) => Select(path, field.id)(S(field))(false) + case Field(path, field) => Select(path, field.id)(S(field), N)(false) def asArg(using ctx: LifterCtxNew) = read.asArg @@ -162,7 +162,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): case Sym(l) => l.asSimpleRef case PathRef(path) => path case InScope(l, d) => l.asMemberRef(d) - case Field(isym, l, d) => Select(ctx.symbolsMap(isym).read, Tree.Ident(l.nme))(S(d))(false) + case Field(isym, l, d) => Select(ctx.symbolsMap(isym).read, Tree.Ident(l.nme))(S(d), N)(false) def asArg(using ctx: LifterCtxNew) = read.asArg @@ -368,11 +368,11 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): def join2: Block = // Resolve reference to unlifted object resolveDefnRef(d, r) match - case Some(value) => k(c.copy(fun = value, argss = newArgss.ne_!)(c.metadata).withLoc(c.toLoc)) + case Some(value) => k(c.copy(fun = value, argss = newArgss.ne_!)(c.metadata, c.toLoc)) case None => super.applyPath(c.fun): fun2 => // Nothing to rewrite if (fun2 is c.fun) && (argss is newArgss) then k(c) - else k(c.copy(fun = fun2, argss = newArgss.ne_!)(c.metadata).withLoc(c.toLoc)) + else k(c.copy(fun = fun2, argss = newArgss.ne_!)(c.metadata, c.toLoc)) r match // Call to lifted function: Rewrite using the efficient version case f: LiftedFunc => k(f.rewriteCall(c, newArgss)) @@ -386,12 +386,12 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): applyArgss(argss): newArgss => def join = if argss is newArgss then inst - else inst.copy(argss = newArgss)(inst.metadata).withLoc(inst.toLoc) + else inst.copy(argss = newArgss)(inst.metadata, inst.toLoc) ctx.rewrittenScopes.get(d) match case N => k(join) case S(c: LiftedClass) => c.rewriteInstantiate(inst, newArgss)(k) case S(r) => resolveDefnRef(d, r) match - case Some(value) => k(Instantiate(inst.mut, value, newArgss)(inst.metadata).withLoc(inst.toLoc)) + case Some(value) => k(Instantiate(inst.mut, value, newArgss)(inst.metadata, inst.toLoc)) case None => k(join) case _ => super.applyResult(r)(k) @@ -598,7 +598,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): S(ClassCtorSymbol(syntax.Fun, N, clsSym)), syntax.Cls, N, - PlainParamList(sortedVars.iterator.map(_.param).toList) :: Nil, None, Nil, Nil, + PlainParamList(sortedVars.iterator.map(_.param).toList)(N) :: Nil, None, Nil, Nil, Nil, End(), sortedVars.iterator.foldLeft[Block](End()): @@ -689,7 +689,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): captureClass.sym.asMemberRef(captureClass.isym), captureInfo._2.map( (sym, _) => sym.asPath.asArg) :: Nil - )(InstantiateMetadata.empty) + )(InstantiateMetadata.empty, N) else lastWords("tried to instantiate an empty capture") protected final def addExtraSyms(b: Block, captureSym: => LocalVarSymbol, objSyms: Iterable[ScopedSymbol]): Block = @@ -885,7 +885,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): */ sealed trait ClsLikeRewrittenScope[T](sym: InnerSymbol) extends RewrittenScope[T]: lazy val captureSym = TermSymbol(syntax.ImmutVal, S(sym), Tree.Ident(obj.nme + "$cap"), erasedType = captureType) - override lazy val capturePath = Select(sym.asThis, captureSym.id)(S(captureSym))(false) + override lazy val capturePath = Select(sym.asThis, captureSym.id)(S(captureSym), N)(false) protected val liftedObjsOrdered: List[InnerSymbol] = node.liftedObjSyms.toList.sortBy(_.uid) protected val liftedObjsSyms: Map[InnerSymbol, TermSymbol] = liftedObjsOrdered.map: s => s -> TermSymbol(syntax.ImmutVal, S(sym), Tree.Ident(s.nme + "$"), erasedType = N) @@ -912,7 +912,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): private def dupParam(p: Param): Param = p.copy(sym = VarSymbol(Tree.Ident(p.sym.nme), erasedType = p.sym.erasedType))(p.toLoc) private def dupParams(plist: List[Param]): List[Param] = plist.map(dupParam) private def dupParamList(plist: ParamList): ParamList = - plist.copy(params = dupParams(plist.params), restParam = plist.restParam.map(dupParam)) + plist.copy(params = dupParams(plist.params), restParam = plist.restParam.map(dupParam))(plist.toLoc) /* CONCRETE IMPLS */ @@ -968,7 +968,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): with ClsLikeRewrittenScope[ClsLikeDefn](obj.cls.isym): private val captureSym = TermSymbol(syntax.ImmutVal, S(obj.cls.isym), Tree.Ident(obj.nme + "$cap"), erasedType = N) - override lazy val capturePath: Path = Select(obj.cls.isym.asThis, captureSym.id)(S(captureSym))(false) + override lazy val capturePath: Path = Select(obj.cls.isym.asThis, captureSym.id)(S(captureSym), N)(false) override def rewriteImpl: LifterResult[ClsLikeDefn] = val liftedSuper = obj.cls.parentPath.flatMap: @@ -1002,7 +1002,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): with ClsLikeRewrittenScope[ClsLikeBody](obj.clsBody.isym): private val captureSym = TermSymbol(syntax.ImmutVal, S(obj.clsBody.isym), Tree.Ident(obj.nme + "$cap"), erasedType = N) - override lazy val capturePath: Path = Select(obj.clsBody.isym.asThis, captureSym.id)(S(captureSym))(false) + override lazy val capturePath: Path = Select(obj.clsBody.isym.asThis, captureSym.id)(S(captureSym), N)(false) override def rewriteImpl: LifterResult[ClsLikeBody] = val rewriterCtor = new BlockRewriter(N) @@ -1059,20 +1059,20 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // Definition with the auxiliary parameters merged into the first parameter list. private def mkFlattenedDefn: LifterResult[FunDefn] = val newPlists = fun.params match - case head :: next => head.copy(params = auxParams ::: head.params) :: next - case Nil => PlainParamList(auxParams) :: Nil + case head :: next => head.copy(params = auxParams ::: head.params)(head.toLoc) :: next + case Nil => PlainParamList(auxParams)(N) :: Nil val rewriter = new BlockRewriter(N) val newBod = rewriter.rewrite(fun.body) val withCapture = addExtraSyms(newBod) val newDefn = fun.copy(owner = N, sym = mainSym, dSym = mainDsym, params = newPlists, body = withCapture)( fun.configOverride, - if liftedFromStagedModule && !fun.isStaged then Annot.Modifier(Keyword.`staged`) :: fun.annotations + if liftedFromStagedModule && !fun.isStaged then Annot.Modifier(Keyword.`staged`)(N) :: fun.annotations else fun.annotations) LifterResult(newDefn, rewriter.extraDefns.toList) // Definition with the auxiliary parameters as a new first parameter list. private def mkAuxDefn: FunDefn = - val newPList = PlainParamList(dupParams(auxParams)) + val newPList = PlainParamList(dupParams(auxParams))(N) val (newPlists, syms, restSym) = fun.params match case head :: _ => val duped = dupParamList(head) @@ -1086,7 +1086,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): syms.map(sym => Arg(N, sym.asSimpleRef)) ::: Arg(S(SpreadKind.Eager), value.asSimpleRef) :: Nil case None => syms.map(s => Arg(N, s.asSimpleRef)) - val call = Call(fun.sym.asMemberRef(fun.dSym), args ne_:: Nil)(CallMetadata.mlsFunWithEffect) + val call = Call(fun.sym.asMemberRef(fun.dSym), args ne_:: Nil)(CallMetadata.mlsFunWithEffect, N) val bod = Return(call) FunDefn( @@ -1095,19 +1095,19 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): auxDsym, newPlists, bod - )(N, if fun.noInline then fun.annotations else Annot.Inline :: fun.annotations) + )(N, if fun.noInline then fun.annotations else Annot.Inline()(N) :: fun.annotations) private val aux = Lazy[Defn](mkAuxDefn) def rewriteCall(c: Call, argss: NELs[List[Arg]])(using ctx: LifterCtxNew): Call = if isTrivial then if argss is c.argss then c - else c.copy(argss = argss)(c.metadata).withLocOf(c) + else c.copy(argss = argss)(c.metadata, c.toLoc) else Call.raw( mainSym.asMemberRef(mainDsym), (formatArgs ::: argss.head) ne_:: argss.tail - )(c.metadata.copy(isMlsFun = true)).withLoc(c.toLoc) + )(c.metadata.copy(isMlsFun = true), c.toLoc) def rewriteRef(using ctx: LifterCtxNew): Call = if isTrivial then lastWords("tried to rewrite a ref to a trivial function") @@ -1115,7 +1115,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): Call.raw( auxSym.asMemberRef(auxDsym), formatArgs ne_:: Nil - )(CallMetadata.defaultMlsFun) + )(CallMetadata.defaultMlsFun, N) def rewriteImpl: LifterResult[FunDefn] = val LifterResult(lifted, extra) = mkFlattenedDefn @@ -1126,7 +1126,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): with ClsLikeRewrittenScope[ClsLikeDefn](obj.cls.isym): private val captureSym = TermSymbol(syntax.ImmutVal, S(obj.cls.isym), Tree.Ident(obj.nme + "$cap"), erasedType = N) - override lazy val capturePath: Path = Select(obj.cls.isym.asThis, captureSym.id)(S(captureSym))(false) + override lazy val capturePath: Path = Select(obj.cls.isym.asThis, captureSym.id)(S(captureSym), N)(false) private val passedSymsMap_ : Map[ValueSymbol, (vs: VarSymbol, ts: TermSymbol)] = passedSymsOrdered.map: s => val erasedType = s.mapErasedValueType @@ -1173,7 +1173,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): ::: capturesOrdered.map(x => capSymsMap_(x).vs) ::: passedSymsOrdered.map(x => passedSymsMap_(x).vs)) .map(Param.simple(_)) - lazy val auxParamList = PlainParamList(auxParams) + lazy val auxParamList = PlainParamList(auxParams)(N) // Whether this can be lifted without the need to pass extra parameters. lazy val isTrivial = auxParams.isEmpty @@ -1187,7 +1187,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): def mkFlattenedDefn: FunDefn = // Symbols for the aux parameter list val auxSyms = auxParams.map(p => VarSymbol(Tree.Ident(p.sym.nme), erasedType = p.sym.erasedType)) - val auxParamListLocal = PlainParamList(auxSyms.map(Param.simple(_))) + val auxParamListLocal = PlainParamList(auxSyms.map(Param.simple(_)))(N) val dupedClsAuxParams = cls.auxParams.map(dupParamList(_)) val dupedMainOpt = cls.paramsOpt.map(dupParamList(_)) @@ -1224,14 +1224,14 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): val argsList = appliedMainAndAuxArgs(appliedClsAuxArgs) val ref = obj.cls.sym.asMemberRef(obj.cls.isym) - val inst = Instantiate(false, ref, argsList)(InstantiateMetadata.empty) + val inst = Instantiate(false, ref, argsList)(InstantiateMetadata.empty, N) val bod = Return(inst) FunDefn(N, flattenedSym, flattenedDSym, allParamLists, bod)(N, annotations = Nil) private val flat = Lazy[Defn](mkFlattenedDefn) - def instObject = Instantiate(false, cls.sym.asMemberRef(cls.isym), formatArgs :: Nil)(InstantiateMetadata.empty) + def instObject = Instantiate(false, cls.sym.asMemberRef(cls.isym), formatArgs :: Nil)(InstantiateMetadata.empty, N) // Rewrite a naked reference to a parameterized class constructor. // Returns a Call to the curried C$ wrapper partially applied with formatArgs. @@ -1241,30 +1241,30 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): Call.raw( flattenedSym.asMemberRef(flattenedDSym), formatArgs ne_:: Nil - )(CallMetadata.defaultMlsFun) + )(CallMetadata.defaultMlsFun, N) def rewriteInstantiate(inst: Instantiate, argss: List[List[Arg]])(k: Result => Block): Block = if obj.isObj then lastWords("tried to rewrite instantiate for an object") val path = cls.sym.asMemberRef(cls.isym) if isTrivial then if (inst.cls === path) && (inst.argss is argss) then k(inst) - else k(inst.copy(cls = path, argss = argss)(inst.metadata).withLocOf(inst)) + else k(inst.copy(cls = path, argss = argss)(inst.metadata, inst.toLoc)) else if cls.paramsOpt.isEmpty && cls.auxParams.isEmpty then // Paramless class: lifter args go directly into the Instantiate constructor - k(Instantiate(inst.mut, path, (formatArgs ::: argss.head) :: argss.tail)(inst.metadata).withLoc(inst.toLoc)) + k(Instantiate(inst.mut, path, (formatArgs ::: argss.head) :: argss.tail)(inst.metadata, inst.toLoc)) else // Parameterized class: use Instantiate with original args + lifter args inserted after the first list - k(Instantiate(inst.mut, path, argss.head :: formatArgs :: argss.tail)(inst.metadata).withLoc(inst.toLoc)) + k(Instantiate(inst.mut, path, argss.head :: formatArgs :: argss.tail)(inst.metadata, inst.toLoc)) def rewriteSuperCall(superCall: Call, argss: List[List[Arg]])(k: Result => Block): Block = if obj.isObj then lastWords("tried to rewrite instantiate for an object") if isTrivial then k(superCall) else if cls.paramsOpt.isEmpty && cls.auxParams.isEmpty then // Paramless class: lifter args go directly into the Instantiate constructor - k(Call(superCall.fun, (formatArgs ::: argss.head) ne_:: argss.tail)(CallMetadata.defaultMlsFun).withLoc(superCall.toLoc)) + k(Call(superCall.fun, (formatArgs ::: argss.head) ne_:: argss.tail)(CallMetadata.defaultMlsFun, superCall.toLoc)) else // Parameterized class: use Instantiate with original args + lifter args inserted after the first list - k(Call(superCall.fun, argss.head ne_:: formatArgs ne_:: argss.tail)(CallMetadata.mlsFunWithEffect).withLoc(superCall.toLoc)) + k(Call(superCall.fun, argss.head ne_:: formatArgs ne_:: argss.tail)(CallMetadata.mlsFunWithEffect, superCall.toLoc)) def rewriteCall(c: Call, argss: NELs[List[Arg]])(k: Result => Block)(using ctx: LifterCtxNew): Block = if obj.isObj then lastWords("tried to rewrite instantiate for an object") @@ -1275,16 +1275,16 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): Call.raw( flattenedSym.asMemberRef(flattenedDSym), (formatArgs :: argss).ne_! - )(c.metadata.copy(isMlsFun = true, mayRaiseEffects = false)).withLoc(c.toLoc) + )(c.metadata.copy(isMlsFun = true, mayRaiseEffects = false), c.toLoc) if isTrivial then if c.argss is argss then k(c) - else k(c.copy(argss = argss)(c.metadata).withLocOf(c)) + else k(c.copy(argss = argss)(c.metadata, c.toLoc)) else if cls.paramsOpt.isEmpty && cls.auxParams.isEmpty then // Paramless class: unreachable lastWords("Call to paramless class") else if argss.lengthCompare(clsParamLists.length) === 0 then // Parameterized class: Same as Instantiate case - k(Instantiate(false, path, argss.head :: formatArgs :: argss.tail)(InstantiateMetadata(c.metadata.annotations)).withLoc(c.toLoc)) + k(Instantiate(false, path, argss.head :: formatArgs :: argss.tail)(InstantiateMetadata(c.metadata.annotations), c.toLoc)) else // Unsaturated constructor calls must remain ordinary curried calls to // the lifted wrapper; only saturated constructor applications may @@ -1352,7 +1352,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): methods = newMtds, auxParams = newAuxList )(obj.cls.configOverride, - if liftedFromStagedModule && !obj.cls.isStaged then Annot.Modifier(Keyword.`staged`) :: obj.cls.annotations + if liftedFromStagedModule && !obj.cls.isStaged then Annot.Modifier(Keyword.`staged`)(N) :: obj.cls.annotations else obj.cls.annotations) val extrasDefns = rewriterCtor.extraDefns.toList ::: rewriterPreCtor.extraDefns.toList ::: extras LifterResult(newCls, flat :: extrasDefns) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 7eb97e0813..683e13be4c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -77,7 +77,7 @@ import LoweringCtx.loweringCtx object Lowering: def compError: Block = - Throw(Value.Lit(Tree.StrLit("This code cannot be run as its compilation yielded an error."))) + Throw(Value.Lit(Tree.StrLit("This code cannot be run as its compilation yielded an error."))(N)) def fail(err: ErrorReport)(using Raise): Block = raise(err) @@ -159,10 +159,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu ) ++ blt.wasm.instrIntrinsics.map(_ -> SpecialBuiltin.WasmIntrinsic) lazy val unreachableFn = - Select(State.runtimeSymbol.asSimpleRef, Tree.Ident("unreachable"))(S(State.unreachableSymbol))(false) + Select(State.runtimeSymbol.asSimpleRef, Tree.Ident("unreachable"))(S(State.unreachableSymbol), N)(false) def unit: Path = - Select(State.runtimeSymbol.asSimpleRef, Tree.Ident("Unit"))(S(State.unitSymbol))(false) + Select(State.runtimeSymbol.asSimpleRef, Tree.Ident("Unit"))(S(State.unitSymbol), N)(false) private def memberIdent(nme: Tree.Ident, sym: Opt[MemberSymbol]): Tree.Ident = sym match @@ -179,7 +179,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu new Tree.Ident(sym.nme).withLocOf(nme) // type Rcd = (mut: Bool, args: List[RcdArg]) // * Better, but Scala's patmat exhaustiveness chokes on it - type Rcd = (Bool, List[RcdArg]) + type Rcd = (Bool, List[RcdArg], Opt[Loc]) /** Lowers `t` in tail-return position, coercing the result to the enclosing function's declared return type. */ def returnedTerm(t: st, returnType: Opt[ErasedType])(using LoweringCtx): Block = @@ -229,21 +229,21 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case Nil => res match case R(res) => term(res, inStmtPos = inStmtPos)(k) - case L((mut, flds)) => - k(Record(mut, flds.reverse)) + case L((mut, flds, loc)) => + k(Record(mut, flds.reverse)(loc)) case RcdSpread(bod) :: stats => res match case R(_) => wat("RcdField in non-Rcd context", res) - case L((mut, flds)) => + case L((mut, flds, loc)) => subTerm(bod): l => - blockImpl(stats, L((mut, RcdArg(N, l) :: flds))) + blockImpl(stats, L((mut, RcdArg(N, l) :: flds, loc))) case RcdField(lhs, rhs, _) :: stats => res match case R(_) => wat("RcdField in non-Rcd context", res) - case L((mut, flds)) => + case L((mut, flds, loc)) => subTerm(lhs): l => subTerm_nonTail(rhs): r => - blockImpl(stats, L((mut, RcdArg(S(l), r) :: flds))) + blockImpl(stats, L((mut, RcdArg(S(l), r) :: flds, loc))) case (decl @ LetDecl(sym, annotations)) :: stats => reportAnnotations(decl, annotations) if sym.asTrm.forall(_.owner.isEmpty) then @@ -315,7 +315,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case S(comp) => comp.defn.getOrElse(wat("Module companion without definition", mod.companion)) case N => val stagedAnnots = mod.annotations.collect: - case Annot.Modifier(Keyword.`staged`) => Annot.Modifier(Keyword.`staged`) + case a @ Annot.Modifier(Keyword.`staged`) => a ClassDef.Plain(mod.owner, syntax.Cls, new ClassSymbol(Tree.DummyTypeDef(syntax.Cls), mod.sym.id), mod.bsym, Nil, @@ -479,7 +479,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu raise(ErrorReport( msg"Extending a partially applied class is not supported" -> loc :: Nil, source = Diagnostic.Source.Compilation)) - k(Call(fr, acc.reverse.ne_!)(CallMetadata(isMlsFun, true, Nil)).withLoc(loc)) + k(Call(fr, acc.reverse.ne_!)(CallMetadata(isMlsFun, true, Nil), loc)) zipArgs(ctorParamLists, args, Nil) case Nil => if !ctorParamLists.isEmpty then @@ -487,7 +487,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu msg"Extending a partially applied class is not supported" -> loc :: Nil, source = Diagnostic.Source.Compilation)) // * No arguments to a super ctor means a nullary call, e.g., `extends C` means `extends C()` - k(Call(fr, Nil ne_:: Nil)(CallMetadata(isMlsFun, true, Nil)).withLoc(loc)) + k(Call(fr, Nil ne_:: Nil)(CallMetadata(isMlsFun, true, Nil), loc)) /** Lower a call with multiple argument lists into `Call` nodes, * trying to group as many as possible into a single one @@ -498,16 +498,16 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case (ps :: remainingParams, args :: remainingArgs) => lowerArgs(args, expectedParamTypes(ps))(as => zipArgs(remainingParams, remainingArgs, as :: acc, mayRaiseEffects)) case (Nil, Nil) => - k(Call(fr, acc.reverse.ne_!)(CallMetadata(isMlsFun, mayRaiseEffects, annotations)).withLoc(loc)) + k(Call(fr, acc.reverse.ne_!)(CallMetadata(isMlsFun, mayRaiseEffects, annotations), loc)) case (Nil, args :: remainingArgss) => acc.reverse match case Nil => lowerRemainingCalls(fr, args, remainingArgss, annotations, loc)(k) case acc: NELs[Ls[Arg]] => - val call = Call(fr, acc)(CallMetadata(isMlsFun, mayRaiseEffects, Nil)).withLoc(loc) + val call = Call(fr, acc)(CallMetadata(isMlsFun, mayRaiseEffects, Nil), loc) val tmp = loweringCtx.registerTempSymbol(N, erasedType = call.erasedValueType, "baseCall") Assign(tmp, call, lowerRemainingCalls(tmp.asSimpleRef, args, remainingArgss, annotations, loc)(k)) case (_ :: _, Nil) => - k(Call(fr, acc.reverse.ne_!)(CallMetadata(isMlsFun, mayRaiseEffects, annotations)).withLoc(loc)) + k(Call(fr, acc.reverse.ne_!)(CallMetadata(isMlsFun, mayRaiseEffects, annotations), loc)) fr.targetSymbol match case S(fs: TermSymbol) => fs.defn match @@ -519,7 +519,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu def lowerRemainingCalls(base: Path, args: Term, remainingArgss: Ls[Term], annotations: Ls[Annot], loc: Opt[Loc]) (k: Result => Block)(using LoweringCtx): Block = lowerArgs(args, Nil): as => - val call = Call(base, as ne_:: Nil)(CallMetadata(false, true, annotations)).withLoc(loc) + val call = Call(base, as ne_:: Nil)(CallMetadata(false, true, annotations), loc) remainingArgss match case Nil => k(call) case args :: remainingArgss => @@ -532,10 +532,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu * when they correspond to constructor parameter lists of the same class. * If fewer argument lists are provided than constructor parameter lists, eta-expands * the missing ones with fresh lambdas (avoiding reliance on mutable JS class curry semantics). */ - def lowerMultiInstantiate(mut: Bool, cls: Path, args: Ls[Term], annotations: Ls[Annot])(k: Result => Block)(using LoweringCtx): Block = + def lowerMultiInstantiate(mut: Bool, cls: Path, args: Ls[Term], annotations: Ls[Annot], loc: Opt[Loc])(k: Result => Block)(using LoweringCtx): Block = // Nullary instantiations are represented with one empty argument list, matching existing `Instantiate` usage. def buildInstantiate(argss: Ls[Ls[Arg]]): Instantiate = - Instantiate(mut, cls, if argss.isEmpty then Nil :: Nil else argss)(InstantiateMetadata(annotations)) + Instantiate(mut, cls, if argss.isEmpty then Nil :: Nil else argss)(InstantiateMetadata(annotations), loc) // * Zip constructor param lists with argument lists, accumulating lowered args. // * Consumes one argument list per constructor param list; when all ctor params are // * consumed but extra args remain, falls back to `Call` nodes on the result. @@ -561,9 +561,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val freshSyms = ps.params.map(p => new VarSymbol(new Tree.Ident(p.sym.nme), erasedType = N)) softTODO(ps.restParam.isEmpty, "Eta expanding rest parameters in constructor definitions is not yet supported") val freshParams = (ps.params zip freshSyms).map((p, s) => Param(p.flags, s, N, p.modulefulness)(p.toLoc)) - val freshParamList = ParamList(ps.flags, freshParams, N) + val freshParamList = ParamList(ps.flags, freshParams, N)(ps.toLoc) val freshArgs = freshSyms.map(s => Arg(N, s.asSimpleRef)) - Lambda(freshParamList, Return(etaExpand(rest, accArgss :+ freshArgs)))(Nil) + Lambda(freshParamList, Return(etaExpand(rest, accArgss :+ freshArgs)))(Nil, loc) k(etaExpand(remainingParamss, acc.reverse)) // * Resolve the class definition to get the constructor param lists. // * The class path typically resolves to a TermSymbol (the constructor function), @@ -711,8 +711,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu private def selectionPath(sel: NewSel, prefix: Path, name: Tree.Ident, target: Opt[DefinitionSymbol[?]]): Path = sel.tupleIndex match - case S(index) => DynSelect(prefix, Value.Lit(Tree.IntLit(BigInt(index))), true).withLocOf(sel) - case N => Select(prefix, name)(target)(false).withLocOf(sel) + case S(index) => DynSelect(prefix, Value.Lit(Tree.IntLit(BigInt(index)))(N), true)(sel.toLoc) + case N => Select(prefix, name)(target, sel.toLoc)(false) /** A projection must identify its class as well as its member: different * classes can inherit the same definition, and wildcard receivers can differ. */ @@ -793,7 +793,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val t2 = new Tree.Ident("arg2") val p1 = Param(FldFlags.empty, VarSymbol(t1, erasedType = N), N, Modulefulness.none)(t1.toLoc) val p2 = Param(FldFlags.empty, VarSymbol(t2, erasedType = N), N, Modulefulness.none)(t2.toLoc) - val ps = PlainParamList(p1 :: p2 :: Nil) + val ps = PlainParamList(p1 :: p2 :: Nil)(N) val bod = st.App(ref, st.Tup(List(st.Ref(p1.sym)(t1, N).resolve, st.Ref(p2.sym)(t2, N).resolve)) (Tree.Tup(Nil // FIXME should not be required (using dummy value) )))( @@ -804,11 +804,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val (paramLists, bodyBlock) = setupFunctionDef(ps :: Nil, bod, S(sym.nme), N) tl.log(s"Ref builtin $sym") assert(paramLists.length === 1) - return k(Lambda(paramLists.head, bodyBlock)(Nil).withLocOf(ref)) + return k(Lambda(paramLists.head, bodyBlock)(Nil, ref.toLoc)) if sym.unary then val t1 = new Tree.Ident("arg") val p1 = Param(FldFlags.empty, VarSymbol(t1, erasedType = N), N, Modulefulness.none)(t1.toLoc) - val ps = PlainParamList(p1 :: Nil) + val ps = PlainParamList(p1 :: Nil)(N) val bod = st.App(ref, st.Tup(List(st.Ref(p1.sym)(t1, N).resolve)) (Tree.Tup(Nil // FIXME should not be required (using dummy value) )))( @@ -819,7 +819,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val (paramLists, bodyBlock) = setupFunctionDef(ps :: Nil, bod, S(sym.nme), N) tl.log(s"Ref builtin $sym") assert(paramLists.length === 1) - return k(Lambda(paramLists.head, bodyBlock)(Nil).withLocOf(ref)) + return k(Lambda(paramLists.head, bodyBlock)(Nil, ref.toLoc)) case bs: BlockMemberSymbol => disamb.flatMap(_.defn) match case S(d) if d.hasDeclareModifier.isDefined => @@ -835,14 +835,14 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu if isImplicitNullaryCall(td.tsym) then return k(Call( bs.asMemberRef(disamb.get).withLocOf(ref), Nil ne_:: Nil - )(CallMetadata(isMlsFun = true, mayRaiseEffects = true, annots))) + )(CallMetadata(isMlsFun = true, mayRaiseEffects = true, annots), ref.toLoc)) case S(td: TermDefinition) => td.tsym.owner match case S(owner) => // * With the current Elaborator semantics, selections are already inserted for most things; // * the current case can only happen if `td` is a let binding defined in some object owner. softAssert(td.k is syntax.LetBind, s"Expected a let binding, got a ${td.k.str} ($td)") - return k(Select(owner.asThis, td.tsym.id)(S(td.tsym))(false).withLocOf(ref)) + return k(Select(owner.asThis, td.tsym.id)(S(td.tsym), ref.toLoc)(false)) case N => () case S(_) => () case N => () // TODO panic here; can only lower refs to elab'd symbols @@ -850,8 +850,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu sym.owner match case S(owner) => warnStmt - val sel = Select(owner.asThis, sym.id)(S(sym))(false) - return k(sel.withLocOf(ref)) + val sel = Select(owner.asThis, sym.id)(S(sym), ref.toLoc)(false) + return k(sel) case N => () case _ => () warnStmt @@ -875,7 +875,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu @tailrec def extractAnnots(t: st, acc: List[Annot]): (List[Annot], st) = t match - case st.Annotated(Annot.Async, trm) => + case st.Annotated(Annot.Async(), trm) => val ident = new Tree.Ident("asyncBody").withLocOf(trm) val bms = BlockMemberSymbol(ident.name, Nil, false) val dsym = TermSymbol( @@ -888,13 +888,13 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu syntax.Fun, bms, dsym, - sem.ParamList(sem.ParamListFlags.empty, Nil, N) :: Nil, + sem.ParamList(sem.ParamListFlags.empty, Nil, N)(N) :: Nil, N, N, S(trm), TermDefFlags.empty, Modulefulness.none, - Annot.RaiseEffects :: Nil, + Annot.RaiseEffects()(N) :: Nil, N, )(trm.toLoc) val rewritten = st.App( @@ -911,8 +911,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu // * `@untyped` discards the term's declared type by binding its value to a local declared with `Unknown`. // * Done here rather than on the IR node so that it reaches every shape of term, not just annotatable ones. - val isUntyped = allAnnots.contains(Annot.Untyped) - val annots = if isUntyped then allAnnots.filterNot(_ === Annot.Untyped) else allAnnots + val isUntyped = allAnnots.exists(_.isInstanceOf[Annot.Untyped]) + val annots = if isUntyped then allAnnots.filterNot(_.isInstanceOf[Annot.Untyped]) else allAnnots val k: Result => Block = if !isUntyped then k0 else r => @@ -924,7 +924,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case st.UnitVal() => k(unit) case st.Lit(lit) => if lit =/= Tree.UnitLit(false) then warnStmt - k(Value.Lit(lit)) + k(Value.Lit(lit)(trm.toLoc)) case st.Ret(res) => returnedTerm(res, loweringCtx.returnType) case st.Throw(res) => @@ -949,7 +949,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu Call( State.builtinOpsMap("===").asSimpleRef, (bodyResult.asSimpleRef.asArg :: State.runtimeSymbol.asSimpleRef.selSN("Continue").asArg :: Nil) ne_:: Nil, - )(CallMetadata.defaultMlsFun), + )(CallMetadata.defaultMlsFun, trm.toLoc), Match( isContinue.asSimpleRef, (Case.Lit(Tree.BoolLit(true)) -> Continue(label)) :: Nil, @@ -967,18 +967,18 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case st.Break(label, result, value) => value match case S(v) => term(v)(r => Assign(result, r, Break(label))) - case N => Assign(result, Value.Lit(Tree.UnitLit(false)), Break(label)) + case N => Assign(result, Value.Lit(Tree.UnitLit(false))(trm.toLoc), Break(label)) case st.Continue(label) => Continue(label) case st.Asc(lhs, rhs) => term(lhs, inStmtPos = inStmtPos)(k) case st.Tup(fs) => - args(fs, Nil)(args => k(Tuple(mut = false, args))) + args(fs, Nil)(args => k(Tuple(mut = false, args)(trm.toLoc))) case Mut(st.Tup(fs)) => - args(fs, Nil)(args => k(Tuple(mut = true, args))) + args(fs, Nil)(args => k(Tuple(mut = true, args)(trm.toLoc))) case st.CtxTup(fs) => // * This case is currently triggered for code such as `f(using 42)` - args(fs, Nil)(args => k(Tuple(mut = false, args))) + args(fs, Nil)(args => k(Tuple(mut = false, args)(trm.toLoc))) case t @ st.SimpleRef(sym) => ref(t, annots, N, inStmtPos = inStmtPos)(k) case t @ st.SelfRef(sym) => @@ -1001,7 +1001,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu else ref(t, annots, N, inStmtPos = inStmtPos)(k) case ref: UnresolvedRef => openSelection(ref)((prefix, member, target) => - setupNamedSelection(prefix, memberIdent(ref.id, member), target)(k)) + setupNamedSelection(prefix, memberIdent(ref.id, member), target, ref.toLoc)(k)) case Capture(base, thru) => term(base, inStmtPos = inStmtPos)(k) case t @ st.Ref(sym) => @@ -1024,7 +1024,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu subTerm(arg): ar => val target = wasmIntrinsicPath(sym, unary = true) .getOrElse(sym.asSimpleRef.withLocOf(ref)) - k(Call(target, (Arg(N, ar) :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun)) + k(Call(target, (Arg(N, ar) :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, trm.toLoc)) case st.Tup(Fld(FldFlags.benign(), arg1, N) :: Fld(FldFlags.benign(), arg2, N) :: Nil) => if !sym.binary then raise: ErrorReport( @@ -1037,7 +1037,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu if k.isInstanceOf[TailOp] then Match( ar1, (Case.Lit(posLit) -> term_nonTail(arg2)(k)) :: Nil, - S(k(Value.Lit(negLit))), + S(k(Value.Lit(negLit)(trm.toLoc))), Unreachable("tail operation in branches"), ) else // Well-typed short-circuit expressions have Boolean operands and results. @@ -1047,7 +1047,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu Match( ar1, (Case.Lit(posLit) -> term_nonTail(arg2)(Assign(ts, _, End()))) :: Nil, - S(Assign(ts, Value.Lit(negLit), End())), + S(Assign(ts, Value.Lit(negLit)(trm.toLoc), End())), k(ts.asSimpleRef), ) sym match @@ -1057,7 +1057,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu subTerm_nonTail(arg2): ar2 => val target = wasmIntrinsicPath(sym, unary = false) .getOrElse(sym.asSimpleRef.withLocOf(ref)) - k(Call(target, (Arg(N, ar1) :: Arg(N, ar2) :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun)) + k(Call(target, (Arg(N, ar1) :: Arg(N, ar2) :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, trm.toLoc)) case _ => fail: ErrorReport( msg"Unexpected arguments for builtin symbol '${sym.nme}'" -> arg.toLoc :: Nil, S(arg), @@ -1161,19 +1161,19 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu conclude(selectionPath(sel, p, name, target)) case ref: UnresolvedRef => openSelection(ref): (prefix, member, target) => subTerm_nonTail(prefix): p => - conclude(Select(p, memberIdent(ref.id, member))(target)(false).withLocOf(ref)) + conclude(Select(p, memberIdent(ref.id, member))(target, ref.toLoc)(false)) case sel @ Sel(prefix, nme) => subTerm(prefix): p => - conclude(Select(p, nme)(selSymbol(sel))(false).withLocOf(sel)) + conclude(Select(p, nme)(selSymbol(sel), trm.toLoc)(false).withLocOf(sel)) case Resolved(sel @ Sel(prefix, nme), sym) => subTerm(prefix): p => - conclude(Select(p, definitionIdent(nme, sym))(S(sym))(false).withLocOf(sel)) + conclude(Select(p, definitionIdent(nme, sym))(S(sym), trm.toLoc)(false).withLocOf(sel)) case sel @ SelProj(prefix, _, nme) => subTerm(prefix): p => - conclude(Select(p, nme)(N)(false).withLocOf(sel)) + conclude(Select(p, nme)(N, sel.toLoc)(false)) case Resolved(sel @ SelProj(prefix, _, nme), sym) => subTerm(prefix): p => - conclude(Select(p, definitionIdent(nme, sym))(S(sym))(false).withLocOf(sel)) + conclude(Select(p, definitionIdent(nme, sym))(S(sym), trm.toLoc)(false).withLocOf(sel)) case _ => subTerm(baseF)(conclude) case h @ Handle(lhs, rhs, as, cls, defs, bod) => if config.effectHandlers.isEmpty then @@ -1243,7 +1243,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case N if sel.tupleIndex.nonEmpty => subTerm_nonTail(prefix): p => subTerm_nonTail(rhs): r => - AssignDynField(p, Value.Lit(Tree.IntLit(BigInt(sel.tupleIndex.get))), true, r, k(unit)) + AssignDynField(p, Value.Lit(Tree.IntLit(BigInt(sel.tupleIndex.get)))(trm.toLoc), true, r, k(unit)) case S(sym: TermSymbol) => subTerm_nonTail(prefix): p => subTerm_nonTail(rhs): r => @@ -1300,7 +1300,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val affineAnnots = annots.collect: case a @ Annot.Affine(0) => a if k.isInstanceOf[TailOp] || bodyBlock.size <= 5 - then k(Lambda(paramLists.head, bodyBlock)(affineAnnots)) + then k(Lambda(paramLists.head, bodyBlock)(affineAnnots, trm.toLoc)) else val lamSym = new BlockMemberSymbol("lambda", Nil, false) loweringCtx.collectScopedSym(lamSym) @@ -1318,14 +1318,14 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case sel: NewSel => newSelection(sel): (prefix, name, target) => if sel.tupleIndex.nonEmpty then subTerm_nonTail(prefix)(p => k(selectionPath(sel, p, name, target))) - else setupNamedSelection(prefix, name, target)(k) + else setupNamedSelection(prefix, name, target, sel.toLoc)(k) case sel @ Sel(prefix, nme) => if sel.isErroneous then compError else - setupSelection(prefix, nme, selSymbol(sel))(k) + setupSelection(prefix, nme, selSymbol(sel), sel.toLoc)(k) case Resolved(sel @ Sel(prefix, nme), sym) => - setupSelection(prefix, nme, S(sym))(k) + setupSelection(prefix, nme, S(sym), sel.toLoc)(k) case sel @ SynthSel(prefix, nme) => // * Not using `setupSelection` as these selections are not meant to be sanity-checked @@ -1340,22 +1340,22 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case s: DefinitionSymbol[?] => S(s) case bms: BlockMemberSymbol => bms.asTrm // TODO: clean up logic case err: ErrorSymbol => N - )(false)) + , trm.toLoc)(false)) case Resolved(sel @ SynthSel(prefix, nme), sym) => // * Not using `setupSelection` as these selections are not meant to be sanity-checked subTerm(prefix): p => - k(Select(p, definitionIdent(nme, sym))(S(sym))(false)) + k(Select(p, definitionIdent(nme, sym))(S(sym), trm.toLoc)(false)) case DynSel(prefix, fld, ai, _) => subTerm(prefix): p => subTerm_nonTail(fld): f => - k(DynSelect(p, f, ai)) + k(DynSelect(p, f, ai)(trm.toLoc)) case DynNew(cls, args) => - subTerm(cls)(sr => lowerMultiInstantiate(false, sr, args, annots)(k)) + subTerm(cls)(sr => lowerMultiInstantiate(false, sr, args, annots, trm.toLoc)(k)) case Mut(DynNew(cls, args)) => - subTerm(cls)(sr => lowerMultiInstantiate(true, sr, args, annots)(k)) + subTerm(cls)(sr => lowerMultiInstantiate(true, sr, args, annots, trm.toLoc)(k)) case nw @ (_: New | Mut(_: New)) => val (mut, cls, as, rft, nwtrm) = nw match case nwtrm @ New(c, a, r) => (false, c, a, r, nwtrm) @@ -1363,7 +1363,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case _ => spuriousWarning classOf(cls, nwtrm): sr => rft match - case N => lowerMultiInstantiate(mut, sr, as, annots)(k) + case N => lowerMultiInstantiate(mut, sr, as, annots, trm.toLoc)(k) case S((isym, rft)) => val sym = new BlockMemberSymbol(isym.name, Nil) loweringCtx.collectScopedSym(sym) @@ -1386,23 +1386,23 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu // * InvalML-specific cases: t.Cls#field and mutable operations case sp @ SelProj(prefix, _, proj) => - setupSelection(prefix, proj, N)(k) + setupSelection(prefix, proj, N, sp.toLoc)(k) case Resolved(sp @ SelProj(prefix, _, proj), sym) => - setupSelection(prefix, proj, S(sym))(k) + setupSelection(prefix, proj, S(sym), sp.toLoc)(k) case Resolved(inner, sym) => TODO(s"lowering for Resolved($inner)") case Region(reg, body) => loweringCtx.collectScopedSym(reg) - Assign(reg, Instantiate(mut = true, Select(State.globalThisSymbol.asThis, Tree.Ident("Region"))(N)(false), Nil :: Nil)(InstantiateMetadata.empty), + Assign(reg, Instantiate(mut = true, Select(State.globalThisSymbol.asThis, Tree.Ident("Region"))(N, trm.toLoc)(false), Nil :: Nil)(InstantiateMetadata.empty, trm.toLoc), term_nonTail(body)(k)) case RegRef(reg, value) => plainArgs(reg :: value :: Nil): args => - k(Instantiate(mut = true, Select(State.globalThisSymbol.asThis, Tree.Ident("Ref"))(N)(false), args :: Nil)(InstantiateMetadata.empty)) + k(Instantiate(mut = true, Select(State.globalThisSymbol.asThis, Tree.Ident("Ref"))(N, trm.toLoc)(false), args :: Nil)(InstantiateMetadata.empty, trm.toLoc)) case Drop(ref) => subTerm(ref): _ => k(unit) case Deref(ref) => subTerm(ref): r => - k(Select(r, Tree.Ident("value"))(N)(false)) + k(Select(r, Tree.Ident("value"))(N, trm.toLoc)(false)) case SetRef(lhs, rhs) => subTerm(lhs): ref => subTerm_nonTail(rhs): value => @@ -1410,9 +1410,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case Mut(Rcd(mut, stats)) => // TODO: warn when in statement position // * Note: I don't think this is supposed to happen... - block(stats, L(mut -> Nil))(k) + block(stats, L((mut, Nil, trm.toLoc)))(k) case Rcd(mut, stats) => // TODO: warn when in statement position - block(stats, L(mut -> Nil))(k) + block(stats, L((mut, Nil, trm.toLoc)))(k) case Missing => fail: ErrorReport( @@ -1431,17 +1431,17 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu // subTerm(t)(k) def setupTerm(name: Str, args: Ls[Path])(k: Result => Block)(using LoweringCtx): Block = - k(Instantiate(mut = false, State.termSymbol.asSimpleRef.selSN(name), args.map(_.asArg) :: Nil)(InstantiateMetadata.empty)) + k(Instantiate(mut = false, State.termSymbol.asSimpleRef.selSN(name), args.map(_.asArg) :: Nil)(InstantiateMetadata.empty, N)) def setupQuotedKeyword(kw: Str): Path = State.termSymbol.asSimpleRef.selSN("Keyword").selSN(kw) def setupSymbol(symbol: ValueSymbol)(k: Result => Block)(using LoweringCtx): Block = k(Instantiate(mut = false, State.termSymbol.asSimpleRef.selSN("Symbol"), - (Value.Lit(Tree.StrLit(symbol.nme)).asArg :: Nil) :: Nil)(InstantiateMetadata.empty)) + (Value.Lit(Tree.StrLit(symbol.nme))(N).asArg :: Nil) :: Nil)(InstantiateMetadata.empty, N)) def quotePattern(p: FlatPattern)(k: Result => Block)(using LoweringCtx): Block = p match - case FlatPattern.Lit(lit) => setupTerm("LitPattern", Value.Lit(lit) :: Nil)(k) + case FlatPattern.Lit(lit) => setupTerm("LitPattern", Value.Lit(lit)(N) :: Nil)(k) case _ => // TODO fail: ErrorReport( @@ -1489,10 +1489,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu def quote(t: st)(k: Result => Block)(using LoweringCtx): Block = t match case Lit(lit) => - setupTerm("Lit", Value.Lit(lit) :: Nil)(k) + setupTerm("Lit", Value.Lit(lit)(N) :: Nil)(k) case Ref(sym) if Elaborator.binaryOps.contains(sym.nme) => // builtin symbols val l = loweringCtx.registerTempSymbol(N, erasedType = N) - setupTerm("Builtin", Value.Lit(Tree.StrLit(sym.nme)) :: Nil)(k) + setupTerm("Builtin", Value.Lit(Tree.StrLit(sym.nme))(N) :: Nil)(k) case Resolved(Ref(sym), disamb) => sym match case sym: BlockMemberSymbol => k(sym.asMemberRef(disamb)) @@ -1503,8 +1503,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case SynthSel(Ref(sym: ModuleOrObjectSymbol), name) => // Module/object cross-stage references setupSymbol(sym): r1 => val l1, l2 = loweringCtx.registerTempSymbol(N, erasedType = N) - Assign(l1, r1, setupTerm("CSRef", l1.asSimpleRef :: setupFilename :: Value.Lit(syntax.Tree.UnitLit(false)) :: Nil)(r2 => - Assign(l2, r2, setupTerm("Sel", l2.asSimpleRef :: Value.Lit(syntax.Tree.StrLit(name.name)) :: Nil)(k)) + Assign(l1, r1, setupTerm("CSRef", l1.asSimpleRef :: setupFilename :: Value.Lit(syntax.Tree.UnitLit(false))(N) :: Nil)(r2 => + Assign(l2, r2, setupTerm("Sel", l2.asSimpleRef :: Value.Lit(syntax.Tree.StrLit(name.name))(N) :: Nil)(k)) )) case SynthSel(Ref(sym: BlockMemberSymbol), name) => // Multi-file cross-stage references if config.qqEnabled then fail: @@ -1519,8 +1519,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val basePath = base.up val targetPath = filename val relPath = targetPath.relativeTo(basePath).map(_.toString).getOrElse(targetPath.toString) - Assign(l1, r1, setupTerm("CSRef", l1.asSimpleRef :: setupFilename :: Value.Lit(syntax.Tree.StrLit(relPath)) :: Nil)(r2 => - Assign(l2, r2, setupTerm("Sel", l2.asSimpleRef :: Value.Lit(syntax.Tree.StrLit(name.name)) :: Nil)(k)) + Assign(l1, r1, setupTerm("CSRef", l1.asSimpleRef :: setupFilename :: Value.Lit(syntax.Tree.StrLit(relPath))(N) :: Nil)(r2 => + Assign(l2, r2, setupTerm("Sel", l2.asSimpleRef :: Value.Lit(syntax.Tree.StrLit(name.name))(N) :: Nil)(k)) )) case _ => fail: ErrorReport( @@ -1535,7 +1535,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val arr = loweringCtx.registerTempSymbol(N, erasedType = S(ErasedType.Array), "arr") Assign( arr, - Tuple(mut = false, ds.reverse.map(_.asArg)), + Tuple(mut = false, ds.reverse.map(_.asArg))(N), Assign(l, r, setupTerm("Lam", arr.asSimpleRef :: l.asSimpleRef :: Nil)(k))) case sym :: rest => loweringCtx.collectScopedSym(sym) @@ -1550,7 +1550,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val arrSym = loweringCtx.registerTempSymbol(N, erasedType = S(ErasedType.Array), "arr") Assign( arrSym, - Tuple(mut = false, xs.reverse.map(_.asArg)), + Tuple(mut = false, xs.reverse.map(_.asArg))(N), setupTerm("Tup", arrSym.asSimpleRef :: Nil): r2 => val l1 = loweringCtx.registerTempSymbol(N, erasedType = N) val l2 = loweringCtx.registerTempSymbol(N, erasedType = N) @@ -1579,7 +1579,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu .chain(b => quote(res)(r3 => Assign(l3, r3, b))) .chain(b => setupTerm("LetDecl", l1.asSimpleRef :: Nil)(r4 => Assign(l4, r4, b))) .chain(b => setupTerm("DefineVar", l1.asSimpleRef :: l2.asSimpleRef :: Nil)(r5 => Assign(l5, r5, b))) - .assign(arrSym, Tuple(mut = false, (l4 :: l5 :: Nil).map(s => s.asSimpleRef.asArg))) + .assign(arrSym, Tuple(mut = false, (l4 :: l5 :: Nil).map(s => s.asSimpleRef.asArg))(N)) .rest(setupTerm("Blk", arrSym.asSimpleRef :: l3.asSimpleRef :: Nil)(k)) } case IfLike(_, IfLikeForm.ReturningIf, split) => quoteSplit(split.getExpandedSplit, Map.empty): r => @@ -1668,7 +1668,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu b, Assign( rcdSym, - Record(mut = false, fsr.reverse), + Record(mut = false, fsr.reverse)(N), k((Arg(N, rcdSym.asSimpleRef) :: asr).reverse))) @@ -1736,13 +1736,13 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu ) - def setupSelection(prefix: Term, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]])(k: Result => Block)(using LoweringCtx): Block = - setupNamedSelection(prefix, disamb.fold(memberIdent(nme, N))(definitionIdent(nme, _)), disamb)(k) + def setupSelection(prefix: Term, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]], loc: Opt[Loc])(k: Result => Block)(using LoweringCtx): Block = + setupNamedSelection(prefix, disamb.fold(memberIdent(nme, N))(definitionIdent(nme, _)), disamb, loc)(k) - private def setupNamedSelection(prefix: Term, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]]) + private def setupNamedSelection(prefix: Term, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]], loc: Opt[Loc]) (k: Result => Block)(using LoweringCtx): Block = subTerm(prefix): p => - k(Select(p, nme)(disamb)( + k(Select(p, nme)(disamb, loc)( !disamb.isDefined // * ^ We assume that resolved selections are well-behaved (will not yield undefined or debind a method) // || disamb.exists(_.defn.exists(_.hasDeclareModifier.isEmpty)) // * This checks `declare` members, which is normally unwanted @@ -1751,7 +1751,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu final def setupFunctionOrByNameDef(paramLists: List[ParamList], bodyTerm: Term, name: Option[Str], returnType: Opt[ErasedType]) (using LoweringCtx): (List[ParamList], Block) = val physicalParams = paramLists match - case Nil => ParamList(ParamListFlags.empty, Nil, N) :: Nil + case Nil => ParamList(ParamListFlags.empty, Nil, N)(N) :: Nil case ps => ps setupFunctionDef(physicalParams, bodyTerm, name, returnType) @@ -1777,22 +1777,22 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case S(value) => WarningReport(value -> annot.toLoc :: Nil) case N => WarningReport(msg"This annotation has no effect." -> annot.toLoc :: Nil) annotations.foreach: - case Annot.Untyped => () - case a @ (Annot.TailRec | Annot.Inline | Annot.NoInline | Annot.Generator | Annot.Async | Annot.RaiseEffects) => + case Annot.Untyped() => () + case a @ (Annot.TailRec() | Annot.Inline() | Annot.NoInline() | Annot.Generator() | Annot.Async() | Annot.RaiseEffects()) => val annot = a match - case Annot.TailRec => "@tailrec" - case Annot.Inline => "@inline" - case Annot.NoInline => "@noInline" - case Annot.Generator => "@generator" - case Annot.Async => "@async" - case Annot.RaiseEffects => "@raiseEffects" + case Annot.TailRec() => "@tailrec" + case Annot.Inline() => "@inline" + case Annot.NoInline() => "@noInline" + case Annot.Generator() => "@generator" + case Annot.Async() => "@async" + case Annot.RaiseEffects() => "@raiseEffects" target match case TermDefinition(body = S(bod), k = syntax.Fun) => () case TermDefinition(k = syntax.Fun) => warn(a, S(msg"Only functions with a body may be marked as $annot.")) case _ => warn(a) case Annot.Modifier(syntax.Keyword.`public` | syntax.Keyword.`private` | syntax.Keyword.`virtual`) => () case Annot.Modifier(syntax.Keyword("staged")) => () - case Annot.Pure => () + case Annot.Pure() => () case a: Annot.Affine => target match case TermDefinition(k = syntax.Fun) => () case _ => warn(a) @@ -1810,13 +1810,13 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu message -> receiver.toLoc :: Nil) annotations.foreach: - case Annot.Untyped => () + case Annot.Untyped() => () case annot: Annot.Trm => receiver match case st.App(Ref(_: BuiltinSymbol), _) => warn(annot) case st.App(_, _) | New(_, _, _) | DynNew(_, _) | Mut(_: New | _: DynNew) => () case st.Resolved(_, defnSym) if isImplicitNullaryCall(defnSym) => () case _ => warn(annot) - case a @ Annot.TailCall => receiver match + case a @ Annot.TailCall() => receiver match case st.App(Ref(_: BuiltinSymbol), _) => warn(a, S(msg"The @tailcall annotation has no effect on calls to built-in symbols.")) case st.App(_, _) => () case st.Resolved(_, defnSym) if isImplicitNullaryCall(defnSym) => () @@ -1833,14 +1833,14 @@ trait LoweringTraceLog(instrument: Bool)(using TL, Raise, State) private def selFromGlobalThis(path: Str*): Path = path.foldLeft[Path](State.globalThisSymbol.asThis): - (qual, name) => Select(qual, Tree.Ident(name))(N)(false) + (qual, name) => Select(qual, Tree.Ident(name))(N, N)(false) private def assignStmts(stmts: (Assignable, Result)*)(rest: Block) = stmts.foldRight(rest): case ((sym, res), acc) => Assign(sym, res, acc) private def pureCall(fn: Path, args: Ls[Arg]): Result = - Call(fn, args ne_:: Nil)(CallMetadata.defaultMlsFun) + Call(fn, args ne_:: Nil)(CallMetadata.defaultMlsFun, N) extension (k: Block => Block) def |>: (b: Block): Block = k(b) @@ -1888,10 +1888,10 @@ trait LoweringTraceLog(instrument: Bool)(using TL, Raise, State) val resInspectedSym = loweringCtx.registerTempSymbol(N, erasedType = N, dbgNme = "traceLogResInspected") - val psSymArgs = psInspectedSyms.zipWithIndex.foldRight[Ls[Arg]](Arg(N, Value.Lit(Tree.StrLit(")"))) :: Nil): + val psSymArgs = psInspectedSyms.zipWithIndex.foldRight[Ls[Arg]](Arg(N, Value.Lit(Tree.StrLit(")"))(N)) :: Nil): case (((s, p), i), acc) => if i == psInspectedSyms.length - 1 then Arg(N, s.asSimpleRef) :: acc - else Arg(N, s.asSimpleRef) :: Arg(N, Value.Lit(Tree.StrLit(", "))) :: acc + else Arg(N, s.asSimpleRef) :: Arg(N, Value.Lit(Tree.StrLit(", "))(N)) :: acc val tmp1, tmp2, tmp3 = loweringCtx.registerTempSymbol(N, erasedType = N) @@ -1901,7 +1901,7 @@ trait LoweringTraceLog(instrument: Bool)(using TL, Raise, State) assignStmts( enterMsgSym -> pureCall( strConcatFn, - Arg(N, Value.Lit(Tree.StrLit(s"CALL ${name.getOrElse("[arrow function]")}("))) :: psSymArgs + Arg(N, Value.Lit(Tree.StrLit(s"CALL ${name.getOrElse("[arrow function]")}("))(N)) :: psSymArgs ), tmp1 -> pureCall(traceLogFn, Arg(N, enterMsgSym.asSimpleRef) :: Nil), prevIndentLvlSym -> pureCall(traceLogIndentFn, Nil) @@ -1912,7 +1912,7 @@ trait LoweringTraceLog(instrument: Bool)(using TL, Raise, State) resInspectedSym -> pureCall(inspectFn, Arg(N, resSym.asSimpleRef) :: Nil), retMsgSym -> pureCall( strConcatFn, - Arg(N, Value.Lit(Tree.StrLit("=> "))) :: Arg(N, resInspectedSym.asSimpleRef) :: Nil + Arg(N, Value.Lit(Tree.StrLit("=> "))(N)) :: Arg(N, resInspectedSym.asSimpleRef) :: Nil ), tmp2 -> pureCall(traceLogResetFn, Arg(N, prevIndentLvlSym.asSimpleRef) :: Nil), tmp3 -> pureCall(traceLogFn, Arg(N, retMsgSym.asSimpleRef) :: Nil) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala index 32fbebb2f9..e266a55503 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala @@ -38,7 +38,7 @@ class ReflectionInstrumenter(using State, Raise, Ctx) extends BlockTransformer(n case b: Bool => Tree.BoolLit(b) case s: Str => Tree.StrLit(s) case n: BigDecimal => Tree.DecLit(n) - Value.Lit(l) + Value.Lit(l)(l.toLoc) extension [A, B](ls: Ls[(A => B) => B]) def collectApply(f: Ls[A] => B): B = @@ -58,11 +58,11 @@ class ReflectionInstrumenter(using State, Raise, Ctx) extends BlockTransformer(n Scoped(Set(sym), Assign(sym, res, k(sym.asSimpleRef))) def tuple(elems: Ls[ArgWrappable], symName: Str = "tmp")(k: Path => Block): Block = - assign(Tuple(false, elems.map(asArg)), symName)(k) + assign(Tuple(false, elems.map(asArg))(N), symName)(k) // isMlsFun is probably always true? def call(fun: Path, args: Ls[ArgWrappable], isMlsFun: Bool = true, symName: Str = "tmp")(k: Path => Block): Block = - assign(Call(fun, args.map(asArg) ne_:: Nil)(CallMetadata(isMlsFun, false, Nil)), symName)(k) + assign(Call(fun, args.map(asArg) ne_:: Nil)(CallMetadata(isMlsFun, false, Nil), N), symName)(k) // helpers for instrumenting Block @@ -217,14 +217,14 @@ class ReflectionInstrumenter(using State, Raise, Ctx) extends BlockTransformer(n raise(ErrorReport(msg"Instantiate with multiple argument lists not supported in staged module." -> r.toLoc :: Nil)) End() // desugar Runtime.Tuple.get into Select - case Call(fun, Ls(Arg(_, scrut), Arg(_, Value.Lit(Tree.IntLit(idx)))) :: _) if fun == Value.SimpleRef(State.runtimeSymbol).selSN("Tuple").selSN("get") => - transformPath(Select(scrut, Tree.Ident(idx.toString()))(N)(false))(k) + case Call(fun, Ls(Arg(_, scrut), Arg(_, Value.Lit(Tree.IntLit(idx)))) :: _) if fun == Value.SimpleRef(State.runtimeSymbol)(N).selSN("Tuple").selSN("get") => + transformPath(Select(scrut, Tree.Ident(idx.toString()))(N, r.toLoc)(false))(k) case Call(fun, argss) => val stagedFunPath = fun match case s @ Select(qual, Tree.Ident(name)) => s.symbol.flatMap({ case t: TermSymbol => t.owner.flatMap({ case sym: DefinitionSymbol[?] => sym.defn.flatMap(_.hasStagedModifier.map(_ => - Select(qual, Tree.Ident(name + "_gen"))(N)(false) + Select(qual, Tree.Ident(name + "_gen"))(N, N)(false) )) }) case _ => N @@ -265,7 +265,7 @@ class ReflectionInstrumenter(using State, Raise, Ctx) extends BlockTransformer(n transformOption(pOpt, transformParamList)(k) def transformCase(cse: Case)(using Context)(k: Path => Block): Block = cse match - case Case.Lit(lit) => blockCtor("Lit", Ls(Value.Lit(lit)))(k) + case Case.Lit(lit) => blockCtor("Lit", Ls(Value.Lit(lit)(N)))(k) case Case.Cls(cls, path) => transformSymbol(cls): cls => transformPath(path): path => @@ -308,7 +308,7 @@ class ReflectionInstrumenter(using State, Raise, Ctx) extends BlockTransformer(n // * owned field symbol is selected on its owner rather than emitted as a // * plain reference (which would otherwise reach `JSBuilder`'s owned-`SimpleRef` path). ((cont: Block) => AssignField(lhs, nme, xStaged, cont)(S(ts))): - given Context = ctx.clone() += Select(lhs, nme)(S(ts))(false) -> xStaged + given Context = ctx.clone() += Select(lhs, nme)(S(ts), N)(false) -> xStaged transformBlock(rest): (z, ctx) => blockCtor("Assign", Ls(xSym, y, z), "assign")(k(_, ctx)) case _ => @@ -384,7 +384,7 @@ class ReflectionInstrumenter(using State, Raise, Ctx) extends BlockTransformer(n // TODO: remove it. only for test val debug = (k: Block) => call(sym, Nil)(fnPrintCode(_)(k)) - val newFun = f.copy(sym = genSym, dSym = dSym, params = Ls(PlainParamList(Nil)), body = newBody)(f.configOverride, f.annotations) + val newFun = f.copy(sym = genSym, dSym = dSym, params = Ls(PlainParamList(Nil)(N)), body = newBody)(f.configOverride, f.annotations) (newFun, debug) override def applyBlock(b: Block): Block = super.applyBlock(b) match @@ -395,7 +395,7 @@ class ReflectionInstrumenter(using State, Raise, Ctx) extends BlockTransformer(n val (stagedMethods, debugPrintCode) = companion.methods .map(applyFunDefnInner) .unzip - val ctor = FunDefn.withFreshSymbol(S(companion.isym), BlockMemberSymbol("ctor$", Nil), Ls(PlainParamList(Nil)), companion.ctor)(N, Nil) + val ctor = FunDefn.withFreshSymbol(S(companion.isym), BlockMemberSymbol("ctor$", Nil), Ls(PlainParamList(Nil)(N)), companion.ctor)(N, Nil) val (stagedCtor, ctorPrint) = applyFunDefnInner(ctor) val debugBlock = (ctorPrint :: debugPrintCode) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/TailRecOpt.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/TailRecOpt.scala index 031a1b915e..9d08855a60 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/TailRecOpt.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/TailRecOpt.scala @@ -255,7 +255,7 @@ class TailRecOpt(checkAnnotations: Bool)(using Config, State, TL, Raise, Ctx): val rest = restArgs match case Arg(S(SpreadKind.Eager), value) :: Nil => value - case _ => Tuple(true, restArgs) + case _ => Tuple(true, restArgs)(N) CallArgsResult.Success(hd.appended(rest)) else CallArgsResult.Success(hd) @@ -279,11 +279,11 @@ class TailRecOpt(checkAnnotations: Bool)(using Config, State, TL, Raise, Ctx): val tupleSym = TempSymbol(N, erasedType = S(ErasedType.Array), "argList") - val tupleRes = Tuple(false, args) + val tupleRes = Tuple(false, args)(N) // Main args def mainArgs(rest: List[Path]) = (0 until paramList.size).toList.foldRight(rest): - case (n, acc) => DynSelect(tupleSym.asSimpleRef, Value.Lit(Tree.IntLit(n)), true) :: acc + case (n, acc) => DynSelect(tupleSym.asSimpleRef, Value.Lit(Tree.IntLit(n))(N), true)(N) :: acc // If the rest param exists, append a slice val (initialBlk: (Block => Block), pathList: List[Path]) = @@ -295,10 +295,10 @@ class TailRecOpt(checkAnnotations: Bool)(using Config, State, TL, Raise, Ctx): .sel(Tree.Ident("Tuple"), State.tupleSymbol) .sel(Tree.Ident("slice"), State.tupleSliceSymbol), (tupleSym.asSimpleRef.asArg - :: Value.Lit(Tree.IntLit(paramList.length)).asArg - :: Value.Lit(Tree.IntLit(0)).asArg + :: Value.Lit(Tree.IntLit(paramList.length))(N).asArg + :: Value.Lit(Tree.IntLit(0))(N).asArg :: Nil) ne_:: Nil - )(CallMetadata.defaultMlsFun) + )(CallMetadata.defaultMlsFun, N) val blk = blockBuilder .assignScoped(tupleSym, tupleRes) .assignScoped(sliceResSym, sliceRes) @@ -474,7 +474,7 @@ class TailRecOpt(checkAnnotations: Bool)(using Config, State, TL, Raise, Ctx): val curIdSym = VarSymbol(Tree.Ident("id"), erasedType = S(ErasedType.Int)) val loopDefnPath = owner match - case Some(value) => Select(value.asThis, Tree.Ident(bms.nme))(S(dSym))(false) + case Some(value) => Select(value.asThis, Tree.Ident(bms.nme))(S(dSym), N)(false) case None => bms.asMemberRef(dSym) def rewriteKnownCall(callee: FunDefn, flattenedArgs: List[Path]) = @@ -486,9 +486,9 @@ class TailRecOpt(checkAnnotations: Bool)(using Config, State, TL, Raise, Ctx): val args = paramSyms.map: s => argsMap.get(s) match case Some(pth) => Arg(N, pth) - case None => Arg(N, Value.Lit(Tree.UnitLit(false))) - val argsWithId = if funsLen > 1 then Value.Lit(Tree.IntLit(dSymIds(callee.dSym))).asArg :: args else args - Call(loopDefnPath, argsWithId ne_:: Nil)(CallMetadata.defaultMlsFun) + case None => Arg(N, Value.Lit(Tree.UnitLit(false))(N)) + val argsWithId = if funsLen > 1 then Value.Lit(Tree.IntLit(dSymIds(callee.dSym)))(N).asArg :: args else args + Call(loopDefnPath, argsWithId ne_:: Nil)(CallMetadata.defaultMlsFun, N) class FunRewriter(f: FunDefn) extends BlockTransformerShallow(SymbolSubst.Id): val params = f.allParamSyms @@ -557,7 +557,7 @@ class TailRecOpt(checkAnnotations: Bool)(using Config, State, TL, Raise, Ctx): Match( o.flag.asSimpleRef, Case.Lit(Tree.BoolLit(true)) -> - Assign(slot, Cast(ref, o.tpe, config.checkCasts), End()) :: Nil, + Assign(slot, Cast(ref, o.tpe, config.checkCasts)(N), End()) :: Nil, N, acc))) @@ -585,12 +585,12 @@ class TailRecOpt(checkAnnotations: Bool)(using Config, State, TL, Raise, Ctx): val cont = val resume = if funsLen === 1 then Continue(loopSym) - else Assign(curIdSym, Value.Lit(Tree.IntLit(dSymIds(calleeSym))), Continue(loopSym)) + else Assign(curIdSym, Value.Lit(Tree.IntLit(dSymIds(calleeSym)))(N), Continue(loopSym)) // Directly emitting the jump causes the return coercion to be dropped, so we record the cast and defer // its emission to the loop's exit. deferredCasts.get(f.dSym) match case S(o) if retCoercion.exists(t => !alwaysSucceedsAtExits(t)) => - Assign(o.flag, Value.Lit(Tree.BoolLit(true)), resume) + Assign(o.flag, Value.Lit(Tree.BoolLit(true))(N), resume) case _ => resume // In some cases, we could have assignments like this: // param0 = whatever @@ -741,7 +741,7 @@ class TailRecOpt(checkAnnotations: Bool)(using Config, State, TL, Raise, Ctx): else Scoped( deferredCasts.iterator.map(_._2.flag).toSet, deferredCasts.foldRight[Block](labelled): - case ((_, o), acc) => Assign(o.flag, Value.Lit(Tree.BoolLit(false)), acc)) + case ((_, o), acc) => Assign(o.flag, Value.Lit(Tree.BoolLit(false))(N), acc)) if !hasWrapper then val f = funs.head @@ -759,16 +759,16 @@ class TailRecOpt(checkAnnotations: Bool)(using Config, State, TL, Raise, Ctx): val newBod = Return( coerceToDeclaredReturn(rewriteKnownCall(f, paramArgs), f.dSym), ) - val annots = if f.inline then f.annotations else Annot.Inline :: f.annotations + val annots = if f.inline then f.annotations else Annot.Inline()(N) :: f.annotations FunDefn(f.owner, f.sym, f.dSym, f.params, newBod)(N, annots) val newParamLists = val initial = paramSyms.map(Param.simple(_)) if funsLen > 1 then - PlainParamList(Param.simple(curIdSym) :: initial) :: Nil + PlainParamList(Param.simple(curIdSym) :: initial)(N) :: Nil else - PlainParamList(initial) :: Nil + PlainParamList(initial)(N) :: Nil val annotations = - if funsLen == 1 && funs.head.inline then Annot.Inline :: Annot.Private :: Nil + if funsLen == 1 && funs.head.inline then Annot.Inline()(N) :: Annot.Private :: Nil else Annot.Private :: Nil val loopDefn = FunDefn( owner, bms, dSym, diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala index 0ee5b1028b..5eea405f22 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/WorkerWrapper.scala @@ -25,10 +25,10 @@ class WorkerWrapper import tl.* private def withInline(annotations: Ls[Annot]): Ls[Annot] = - if annotations.contains(Annot.Inline) then annotations else Annot.Inline :: annotations + if annotations.exists(_.isInstanceOf[Annot.Inline]) then annotations else Annot.Inline()(N) :: annotations private def withoutInline(annotations: Ls[Annot]): Ls[Annot] = - annotations.filterNot(_ == Annot.Inline) + annotations.filterNot(_.isInstanceOf[Annot.Inline]) private def isPlainParamList(params: ParamList): Bool = params.flags == ParamListFlags.empty && params.restParam.isEmpty @@ -53,7 +53,7 @@ class WorkerWrapper val mapping = collection.mutable.LinkedHashMap.empty[Symbol, Symbol] val flatParams = params.flatMap: params => params.params.map(freshParam(_, mapping)) - PlainParamList(flatParams) -> mapping.toMap + PlainParamList(flatParams)(Loc(params)) -> mapping.toMap private def rewriteParams(body: Block, mapping: Map[Symbol, Symbol]): Block = val subst = new SymbolSubst: @@ -76,7 +76,7 @@ class WorkerWrapper val workerArgs = fun.params.flatMap(_.params).map: param => Arg(N, param.sym.asSimpleRef) val wrapperBody = Return( - Call(worker.asPath, workerArgs ne_:: Nil)(CallMetadata.mlsFunWithEffect), + Call(worker.asPath, workerArgs ne_:: Nil)(CallMetadata.mlsFunWithEffect, N), ) val wrapper = FunDefn( fun.owner, @@ -85,7 +85,7 @@ class WorkerWrapper fun.params, wrapperBody, )(fun.configOverride, withInline(fun.annotations.filter: - case Annot.NoInline | Annot.Generator => false + case Annot.NoInline() | Annot.Generator() => false case _ => true )) log(s"▶ Worker-wrapper: ${fun.dSym.showDbg} -> ${worker.dSym.showDbg}") diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala index 12bcfab9af..32d149f608 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala @@ -32,7 +32,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise) case l: LabelSymbol => l -> instId case scrutId: ResultId => ConcreteId(scrutId, instId) extension (vs: Ls[VarSymbol]) def asParamList: ParamList = - ParamList(ParamListFlags.empty, vs.map(Param.simple), N) + ParamList(ParamListFlags.empty, vs.map(Param.simple), N)(N) extension (c: CtorCls) def ctorClsName: String = c match case cls: ClassLikeSymbol => cls.nme case n: Int => s"tup$n" @@ -72,8 +72,8 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise) .asInstanceOf[Iterator[Label | Match]] parents -> blockUntilParent - private val PrivateModifier = Annot.Modifier(syntax.Keyword.`private`) - private val AffineAnnotForBranchFns = Annot.Affine(1) + private val PrivateModifier = Annot.Modifier(syntax.Keyword.`private`)(N) + private val AffineAnnotForBranchFns = Annot.Affine(1)(N) val branchSelSyms = MutMap.empty[CtorDtorId, VarSymbol] // branch fun params for fields (which share the same symbol in `branchSelSyms`) @@ -316,9 +316,9 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise) private def mkCall(target: (BlockMemberSymbol, TermSymbol), args: Ls[ValueSymbol]): Result = Call( - Value.MemberRef(target._1, target._2), + Value.MemberRef(target._1, target._2)(N), args.map(a => Arg(N, a.asPath)) ne_:: Nil - )(CallMetadata.defaultMlsFun) + )(CallMetadata.defaultMlsFun, N) // Rewrites the program under a specific instantiation id // from the polymorphic analysis @@ -397,7 +397,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise) Call( newScrut, callWithFvs.map(s => Arg(N, s.asPath)) ne_:: Nil - )(CallMetadata.defaultMlsFun)) + )(CallMetadata.defaultMlsFun, N)) case Break(label) => val labelRestFunId = label.withInstId(instId) restFunSyms.get(labelRestFunId) match @@ -438,7 +438,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise) case Value.MemberRef(bms, disamb) if existingMapping.contains(bms) => val sym = existingMapping(bms) sym match - case s: VarSymbol => k(Value.SimpleRef(s)) + case s: VarSymbol => k(Value.SimpleRef(s)(v.toLoc)) case _ => super.applyValue(v)(k) case _ => super.applyValue(v)(k) end RefreshSymbol @@ -488,7 +488,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise) val newSym = new VarSymbol(Tree.Ident(p.sym.name), erasedType = p.sym.erasedType) refreshParamMap(p.sym) = newSym Param(p.flags, newSym, p.sign, p.modulefulness)(p.toLoc), - pl.restParam) + pl.restParam)(pl.toLoc) val bodyWithCorrectSymbols = refreshExtractedBody(refreshParamMap.toMap, rewrittenBody) FunDefn( N, bms, tSym, refreshedParams, @@ -531,7 +531,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise) transformedOgBody, Return(mkCall(parentFunSym, parentFunFvs))) case None => - Begin(transformedOgBody, Return(Value.Lit(Tree.UnitLit(true)))) + Begin(transformedOgBody, Return(Value.Lit(Tree.UnitLit(true))(N))) val refreshedFvSymbols = restFnFvs(restFunId).map(s => s -> new VarSymbol(Tree.Ident(s"fv_${s.nme}"), erasedType = s.mapErasedValueType)) val bodyWithCorrectSymbols = refreshExtractedBody(refreshedFvSymbols.toMap, actualBody) FunDefn(N, bms, tsym, refreshedFvSymbols.unzip._2.asParamList :: Nil, bodyWithCorrectSymbols)(N, annotations = PrivateModifier :: Nil) 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 1a1a8ac695..d3255f343f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -84,7 +84,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case Operand(prec: Int) def mkErr(errMsg: Message)(using Raise, Scope): Document = - doc"throw globalThis.Error(${result(Value.Lit(syntax.Tree.StrLit(errMsg.show)))})" + doc"throw globalThis.Error(${result(Value.Lit(syntax.Tree.StrLit(errMsg.show))(N))})" def errExpr(errMsg: Message)(using Raise, Scope): Document = raise(ErrorReport(errMsg -> N :: Nil, @@ -313,9 +313,9 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: paramLists match case Nil => body case params :: Nil => - Return(Lambda(params, body)(if generator then Annot.Generator :: Nil else Nil)) + Return(Lambda(params, body)(if generator then Annot.Generator()(N) :: Nil else Nil, N)) case params :: rest => - Return(Lambda(params, curriedFunctionBody(rest, body, generator))(Nil)) + Return(Lambda(params, curriedFunctionBody(rest, body, generator))(Nil, N)) /** Looks through the casts that a JS program does not materialize. */ @tailrec @@ -394,7 +394,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: else doc"$runtimeVar.safeCall(${calls})" case lam @ Lambda(ps, bod) => scope.nest givenIn: val (params, bodyDoc) = setupFunction(none, ps, bod, isLambda = true) - if lam.annot.contains(Annot.Generator) + if lam.annot.exists(_.isInstanceOf[Annot.Generator]) then // JavaScript has no generator arrows, so bind `this` to preserve the // lexical-`this` behavior of the Lambda IR. @@ -873,7 +873,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case SpecializedSwitch(scrut, cases, dflt, rest) => val switchBod = cases.foldLeft(doc""): (acc, arm) => val needsBreak = arm.isInstanceOf[SwitchCase.ExplicitBreak] - acc :: doc" # case ${result(Value.Lit(arm.litValue))}: #{ ${ + acc :: doc" # case ${result(Value.Lit(arm.litValue)(arm.litValue.toLoc))}: #{ ${ // * Note: we use `block` here so that Scoped nodes will create proper brace sections, // * necessary since `case` clauses do not create a new scope, // * so something like `switch (x) { case 1: let y = 1; break; case 2: let y = 2 }` is ill-formed! diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala index c6e2ca397b..4ce5d1aa69 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala @@ -472,7 +472,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui ctorSym = N, k = syntax.Obj, paramsOpt = N, - auxParams = PlainParamList(Nil) :: Nil, + auxParams = PlainParamList(Nil)(N) :: Nil, parentPath = N, methods = Nil, privateFields = Nil, @@ -2235,7 +2235,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui val result = pss.foldRight(bod): case (ps, block) => - Return(Lambda(ps, block)(Nil)) + Return(Lambda(ps, block)(Nil, N)) // Nested functions are not predeclared in `program` - declare them now. // Note that predeclaring functions twice causes an orphaned type to be duplicated in the module. if ctx.getFunc(sym).isEmpty then predeclareTopLevelFun(sym, ps) @@ -2380,7 +2380,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui clsLikeDefn.methods.foreach: case FunDefn(_, sym, _, Nil, bod) => - overwriteMethod(sym, PlainParamList(Nil), bod) + overwriteMethod(sym, PlainParamList(Nil)(N), bod) case FunDefn(_, sym, _, ps :: Nil, bod) => overwriteMethod(sym, ps, bod) case methodDefn => diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala index bc911a893b..1b81a6a048 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala @@ -613,7 +613,7 @@ class InvalTyper(using codegen.Erasure)(using elState: Elaborator.State, tl: TL) trace[(GeneralType, Type)](s"${ctx.lvl}. Typing ${t.showDbg}", res => s": (${res._1.showDbg}, ${res._2.showDbg})"): given CCtx = CCtx.init(t, N) t match - case Term.Annotated(Annot.Untyped, _) => (Bot, Bot) + case Term.Annotated(Annot.Untyped(), _) => (Bot, Bot) case sel @ Term.SynthSel(Ref(_: TopLevelSymbol), nme) if sel.symbol.isDefined => typeCheck(Ref(sel.symbol.get)(sel.nme, N)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 753e6d73c5..3ffe73e0bd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -747,8 +747,8 @@ extends Importer: msg"Member names must start with a letter or underscore, followed by letters, digits, or underscores." -> N :: Nil - def mkLetBinding(kw: Tree.Keywrd[?], sym: LocalVarSymbol | TermSymbol, rhs: Term, annotations: Ls[Annot]): Ls[Statement] = - LetDecl(sym, annotations).mkLocWith(kw, sym) :: defineVar(sym, rhs) :: Nil + def mkLetBinding(sym: LocalVarSymbol | TermSymbol, rhs: Term, annotations: Ls[Annot])(loc: Opt[Loc]): Ls[Statement] = + LetDecl(sym, annotations)(loc) :: defineVar(sym, rhs)(loc) :: Nil // TODO: remove in favor of new resolution logic def resolveField(srcTree: Tree, base: Opt[Symbol], nme: Ident): Opt[MemberSymbol] = @@ -775,12 +775,12 @@ extends Importer: | Keyword.`virtual` | Keyword.`public` | Keyword.`private` - )) => S(Annot.Modifier(kw)) + )) => S(Annot.Modifier(kw)(tree.toLoc)) case App(Ident("config"), Tup(args)) => val modify = ConfigParser.parseOverrides(args) - S(Annot.Config(modify)) + S(Annot.Config(modify)(tree.toLoc)) case App(Ident("affine"), Tup(IntLit(whichParamList) :: Nil)) => - S(Annot.Affine(whichParamList.toInt).withLocOf(tree)) + S(Annot.Affine(whichParamList.toInt)(tree.toLoc)) case _ => term(tree, Trm) match case Term.Error() => N case trm => @@ -790,24 +790,24 @@ extends Importer: case S(sym) => sym match case ctx.builtins.annotations.untyped => - return S(Annot.Untyped) + return S(Annot.Untyped()(tree.toLoc)) case ctx.builtins.annotations.tailcall => - return S(Annot.TailCall) + return S(Annot.TailCall()(tree.toLoc)) case ctx.builtins.annotations.tailrec => - return S(Annot.TailRec) + return S(Annot.TailRec()(tree.toLoc)) case ctx.builtins.annotations.inline => - return S(Annot.Inline) + return S(Annot.Inline()(tree.toLoc)) case ctx.builtins.annotations.noInline => - return S(Annot.NoInline) + return S(Annot.NoInline()(tree.toLoc)) case ctx.builtins.annotations.generator => - return S(Annot.Generator) + return S(Annot.Generator()(tree.toLoc)) case ctx.builtins.annotations.async => - return S(Annot.Async) + return S(Annot.Async()(tree.toLoc)) case ctx.builtins.annotations.pure => - return S(Annot.Pure) + return S(Annot.Pure()(tree.toLoc)) case _ => () case _ => () - S(Annot.Trm(trm)) + S(Annot.Trm(trm)(tree.toLoc)) private final case class EffectHandlerMethodSpec( methodName: Str, @@ -846,7 +846,7 @@ extends Importer: Fun, mtdSym, tsym, - PlainParamList(valueSym.fold(Nil)(sym => Param.simple(sym) :: Nil)) :: Nil, + PlainParamList(valueSym.fold(Nil)(sym => Param.simple(sym) :: Nil))(N) :: Nil, N, N, S(spec.methodBody(valueSym)), @@ -1195,7 +1195,7 @@ extends Importer: Param.simple(sym) .toList if params.isEmpty then st - else Term.Lam(PlainParamList(params), st) + else Term.Lam(PlainParamList(params)(N), st) private def app(lt: Term, rt: Term)(tree: Tree.App, typ: Opt[typing.Type], resSym: FlowSymbol): Term = val res = new Term.App(lt, rt)(tree, typ, resSym) @@ -1324,7 +1324,7 @@ extends Importer: val lt = subterm(lhs) val sym = TempSymbol(S(lt), erasedType = N, "old") Blk( - LetDecl(sym, Nil) :: defineVar(sym, lt) :: Nil, Term.Try(Blk( + LetDecl(sym, Nil)(tree.toLoc) :: defineVar(sym, lt)(tree.toLoc) :: Nil, Term.Try(Blk( assignment(lt, subterm(rhs)) :: Nil, subterm(bod, interp), ), assignment(lt, sym.ref()))) @@ -1621,7 +1621,7 @@ extends Importer: Param.simple(self) :: Nil, S: Param.simple(args) - ) + )(N) val rs = FlowSymbol.app() Term.Lam(ps, app( @@ -1695,7 +1695,7 @@ extends Importer: val scrut = VarSymbol(Ident("caseScrut"), erasedType = N) val body = caseSplit(scrut, tree) val params = Param.simple(scrut) :: Nil - Term.Lam(PlainParamList(params), body).mkLocWith(kw) + Term.Lam(PlainParamList(params)(N), body).mkLocWith(kw) case PrefixApp(kw @ Keywrd(Keyword.`return`), body) => ctx.getRetHandler match case ReturnHandler.Required(sym) => @@ -2066,7 +2066,7 @@ extends Importer: case pathArg => S((pathArg, N)) val (newCtx, newAcc) = pathAndAlias match case S((StrLit(path), alias)) => - val stmt = importPath(path, alias).withLocOf(m) + val stmt = importPath(path, alias)(m.toLoc) (ctx + (stmt.sym.nme -> stmt.sym), stmt :: acc) case S((pathArg, _)) => @@ -2101,8 +2101,8 @@ extends Importer: val sym = new VarSymbol(id, erasedType = N) newCtx += id.name -> sym RcdField(Term.Lit(StrLit(id.name)).withLocOf(id), sym.ref(id)) - :: defineVar(sym, rhs_t) - :: LetDecl(sym, annotations) + :: defineVar(sym, rhs_t)(Loc(rlhs, rhs)) + :: LetDecl(sym, annotations)(id.toLoc) :: acc case lit: Literal => reportUnusedAnnotations @@ -2129,17 +2129,17 @@ extends Importer: case S(rhs) => val rrhs = tups.foldRight(rhs): InfixApp(_, Keywrd(Keyword.`=>`), _) - mkLetBinding(kw, sym, term(rrhs, Trm), annotations) reverse_::: acc + mkLetBinding(sym, term(rrhs, Trm), annotations)(hd.toLoc) reverse_::: acc case N => if tups.nonEmpty then raise(ErrorReport(msg"Expected a right-hand side for let bindings with parameters" -> hd.toLoc :: Nil)) - LetDecl(sym, annotations).mkLocWith(kw) :: acc + LetDecl(sym, annotations)(hd.toLoc) :: acc (ctx + (id.name -> sym)) givenIn: go(sts, Nil, newAcc) case (tree @ LetLike(Keywrd(`let`), lhs, _, N)) :: sts => raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) go(sts, Nil, Term.Error().withLocOf(tree) :: acc) - case Def(lhs, rhs) :: sts => + case (definition @ Def(lhs, rhs)) :: sts => reportUnusedAnnotations lhs match case id: Ident => @@ -2147,7 +2147,7 @@ extends Importer: ctx.get(id.name) match case S(elem) => elem.symbol match - case S(sym: (LocalSymbol | TermSymbol)) => go(sts, Nil, defineVar(sym, r) :: acc) + case S(sym: (LocalSymbol | TermSymbol)) => go(sts, Nil, defineVar(sym, r)(definition.toLoc) :: acc) case S(sym) => raise(ErrorReport(msg"Symbol '${id.name}' is not a variable and cannot be reassigned" -> id.toLoc :: Nil)) go(sts, Nil, Term.Error().withLocOf(id) :: acc) @@ -2224,8 +2224,8 @@ extends Importer: case _ if ctx.mode is Mode.Light => S(Term.Missing) case S(rhs) => S: val nonLocalRetHandler = TempSymbol(N, erasedType = N, s"nonLocalRetHandler$$${id.name}") - val hasGeneratorAnnotation = annotations.contains(Annot.Generator) - val hasAsyncAnnotation = annotations.contains(Annot.Async) + val hasGeneratorAnnotation = annotations.exists(_.isInstanceOf[Annot.Generator]) + val hasAsyncAnnotation = annotations.exists(_.isInstanceOf[Annot.Async]) if pss.isEmpty && hasGeneratorAnnotation then raise(ErrorReport(msg"Generators are not supported on functions without a parameter list" -> td.toLoc :: Nil)) newCtx.nest(OuterCtx.Function(nonLocalRetHandler, S(tsym))(pss.nonEmpty && hasGeneratorAnnotation, hasAsyncAnnotation)).givenIn: newCtx ?=> @@ -2335,7 +2335,7 @@ extends Importer: res.restParam.foreach: rp => raise(ErrorReport( msg"Spread parameters are not supported in class parameters." -> rp.toLoc :: Nil)) - res.copy(restParam = N) + res.copy(restParam = N)(res.toLoc) def withFields(extraParams: Ls[ParamList])(using Ctx)(fn: (Ctx) ?=> (Term.Blk, Ctx)): (Term.Blk, Ctx) = softAssert(pss.sizeCompare(td.clsParams) === 0, @@ -2387,8 +2387,8 @@ extends Importer: else val psym = TermSymbol(LetBind, owner, p.sym.id) psym.sourceAliases = p.sym.sourceAliases - val decl = LetDecl(psym, Nil) // TODO: never use term symbols on LetDecl LHS - val defn = defineVar(psym, p.sym.ref()) + val decl = LetDecl(psym, Nil)(p.toLoc) // TODO: never use term symbols on LetDecl LHS + val defn = defineVar(psym, p.sym.ref())(p.toLoc) p.fldSym = S(psym) decl :: defn :: Nil @@ -2400,8 +2400,8 @@ extends Importer: case _: TypeAliasSymbol => die val psym = TermSymbol(LetBind, owner, p.sym.id) psym.sourceAliases = p.sym.sourceAliases - val decl = LetDecl(psym, Nil) - val defn = defineVar(psym, p.sym.ref()) + val decl = LetDecl(psym, Nil)(p.toLoc) + val defn = defineVar(psym, p.sym.ref())(p.toLoc) p.fldSym = S(psym) decl :: defn :: Nil @@ -2543,7 +2543,7 @@ extends Importer: res.restParam.foreach: rp => raise(ErrorReport( msg"Spread parameters are not supported in class parameters." -> rp.toLoc :: Nil)) - res.copy(restParam = N) + res.copy(restParam = N)(res.toLoc) newCtx.givenIn: trace(s"Processing class definition $nme"): val comp = sym.asMod @@ -2585,14 +2585,14 @@ extends Importer: // * Reconstruct as `Directive(config, Tup(args))` and re-process. case App(Directive(prefix, _), args) :: sts => go(Directive(prefix, args) :: sts, annotations, acc) - case Directive(Ident("config"), Tup(args)) :: sts => + case (directive @ Directive(Ident("config"), Tup(args))) :: sts => reportUnusedAnnotations val modify = ConfigParser.parseOverrides(args) - go(sts, Nil, SetConfig(modify) :: acc) - case Directive(Ident("lang"), Tup(args)) :: sts => + go(sts, Nil, SetConfig(modify)(directive.toLoc) :: acc) + case (directive @ Directive(Ident("lang"), Tup(args))) :: sts => reportUnusedAnnotations val modify = ConfigParser.parseLanguageDirective(args) - go(sts, Nil, SetConfig(modify) :: acc) + go(sts, Nil, SetConfig(modify)(directive.toLoc) :: acc) case Directive(Ident(name), _) :: sts => raise(ErrorReport( msg"Unknown directive '#${name}'" -> sts.headOption.flatMap(_.toLoc) :: Nil, @@ -2728,7 +2728,7 @@ extends Importer: case Tup(ps) => def go(ps: Ls[Tree], acc: Ls[Param], ctx: Ctx, flags: ParamListFlags): (ParamList, Ctx) = ps match - case Nil => (ParamList(flags, acc.reverse, N).withLocOf(t), ctx) + case Nil => (ParamList(flags, acc.reverse, N)(t.toLoc), ctx) case hd :: tl => val isCtxParam = hd.isModified(Ins) val inUsing = flags.ctx || isCtxParam @@ -2744,7 +2744,7 @@ extends Importer: if spd is SpreadKind.Lazy then raise(ErrorReport(msg"Lazy spread parameters not allowed." -> hd.toLoc :: Nil)) if tl.isEmpty then - (ParamList(flags, acc.reverse, S(p)).withLocOf(t), newCtx) + (ParamList(flags, acc.reverse, S(p))(t.toLoc), newCtx) else raise(ErrorReport(msg"Spread parameters must be the last in the parameter list." -> hd.toLoc :: Nil)) go(tl, p :: acc, newCtx, newFlags) @@ -2755,7 +2755,7 @@ extends Importer: raise: ErrorReport: msg"Expected a parameter list (a tuple of parameters), but found ${t.describe}" -> t.toLoc :: Nil - (ParamList(ParamListFlags.empty, Nil, N).withLocOf(t), ctx) + (ParamList(ParamListFlags.empty, Nil, N)(t.toLoc), ctx) def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = ctx.get(id.name) match case candidate @ (S(_: Ctx.WildcardElem) | N) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala index 5d00318b10..bacb192593 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala @@ -19,7 +19,7 @@ class Importer extends NewResolver: import tl.* - def importPath(path: Str, alias: Opt[syntax.Tree.Ident])(using cfg: Config, rs: NewResolverState): Import = + def importPath(path: Str, alias: Opt[syntax.Tree.Ident])(loc: Opt[Loc])(using cfg: Config, rs: NewResolverState): Import = // log(s"pwd: ${os.pwd}") // log(s"wd: ${wd}") @@ -43,7 +43,7 @@ class Importer extends NewResolver: case "mjs" | "js" => if newResolution then sym.currentShapes.add(DynShape()) - Import(sym, file.toString, file) + Import(sym, file.toString, file)(loc) case "mls" => def reportCycle(files: Ls[io.Path]): Import = @@ -51,7 +51,7 @@ class Importer extends NewResolver: ErrorReport: msg"Circular imports of `mls` files are not yet supported" -> N :: files.map(f => msg" importing ${f.toString}" -> N) - Import(sym, path, file) + Import(sym, path, file)(loc) if cctx.beingCompiled.contains(file) then reportCycle(cctx.allFilesBeingImported :+ file) @@ -73,14 +73,14 @@ class Importer extends NewResolver: case _: BlockMemberSymbol => () val jsFile = file.up / io.RelPath(file.baseName + ".mjs") - Import(sym, jsFile.toString, jsFile) + Import(sym, jsFile.toString, jsFile)(loc) case _ => if file.ext =/= "mls" then raise: ErrorReport(msg"Unsupported file extension: ${file.ext}" -> N :: Nil) - Import(sym, path, file) + Import(sym, path, file)(loc) else if newResolution then sym.currentShapes.add(DynShape()) - Import(sym, path, file) + Import(sym, path, file)(loc) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index 521f227814..34e502c883 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -10,7 +10,7 @@ import NewResolverState.Listener object InterfaceExposure: def isPublic(definition: Definition): Bool = !definition.annotations.contains(Annot.Private) && (definition match - case td: TermDefinition => (td.k isnt syntax.LetBind) || td.annotations.contains(Annot.Modifier(syntax.Keyword.`public`)) + case td: TermDefinition => (td.k isnt syntax.LetBind) || td.annotations.contains(Annot.Modifier(syntax.Keyword.`public`)(N)) case _: TypeDef => false case _ => true) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 871a5d897b..23d42ca2a2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2460,7 +2460,7 @@ class NewResolver: resolError(nw, (msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.diagnosticLocation) :: shape.diagnosticNotes) ) - def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term)(using NewResolverState): DefineVar = + def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term)(loc: Opt[Loc])(using NewResolverState): DefineVar = if newResolution then sym match case sym: TermSymbol => // symShape(sym, rhs) @@ -2470,7 +2470,7 @@ class NewResolver: case sym: LocalSymbol => listen(rhs): sh => publishActivated(sym, sh) - DefineVar(sym, rhs) + DefineVar(sym, rhs)(loc) def listenDefn(sym: TermSymbol, listener: Listener)(using NewResolverState): Unit = sym.defn match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 4e3128a226..2ce084bf97 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -20,22 +20,27 @@ import hkmc2.codegen.Erasure final case class QuantVar(sym: VarSymbol, ub: Opt[Term], lb: Opt[Term]) -enum Annot extends AutoLocated: - case Untyped - case Modifier(mod: Keyword) - case Trm(trm: Term) +/** Each annotation occurrence owns its source span, including marker annotations. + * The auxiliary location does not affect equality or matching by annotation kind. + */ +enum Annot extends Located: + val toLoc: Opt[Loc] + + case Untyped()(val toLoc: Opt[Loc]) + case Modifier(mod: Keyword)(val toLoc: Opt[Loc]) + case Trm(trm: Term)(val toLoc: Opt[Loc]) // NOTE: The presence of TailRec and TailCall annotations does not affect whether a function is optimized or not; // it only affects whether a warning is thrown if the function/call is not actually tail-recursive. - case TailRec - case TailCall - case Inline - case NoInline - case Generator - case Async - case RaiseEffects + case TailRec()(val toLoc: Opt[Loc]) + case TailCall()(val toLoc: Opt[Loc]) + case Inline()(val toLoc: Opt[Loc]) + case NoInline()(val toLoc: Opt[Loc]) + case Generator()(val toLoc: Opt[Loc]) + case Async()(val toLoc: Opt[Loc]) + case RaiseEffects()(val toLoc: Opt[Loc]) // Whether the function is guaranteed to not raise effects. - case Pure - case Config(modify: hkmc2.Config => hkmc2.Config) + case Pure()(val toLoc: Opt[Loc]) + case Config(modify: hkmc2.Config => hkmc2.Config)(val toLoc: Opt[Loc]) // Marks if a function or lambda is one-shot, i.e. called at most once. // Functions with multiple parameter lists are considered here as a chain of // function values. `whichParamList` is the zero-based index of the parameter @@ -46,7 +51,7 @@ enum Annot extends AutoLocated: // each function value produced by `f(a)` is one-shot; // - its list of annotations containing both `Affine(0)` and `Affine(1)` says that // `f` is one-shot and each function value produced by `f(a)` is also one-shot. - case Affine(whichParamList: Int) + case Affine(whichParamList: Int)(val toLoc: Opt[Loc]) def symbol: Opt[Symbol] = this match case Trm(trm) => trm.symbol @@ -54,48 +59,31 @@ enum Annot extends AutoLocated: def subTerms: Vector[Term] = this match case Trm(trm) => Vector.single(trm) - case _: Modifier | Untyped | TailRec | TailCall | Inline | NoInline - | Generator | Async | RaiseEffects | Pure | _: Config | _: Affine => Vector.empty - - def children: Vector[Located] = this match - case Trm(trm) => Vector.single(trm) - // case Modifier(kw) => Vector.single(kw) // TODO: make `kw` a `Keywrd` - case _: Modifier | Untyped | TailRec | TailCall | Inline | NoInline - | Generator | Async | RaiseEffects | Pure | _: Config | _: Affine => Vector.empty + case _: Modifier | Untyped() | TailRec() | TailCall() | Inline() | NoInline() + | Generator() | Async() | RaiseEffects() | Pure() | _: Config | _: Affine => Vector.empty def show(using Scope, ShowCfg, Raise): Document = this match - case Untyped => doc"@untyped" - case Inline => doc"@inline" - case NoInline => doc"@noInline" - case Generator => doc"@generator" - case Async => doc"@async" - case RaiseEffects => doc"@raiseEffects" - case TailRec => doc"@tailrec" - case TailCall => doc"@tailcall" + case Untyped() => doc"@untyped" + case Inline() => doc"@inline" + case NoInline() => doc"@noInline" + case Generator() => doc"@generator" + case Async() => doc"@async" + case RaiseEffects() => doc"@raiseEffects" + case TailRec() => doc"@tailrec" + case TailCall() => doc"@tailcall" case Affine(n) => doc"@affine($n)" case Modifier(mod) => doc"@${mod.name}" - case Pure => doc"@pure" + case Pure() => doc"@pure" case Trm(trm) => doc"@${trm.show}" case Config(_) => doc"@config(...)" def mkClone(using State, Erasure): Annot = this match - case Untyped => Untyped - case Modifier(mod) => Modifier(mod) - case Trm(trm) => Trm(trm.mkClone) - case TailRec => TailRec - case TailCall => TailCall - case Inline => Inline - case NoInline => NoInline - case Generator => Generator - case Async => Async - case RaiseEffects => RaiseEffects - case Pure => Pure - case c: Config => c - case a: Affine => a + case Trm(trm) => Trm(trm.mkClone)(toLoc) + case _ => this object Annot: - val Private = Modifier(Keyword.`private`) + val Private = Modifier(Keyword.`private`)(N) /** The `declare` modifier in `annotations`, if present. */ def declareModifierOf(annotations: Ls[Annot]): Opt[Annot.Modifier] = annotations.collectFirst: @@ -827,11 +815,11 @@ sealed trait Statement extends Located, ProductWithExtraInfo, Describable: def mkClone(using State, Erasure): Statement = this match case t: Term => lastWords(s"overridden implementation") case d: Definition => ??? - case imp: Import => Import(imp.sym, imp.str, imp.file) - case LetDecl(sym, annotations) => LetDecl(sym, annotations.map(_.mkClone)) + case imp: Import => Import(imp.sym, imp.str, imp.file)(imp.toLoc) + case LetDecl(sym, annotations) => LetDecl(sym, annotations.map(_.mkClone))(toLoc) case RcdField(field, rhs, sym) => RcdField(field.mkClone, rhs.mkClone, sym) case RcdSpread(rcd) => RcdSpread(rcd.mkClone) - case DefineVar(sym, rhs) => DefineVar(sym, rhs.mkClone) + case DefineVar(sym, rhs) => DefineVar(sym, rhs.mkClone)(toLoc) case sc: SetConfig => sc def describe: Str = @@ -1284,7 +1272,7 @@ sealed trait Statement extends Located, ProductWithExtraInfo, Describable: case LeadingDotSel(nme) => s"_?_.${nme.name}" case SetConfig(_) => "#config(...)" -final case class LetDecl(sym: LocalVarSymbol | TermSymbol, annotations: Ls[Annot]) extends Statement, AutoLocated +final case class LetDecl(sym: LocalVarSymbol | TermSymbol, annotations: Ls[Annot])(val toLoc: Opt[Loc]) extends Statement /** The symbol identifies the property, independently of any local binding used * to evaluate its value. Computed keys also have an identity, but cannot be @@ -1321,11 +1309,11 @@ object RcdField: RcdField(field, rhs, sym) final case class RcdSpread(rcd: Term) extends Statement, AutoLocated -final case class DefineVar(sym: LocalSymbol | TermSymbol, rhs: Term) extends Statement, AutoLocated +final case class DefineVar(sym: LocalSymbol | TermSymbol, rhs: Term)(val toLoc: Opt[Loc]) extends Statement /** A global configuration change directive (`#config(...)`). * Records a function that modifies the current compiler configuration. */ -final case class SetConfig(modify: hkmc2.Config => hkmc2.Config) extends Statement, AutoLocated: +final case class SetConfig(modify: hkmc2.Config => hkmc2.Config)(val toLoc: Opt[Loc]) extends Statement: override def toString: String = "#config(...)" enum Visibility: @@ -1413,7 +1401,7 @@ final case class TermDefinition( .getOrElse(Visibility.Public) lazy val mayRaiseEffects: Bool = annotations.forall: - case Annot.Pure => false + case Annot.Pure() => false case _ => true def extraAnnotations: Ls[Annot] = annotations.filter: case Annot.Modifier(Keyword.`declare` | Keyword.`abstract`) => false @@ -1487,7 +1475,7 @@ end ObjBody * in which case it is a `BlockMemberSymbol` when importing files explicitly * and a `TermSymbol` when the import is made implicitly by the compiler (eg, importing "Predef"). * Note that the `file` Path may not represent a real file; eg when importing "fs". */ -case class Import(sym: ImportSymbol, str: Str, file: io.Path) extends Statement, AutoLocated +case class Import(sym: ImportSymbol, str: Str, file: io.Path)(val toLoc: Opt[Loc]) extends Statement /** Declaration spans come from the syntax that introduced them, never from semantic @@ -1790,8 +1778,7 @@ final case class Param(flags: FldFlags, sym: VarSymbol, sign: Opt[Term], modulef def showDbg(using DebugPrinter): Str = flags.show(true) + sym.showDbg + sign.fold("")(": " + _.showDbg) final case class ParamList(flags: ParamListFlags, params: Ls[Param], restParam: Opt[Param]) -extends AutoLocated: - override protected def children: Vector[Located] = params.toVector ++ restParam +(val toLoc: Opt[Loc]) extends Located: def foreach(f: Param => Unit): Unit = (params.iterator ++ restParam).foreach(f) def paramCountLB: Int = params.length def paramCountUB: Bool = restParam.isEmpty @@ -1809,8 +1796,8 @@ extends AutoLocated: def showDbg(using DebugPrinter): Str = flags.showDbg + (params.map(_.showDbg) ++ restParam.toList.map("..." + _.showDbg)).mkString("(", ", ", ")") object PlainParamList: - def apply(params: Ls[Param]) = - ParamList(ParamListFlags.empty, params, N) + def apply(params: Ls[Param])(toLoc: Opt[Loc]) = + ParamList(ParamListFlags.empty, params, N)(toLoc) def unapply(pl: ParamList): Opt[Ls[Param]] = pl match case ParamList(ParamListFlags.empty, params, N) => S(params) case _ => N diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index 0d622f26d5..3ba594fddd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -349,7 +349,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C Case.Cls(ctorSym, st) -> lowerSplit(tail, cont) case (param, arg) :: args => val (cse, blk) = mkArgs(args) - (cse, Assign(arg, Select(sr, new Tree.Ident(param.id.name).withLocOf(arg))(S(param))(false), blk)) + (cse, Assign(arg, Select(sr, new Tree.Ident(param.id.name).withLocOf(arg))(S(param), N)(false), blk)) mkMatch(mkArgs(clsParams.iterator.zip(args).toList)) symbol match case cls: ClassSymbol if ctx.builtins.virtualClasses contains cls => @@ -371,12 +371,12 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C for (_, s) <- entries do LoweringCtx.loweringCtx.collectScopedSym(s) val objectSym = ctx.builtins.Object mkMatch( // checking that we have an object - Case.Cls(objectSym, Select(State.globalThisSymbol.asThis, Tree.Ident(objectSym.nme))(S(objectSym))(false)), + Case.Cls(objectSym, Select(State.globalThisSymbol.asThis, Tree.Ident(objectSym.nme))(S(objectSym), N)(false)), entries.foldRight(lowerSplit(tail, cont)): case ((fieldName, fieldSymbol), blk) => mkMatch( Case.Field(fieldName, safe = true), // we know we have an object, no need to check again - Assign(fieldSymbol, Select(sr, fieldName)(N)(false), blk) + Assign(fieldSymbol, Select(sr, fieldName)(N, N)(false), blk) ) ) case Split.Else(els) => @@ -426,8 +426,8 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C * match failure in the future. */ private def throwMatchErrorBlock = - Throw(Instantiate(mut = false, Select(State.globalThisSymbol.asThis, Tree.Ident("Error"))(S(ctx.builtins.Error))(false), - (Value.Lit(syntax.Tree.StrLit("match error")).asArg :: Nil) :: Nil)(InstantiateMetadata.empty)) // TODO add failed-match scrutinee info + Throw(Instantiate(mut = false, Select(State.globalThisSymbol.asThis, Tree.Ident("Error"))(S(ctx.builtins.Error), N)(false), + (Value.Lit(syntax.Tree.StrLit("match error"))(N).asArg :: Nil) :: Nil)(InstantiateMetadata.empty, N)) // TODO add failed-match scrutinee info /** Gives a lowering temp the join of the representations of the results stored into it. * @@ -510,7 +510,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C // NOTE: `shouldRewriteWhile` is not the same as `config.rewriteWhileLoops` // as shouldRewriteWhile is always true when effect handler lowering is on lazy val loopCont = if config.shouldRewriteWhile - then Return(Call(f.asMemberRef(tSym), Nil ne_:: Nil)(CallMetadata.mlsFunWithEffect)) + then Return(Call(f.asMemberRef(tSym), Nil ne_:: Nil)(CallMetadata.mlsFunWithEffect, N)) else Continue(loopLabel) val cont = form match @@ -547,14 +547,14 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C outerCtx.collectScopedSym(loopResult) outerCtx.collectScopedSym(isReturned) val loopEnd: Path = - Select(State.runtimeSymbol.asSimpleRef, Tree.Ident("LoopEnd"))(S(State.loopEndSymbol))(false) + Select(State.runtimeSymbol.asSimpleRef, Tree.Ident("LoopEnd"))(S(State.loopEndSymbol), N)(false) val blk = blockBuilder - .define(FunDefn(N, f, tSym, PlainParamList(Nil) :: Nil, Begin(body, Return(loopEnd)))(configOverride = N, annotations = Nil)) - .assign(loopResult, Call(f.asMemberRef(tSym), Nil ne_:: Nil)(CallMetadata.mlsFunWithEffect)) + .define(FunDefn(N, f, tSym, PlainParamList(Nil)(N) :: Nil, Begin(body, Return(loopEnd)))(configOverride = N, annotations = Nil)) + .assign(loopResult, Call(f.asMemberRef(tSym), Nil ne_:: Nil)(CallMetadata.mlsFunWithEffect, N)) if summon[LoweringCtx].mayRet then blk .assign(isReturned, Call(State.builtinOpsMap("!==").asSimpleRef, - (loopResult.asPath.asArg :: loopEnd.asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun)) + (loopResult.asPath.asArg :: loopEnd.asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, N)) .ifthen(isReturned.asSimpleRef, Case.Lit(Tree.BoolLit(true)), Return(loopResult.asSimpleRef), N diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala index 0132b578a8..6540a74c59 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala @@ -34,7 +34,7 @@ trait TermSynthesizer(using State): Split.Let(sym, term, inner) protected final def param = Param.simple - protected final def paramList(params: Param*) = PlainParamList(params.toList) + protected final def paramList(params: Param*) = PlainParamList(params.toList)(N) private lazy val runtimeRef: Term.Ref = State.runtimeSymbol.ref().resolve diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala index 5cea64e37e..dc812615a9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -218,7 +218,7 @@ class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, St val consequent = Split.Else(makeResult(fieldSymbol)) val branch = Branch(scrutinee.safeRef, fieldTest, consequent) SynthIf(branch ~: Split.Else(subScrutinee.default)) - LetDecl(subScrutinee.symbol, Nil) :: DefineVar(subScrutinee.symbol, result) :: Nil + LetDecl(subScrutinee.symbol, Nil)(N) :: DefineVar(subScrutinee.symbol, result)(N) :: Nil .toList // For each pattern, we compile a split and bind the result to a variable. // The variable will be a field of the output record. @@ -235,7 +235,7 @@ class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, St else makeMatchSuccess(outputSymbol.use, nullifyEmptyBindings(bindings.use)), alternative = Split.Else(emptyMatchResult("topmost"))) val test = SynthIf(split) - (DefineVar(symbol, test) :: LetDecl(symbol, Nil) :: stmts, (label, symbol.safeRef) :: results) + (DefineVar(symbol, test)(N) :: LetDecl(symbol, Nil)(N) :: stmts, (label, symbol.safeRef) :: results) // Materialize the matcher's final return value. Singleton matchers return // their only field directly; multi-label matchers still return a record. val resultTerm = resultTerms.reverse match @@ -528,8 +528,8 @@ class Compiler(using Context)(using codegen.Erasure)(using tl: TL)(using Ctx, St // then `term`. val letBindings = pattern.symbols.flatMap: symbol => val termSymbol = correspondence(symbol) - LetDecl(termSymbol, Nil) :: - DefineVar(termSymbol, sel(bindingsSymbol.safeRef, termSymbol.name)) :: Nil + LetDecl(termSymbol, Nil)(N) :: + DefineVar(termSymbol, sel(bindingsSymbol.safeRef, termSymbol.name))(N) :: Nil val makeSplit = completePattern(pattern, scrutinee, subScrutinees, Nil) (makeConsequent, alternative) => Split.Let( sym = transformSymbol, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala index 4766d845cd..a6fd31f653 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/FixedPointCompiler.scala @@ -762,11 +762,11 @@ class FixedPointCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, matchers.flatMap((_, stepImpls, postMatcherOpt) => stepImpls ::: postMatcherOpt.fold(Nil)(_._2) ).flatMap: (symbol, params, body) => - LetDecl(symbol, Nil) :: DefineVar(symbol, Term.Lam(params, body)) :: Nil + LetDecl(symbol, Nil)(N) :: DefineVar(symbol, Term.Lam(params, body))(N) :: Nil ::: List( - LetDecl(phaseSymbol, Nil), DefineVar(phaseSymbol, int(0)), - LetDecl(focusSymbol, Nil), DefineVar(focusSymbol, inputSymbol.safeRef), - LetDecl(failPhaseSymbol, Nil), DefineVar(failPhaseSymbol, int(-1))) + LetDecl(phaseSymbol, Nil)(N), DefineVar(phaseSymbol, int(0))(N), + LetDecl(focusSymbol, Nil)(N), DefineVar(focusSymbol, inputSymbol.safeRef)(N), + LetDecl(failPhaseSymbol, Nil)(N), DefineVar(failPhaseSymbol, int(-1))(N)) Machine(paramList(param(inputSymbol)), prelude, loop, result) @@ -965,14 +965,14 @@ class FixedPointCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, val prelude = (redexImpls ::: sideImpls ::: postMatcherOpt.fold(Nil)(_._2)).flatMap: (symbol, params, body) => - LetDecl(symbol, Nil) :: DefineVar(symbol, Term.Lam(params, body)) :: Nil + LetDecl(symbol, Nil)(N) :: DefineVar(symbol, Term.Lam(params, body))(N) :: Nil ::: List( - LetDecl(modeSymbol, Nil), DefineVar(modeSymbol, int(ModeFind)), - LetDecl(focusSymbol, Nil), DefineVar(focusSymbol, inputSymbol.safeRef), - LetDecl(stackSymbol, Nil), DefineVar(stackSymbol, `null`), - LetDecl(resultSymbol, Nil), DefineVar(resultSymbol, `null`)) + LetDecl(modeSymbol, Nil)(N), DefineVar(modeSymbol, int(ModeFind))(N), + LetDecl(focusSymbol, Nil)(N), DefineVar(focusSymbol, inputSymbol.safeRef)(N), + LetDecl(stackSymbol, Nil)(N), DefineVar(stackSymbol, `null`)(N), + LetDecl(resultSymbol, Nil)(N), DefineVar(resultSymbol, `null`)(N)) ::: (if requireProgress then - LetDecl(progressedSymbol, Nil) :: DefineVar(progressedSymbol, bool(false)) :: Nil + LetDecl(progressedSymbol, Nil)(N) :: DefineVar(progressedSymbol, bool(false))(N) :: Nil else Nil) // Succeed with the normal form, post-processed by the trailing diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala index 9b42947856..c3b322a858 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala @@ -836,7 +836,7 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise // consequence split. (makeConsequent, alternative) => Split.Let( sym = lambdaSymbol, - term = Term.Lam(PlainParamList(params), transform.mkClone), + term = Term.Lam(PlainParamList(params)(N), transform.mkClone), // Declare the lambda function at the outermost level. Even if there // are multiple disjunctions in the consequent, we will not need to // repeat the `transform` term. @@ -1110,7 +1110,7 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise val lambdaSymbol = new TempSymbol(N, erasedType = N, "transform") (makeConsequent, alternative) => Split.Let( sym = lambdaSymbol, - term = Term.Lam(PlainParamList(params), transform), + term = Term.Lam(PlainParamList(params)(N), transform), tail = make( // Note that the output is not used. Semantically, the `transform` // term can only access the matched values by bindings. @@ -1285,7 +1285,7 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise val patternInputs = patternParameters.map(p => p.copy(flags = FldFlags.empty)(p.toLoc)) // The last parameter is the scrutinee. val scrutParam = Param.simple(scrut) - val ps = PlainParamList(patternInputs :+ scrutParam) + val ps = PlainParamList(patternInputs :+ scrutParam)(N) (sym, ps, topmost) /** Translate a list of extractor/matching functions for the given pattern. @@ -1353,11 +1353,11 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise topmost: Split ): Ls[Statement] = val fieldSymbol = TempSymbol(N, erasedType = N, name) - val decl = LetDecl(fieldSymbol, Nil) + val decl = LetDecl(fieldSymbol, Nil)(N) val param = Param.simple(scrut) - val paramList = PlainParamList(param :: Nil) + val paramList = PlainParamList(param :: Nil)(N) val lambda = Term.Lam(paramList, Term.SynthIf(topmost)) - val defineVar = DefineVar(fieldSymbol, lambda) + val defineVar = DefineVar(fieldSymbol, lambda)(N) val field = RcdField(str(name), fieldSymbol.safeRef) decl :: defineVar :: field :: Nil diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index 03a60c9649..52d6da3a07 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -129,7 +129,7 @@ object DebugPrinter: val emptyValues: Set[Any] = Set( None, Nil, Vector.empty, - semantics.PlainParamList(Nil), + semantics.PlainParamList(Nil)(N), ParamListFlags.empty, TermDefFlags.empty, FldFlags.empty, diff --git a/hkmc2/shared/src/test/mlscript/basics/DataClass.mls b/hkmc2/shared/src/test/mlscript/basics/DataClass.mls index 8e475fad67..0c51f3e264 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DataClass.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DataClass.mls @@ -12,6 +12,8 @@ Foo(1).x :todo data Foo(x: Int) //│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.13: data Foo(x: Int) +//│ ║ ^^^^ //│ ╟── This annotation is not supported on application terms. //│ ║ l.13: data Foo(x: Int) //│ ╙── ^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/block-staging/Functions.mls b/hkmc2/shared/src/test/mlscript/block-staging/Functions.mls index 9f749b55ab..18b113863a 100644 --- a/hkmc2/shared/src/test/mlscript/block-staging/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/block-staging/Functions.mls @@ -261,7 +261,7 @@ staged module A with //│ ═══[COMPILATION ERROR] Field assignment is not supported in staged modules: a //│ ╔══[COMPILATION ERROR] Other Results not supported in staged module: class hkmc2.codegen.Record //│ ║ l.260: fun g() = {1 : 2} -//│ ╙── ^ +//│ ╙── ^^^^^ //│ > fun ctor_() = //│ > x = C(1) //│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadConfigDirective.mls b/hkmc2/shared/src/test/mlscript/codegen/BadConfigDirective.mls index 994775b521..c171e66e37 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadConfigDirective.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadConfigDirective.mls @@ -5,6 +5,8 @@ :w @config(tailRecOpt: false) "hi" //│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.6: @config(tailRecOpt: false) "hi" +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── This annotation is not supported on string literal terms. //│ ║ l.6: @config(tailRecOpt: false) "hi" //│ ╙── ^^^^ @@ -19,7 +21,7 @@ :e #lang(1.0.x) //│ ╔══[COMPILATION ERROR] Unknown language version '1.0.x' -//│ ║ l.20: #lang(1.0.x) +//│ ║ l.22: #lang(1.0.x) //│ ║ ^^^^^ //│ ╙── Available language versions: 0.2.x, 0.3.x @@ -27,12 +29,12 @@ :e #lang(unknownLanguageField: true) //│ ╔══[COMPILATION ERROR] Unsupported Language argument -//│ ║ l.28: #lang(unknownLanguageField: true) +//│ ║ l.30: #lang(unknownLanguageField: true) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ :e #lang(versionName: "custom") //│ ╔══[COMPILATION ERROR] Unsupported Language argument -//│ ║ l.35: #lang(versionName: "custom") +//│ ║ l.37: #lang(versionName: "custom") //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls b/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls index c7cb6ab466..fcadadaab6 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls @@ -14,6 +14,8 @@ yield 1 @generator () => 3 //│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.14: @generator +//│ ║ ^^^^^^^^^^ //│ ╟── This annotation is not supported on function literal terms. //│ ║ l.15: () => 3 //│ ╙── ^^^^^^^ @@ -26,17 +28,17 @@ class C with fun f = yield 1 //│ ╔══[COMPILATION ERROR] Generators are not supported on functions without a parameter list -//│ ║ l.26: fun f = +//│ ║ l.28: fun f = //│ ║ ^^^^^^^ -//│ ║ l.27: yield 1 +//│ ║ l.29: yield 1 //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Yield expressions are not allowed in this context. -//│ ║ l.27: yield 1 +//│ ║ l.29: yield 1 //│ ╙── ^^^^^^^ :ge @generator fun bad = 1 //│ ╔══[COMPILATION ERROR] Generators are not supported on functions without a parameter list -//│ ║ l.39: fun bad = 1 +//│ ║ l.41: fun bad = 1 //│ ╙── ^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls index d368b46382..8cc52dad51 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls @@ -117,10 +117,10 @@ bar([foo].0) bar([foo, x => x].1) //│ ╔══[COMPILATION ERROR] Cannot determine if 0 is a function. //│ ║ l.116: bar([foo].0) -//│ ╙── ^^ +//│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot determine if 1 is a function. //│ ║ l.117: bar([foo, x => x].1) -//│ ╙── ^^ +//│ ╙── ^^^^^^^^^^^^^^^ //│ = 0 @@ -135,7 +135,7 @@ bar([foo, x => x].(i)) [foo, x => x].(i)(0) //│ ╔══[COMPILATION ERROR] Cannot determine if the dynamic selection is a function object. //│ ║ l.135: [foo, x => x].(i)(0) -//│ ╙── ^ +//│ ╙── ^^^^^^^^^^^^^^^^ class Foo() with diff --git a/hkmc2/shared/src/test/mlscript/codegen/SourceLocations.mls b/hkmc2/shared/src/test/mlscript/codegen/SourceLocations.mls new file mode 100644 index 0000000000..82775ba8e7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/codegen/SourceLocations.mls @@ -0,0 +1,43 @@ +:. +:js +open annotations + +// Marker annotations have separate locations for each occurrence. +:w +@inline +class First +@inline +class Second +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.7: @inline +//│ ╙── ^^^^^^^ +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.9: @inline +//│ ╙── ^^^^^^^ + +// Annotations on let bindings point to the annotation occurrence. +:w +@tailrec +let value = 1 +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.20: @tailrec +//│ ╙── ^^^^^^^^ +//│ value = 1 + +// Annotations on bodyless functions retain their own locations. +:w +@inline +fun abstractFunction() +//│ ╔══[WARNING] Only functions with a body may be marked as @inline. +//│ ║ l.29: @inline +//│ ╙── ^^^^^^^ + +// This error is emitted from lowered IR after the prefix has become a temporary. +:ftc +:ge +let index = 0 +[(x => x)].(index)(1) +//│ ╔══[COMPILATION ERROR] Cannot determine if the dynamic selection is a function object. +//│ ║ l.39: [(x => x)].(index)(1) +//│ ╙── ^^^^^^^^^^^^^^^^^ +//│ index = 0 diff --git a/hkmc2/shared/src/test/mlscript/tailrec/Annots.mls b/hkmc2/shared/src/test/mlscript/tailrec/Annots.mls index 30e09e83cf..70190a6379 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/Annots.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/Annots.mls @@ -5,18 +5,24 @@ open annotations :w @tailrec fun f -//│ ═══[WARNING] Only functions with a body may be marked as @tailrec. +//│ ╔══[WARNING] Only functions with a body may be marked as @tailrec. +//│ ║ l.6: @tailrec +//│ ╙── ^^^^^^^^ :w @tailrec class A -//│ ═══[WARNING] This annotation has no effect. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.13: @tailrec +//│ ╙── ^^^^^^^^ :w @tailrec 2 + 2 //│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.20: @tailrec 2 + 2 +//│ ║ ^^^^^^^^ //│ ╟── This annotation is not supported on application terms. -//│ ║ l.16: @tailrec 2 + 2 +//│ ║ l.20: @tailrec 2 + 2 //│ ╙── ^^^^^ //│ = 4 @@ -27,7 +33,9 @@ fun g = f :w @tailcall fun g = 2 -//│ ═══[WARNING] This annotation has no effect. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.34: @tailcall +//│ ╙── ^^^^^^^^^ fun test = @tailcall test @@ -37,8 +45,10 @@ let f = 0 fun test = @tailcall f //│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.46: @tailcall f +//│ ║ ^^^^^^^^^ //│ ╟── This annotation is not supported on reference terms. -//│ ║ l.38: @tailcall f +//│ ║ l.46: @tailcall f //│ ╙── ^ //│ f = 0 @@ -48,14 +58,16 @@ class A with fun f() = g() fun g() = f() //│ ╔══[COMPILATION ERROR] Class methods may not yet be marked @tailrec. -//│ ║ l.48: fun f() = g() +//│ ║ l.58: fun f() = g() //│ ╙── ^ :w class A with @tailcall fun f = 2 -//│ ═══[WARNING] This annotation has no effect. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.66: @tailcall +//│ ╙── ^^^^^^^^^ :todo class A @@ -63,7 +75,7 @@ module A with @tailrec fun f = 2 //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.64: fun f = 2 +//│ ║ l.76: fun f = 2 //│ ╙── ^ :w @@ -71,7 +83,9 @@ class A module A with @tailcall fun f = 2 -//│ ═══[WARNING] This annotation has no effect. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.84: @tailcall +//│ ╙── ^^^^^^^^^ :w @@ -80,12 +94,16 @@ module A with handle h = Object with @tailcall fun f()(r) = 2 -//│ ═══[WARNING] This annotation has no effect. +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.95: @tailcall +//│ ╙── ^^^^^^^^^ :w fun test = @tailcall 1 + 2 //│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.103: @tailcall 1 + 2 +//│ ║ ^^^^^^^^^ //│ ╟── The @tailcall annotation has no effect on calls to built-in symbols. -//│ ║ l.87: @tailcall 1 + 2 -//│ ╙── ^^^^^ +//│ ║ l.103: @tailcall 1 + 2 +//│ ╙── ^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 3a03f52a1c..43ed423c2b 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -86,7 +86,9 @@ fun constOf(x: Int) = wasm.ref.i31(wasm.i32.const(x)) // not used as an immediate (or just do it away entirely). :ge wasm.i32.const(-1) -//│ ═══[COMPILATION ERROR] Wasm intrinsic 'i32.const' expects an integer literal immediate +//│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' expects an integer literal immediate +//│ ║ l.88: wasm.i32.const(-1) +//│ ╙── ^^ // The remaining arithmetic instructions. Each runs on operands whose result identifies it - `-1`, `42`, `3` diff --git a/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls b/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls index 35abe0f1f5..449ef9a522 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls @@ -202,7 +202,7 @@ class A(val x) with A(1).get //│ ╔══[COMPILATION ERROR] `member:get` is neither a field access nor a callable method //│ ║ l.202: A(1).get -//│ ╙── ^^^^ +//│ ╙── ^^^^^^^^ // Method result types are taken from the return annotation, so primitives stay unboxed diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index 16dbc82ba1..e463cc7dcd 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -305,7 +305,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: NoSymbol, Call( Elaborator.State.runtimeSymbol.asSimpleRef.selSN("printRaw"), - (Arg(N, sym.asPath) :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun), + (Arg(N, sym.asPath) :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun, N), End()) val je = nestedScp.givenIn: jsb.block(le, endSemi = false) From f30a6485bf871bd2849dfd32938ed964a2045f7e Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 29 Sep 2026 22:34:52 +0800 Subject: [PATCH 147/228] Preserve complete declaration spans while unwrapping annotations --- .../scala/hkmc2/semantics/Elaborator.scala | 100 +++++++++--------- .../test/mlscript/codegen/BadGenerators.mls | 8 +- .../mlscript/newres/StoredSpecializations.mls | 2 +- .../src/test/mlscript/objbuf/BasicsObjBuf.mls | 26 +++-- .../ups/fixpoint/UnsupportedShapes.mls | 2 +- .../test/mlscript/ups/syntax/WrongArity.mls | 32 +++++- 6 files changed, 108 insertions(+), 62 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 3ffe73e0bd..0344dda822 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1972,7 +1972,11 @@ extends Importer: // * @param funs: // * While elaborating a block, we move all function definitions to the top (similar to JS function semantics) @tailrec - def go(sts: Ls[Tree], annotations: Ls[Annot], acc: Ls[Statement]): Ctxl[(Blk | Rcd, Ctx)] = + def go(sts: Ls[Tree], annotations: Ls[Annot], acc: Ls[Statement])(sourceLoc: Opt[Loc]): Ctxl[(Blk | Rcd, Ctx)] = + // Annotation wrappers belong to the declaration's source span even when the + // annotation itself is invalid. Preserve the outer syntax while unwrapping; + // advancing to another statement starts a fresh span. + val statementLoc = sourceLoc.orElse(sts.headOption.flatMap(_.toLoc)) /** Call this function when the following term cannot be annotated. */ def reportUnusedAnnotations: Unit = if annotations.nonEmpty then raise: WarningReport: @@ -1990,16 +1994,16 @@ extends Importer: (mkBlk(acc, N, hasResult), ctx) case Constructor(Block(ctors)) :: sts => // TODO properly handle (it currently desugars to sibling classes) - go(sts, annotations, acc) + go(sts, annotations, acc)(N) case (ctorParams @ Constructor(ConstructorParamDecl(_))) :: sts => // constructor(x, y) or constructor(x, y)(u, v) syntax: params are extracted during class elaboration ctx.getOuter match case S(_: ClassSymbol) => - go(sts, annotations, acc) + go(sts, annotations, acc)(N) case _ => raise(ErrorReport(msg"'constructor(...)' declarations are only allowed in class bodies" -> ctorParams.toLoc :: Nil)) - go(sts, annotations, acc) + go(sts, annotations, acc)(N) case Open(bod) :: sts => reportUnusedAnnotations bod match @@ -2015,14 +2019,14 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement shape." -> bod.toLoc :: Nil)) N match - case N => go(sts, annotations, acc) + case N => go(sts, annotations, acc)(N) case S((base, importedTrees)) => base match case baseId: Ident => ctx.get(baseId.name) match case S(baseElem) if newResolution && importedTrees.isEmpty => ctx.copy(wildcardOpens = Ctx.OpenSource(baseElem)(baseId) :: ctx.wildcardOpens).givenIn: - go(sts, Nil, acc) + go(sts, Nil, acc)(N) case S(baseElem) => val importedNames = importedTrees match case N => // "wilcard" open @@ -2046,13 +2050,13 @@ extends Importer: raise(ErrorReport(msg"Illegal 'open' statement element." -> t.toLoc :: Nil)) Nil (ctx elem_++ importedNames).givenIn: - go(sts, Nil, acc) + go(sts, Nil, acc)(N) case N => raise(ErrorReport(msg"Name not found: ${baseId.name}" -> baseId.toLoc :: Nil)) - go(sts, Nil, acc) + go(sts, Nil, acc)(N) case _ => raise(ErrorReport(msg"Illegal 'open' statement base." -> base.toLoc :: Nil)) - go(sts, Nil, acc) + go(sts, Nil, acc)(N) case (m @ PrefixApp(Keywrd(Keyword.`import`), arg)) :: sts => reportUnusedAnnotations val pathAndAlias: Opt[(Tree, Opt[Ident])] = arg match @@ -2066,7 +2070,7 @@ extends Importer: case pathArg => S((pathArg, N)) val (newCtx, newAcc) = pathAndAlias match case S((StrLit(path), alias)) => - val stmt = importPath(path, alias)(m.toLoc) + val stmt = importPath(path, alias)(statementLoc) (ctx + (stmt.sym.nme -> stmt.sym), stmt :: acc) case S((pathArg, _)) => @@ -2077,11 +2081,11 @@ extends Importer: case N => // errors have been reported above. (ctx, acc) newCtx.givenIn: - go(sts, Nil, newAcc) + go(sts, Nil, newAcc)(N) case Spread(Keywrd(Keyword.`...`), S(body)) :: sts => reportUnusedAnnotations - go(sts, Nil, RcdSpread(term(body, fieldInterpretation)) :: acc) + go(sts, Nil, RcdSpread(term(body, fieldInterpretation)) :: acc)(N) case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) :: sts => var newCtx = ctx val (rlhs, rhs_t) = rhs match @@ -2116,7 +2120,7 @@ extends Importer: raise(ErrorReport(msg"Unexpected record key shape." -> rlhs.toLoc :: Nil)) RcdField(Term.Error().withLocOf(rlhs), rhs_t) :: acc newCtx.givenIn: - go(sts, Nil, newAcc) + go(sts, Nil, newAcc)(N) case (hd @ LetLike(kw @ Keywrd(`let`), Apps(id: Ident, tups), rhso, N)) :: sts if tups.isEmpty || id.name.headOption.exists(_.isLower) => val sym = @@ -2129,16 +2133,16 @@ extends Importer: case S(rhs) => val rrhs = tups.foldRight(rhs): InfixApp(_, Keywrd(Keyword.`=>`), _) - mkLetBinding(sym, term(rrhs, Trm), annotations)(hd.toLoc) reverse_::: acc + mkLetBinding(sym, term(rrhs, Trm), annotations)(statementLoc) reverse_::: acc case N => if tups.nonEmpty then raise(ErrorReport(msg"Expected a right-hand side for let bindings with parameters" -> hd.toLoc :: Nil)) - LetDecl(sym, annotations)(hd.toLoc) :: acc + LetDecl(sym, annotations)(statementLoc) :: acc (ctx + (id.name -> sym)) givenIn: - go(sts, Nil, newAcc) + go(sts, Nil, newAcc)(N) case (tree @ LetLike(Keywrd(`let`), lhs, _, N)) :: sts => raise(ErrorReport(msg"Unsupported let binding shape" -> tree.toLoc :: Nil)) - go(sts, Nil, Term.Error().withLocOf(tree) :: acc) + go(sts, Nil, Term.Error().withLocOf(tree) :: acc)(N) case (definition @ Def(lhs, rhs)) :: sts => reportUnusedAnnotations lhs match @@ -2147,23 +2151,23 @@ extends Importer: ctx.get(id.name) match case S(elem) => elem.symbol match - case S(sym: (LocalSymbol | TermSymbol)) => go(sts, Nil, defineVar(sym, r)(definition.toLoc) :: acc) + case S(sym: (LocalSymbol | TermSymbol)) => go(sts, Nil, defineVar(sym, r)(statementLoc) :: acc)(N) case S(sym) => raise(ErrorReport(msg"Symbol '${id.name}' is not a variable and cannot be reassigned" -> id.toLoc :: Nil)) - go(sts, Nil, Term.Error().withLocOf(id) :: acc) + go(sts, Nil, Term.Error().withLocOf(id) :: acc)(N) case N => raise(ErrorReport(msg"Name not found: ${id.name}" -> id.toLoc :: Nil)) - go(sts, Nil, Term.Error().withLocOf(id) :: acc) + go(sts, Nil, Term.Error().withLocOf(id) :: acc)(N) case N => // TODO lookup in members? inherited/refined stuff? raise(ErrorReport(msg"Name not found: ${id.name}" -> id.toLoc :: Nil)) - go(sts, Nil, Term.Error().withLocOf(id) :: acc) + go(sts, Nil, Term.Error().withLocOf(id) :: acc)(N) case App(base, args) => - go(Def(base, InfixApp(args, Keywrd(Keyword.`=>`), rhs)) :: sts, Nil, acc) + go(Def(base, InfixApp(args, Keywrd(Keyword.`=>`), rhs)) :: sts, Nil, acc)(statementLoc) case _ => raise(ErrorReport(msg"Unrecognized definitional assignment left-hand side: ${lhs.describe}" -> lhs.toLoc :: Nil)) // TODO BE - go(sts, Nil, Term.Error().withLocOf(lhs) :: acc) + go(sts, Nil, Term.Error().withLocOf(lhs) :: acc)(N) case (td @ TermDef(k, nme, rhs)) :: sts => log(s"Processing term definition $nme") td.symbName match @@ -2179,15 +2183,15 @@ extends Importer: if (k is MutVal) && owner.isEmpty then raise: ErrorReport: - msg"Mutable 'val' definitions are only valid as members of a module, object, or class definition" -> td.toLoc + msg"Mutable 'val' definitions are only valid as members of a module, object, or class definition" -> statementLoc :: Nil - return go(sts, Nil, acc) + return go(sts, Nil, acc)(N) if owner.isDefined && !identifierPattern.matches(id.name) then raise: ErrorReport: msg"Illegal ${k.desc} member name: '${id.name}'" -> nme.toLoc :: illegalMemberNameTail - return go(sts, Nil, acc) + return go(sts, Nil, acc)(N) val isMethod = owner.exists(_.isInstanceOf[ClassSymbol]) val tsym = TermSymbol(k, owner, id) // TODO? @@ -2227,7 +2231,7 @@ extends Importer: val hasGeneratorAnnotation = annotations.exists(_.isInstanceOf[Annot.Generator]) val hasAsyncAnnotation = annotations.exists(_.isInstanceOf[Annot.Async]) if pss.isEmpty && hasGeneratorAnnotation then - raise(ErrorReport(msg"Generators are not supported on functions without a parameter list" -> td.toLoc :: Nil)) + raise(ErrorReport(msg"Generators are not supported on functions without a parameter list" -> statementLoc :: Nil)) newCtx.nest(OuterCtx.Function(nonLocalRetHandler, S(tsym))(pss.nonEmpty && hasGeneratorAnnotation, hasAsyncAnnotation)).givenIn: newCtx ?=> val b = term(rhs, Trm)(using newCtx) if nonLocalRetHandler.directRefs.isEmpty then b else @@ -2251,17 +2255,17 @@ extends Importer: // Retain the source signature's meaning; Erasure decides which arrows are physical parameters. if newResolution then s.foreach(registerSignature) val tdf = TermDefinition(k, sym, tsym, pss, tps, s, body, - TermDefFlags.empty.copy(isMethod = isMethod, hasResultAnnotation = td.annotatedResultType.isDefined), mfn, annotations, N)(td.toLoc) + TermDefFlags.empty.copy(isMethod = isMethod, hasResultAnnotation = td.annotatedResultType.isDefined), mfn, annotations, N)(statementLoc) sym.tsym = S(tsym) tsym.defn = S(tdf) if newResolution then checkDeclaredResult(tdf) tdf - go(sts, Nil, tdf :: acc) + go(sts, Nil, tdf :: acc)(N) case L(d) => reportUnusedAnnotations raise(d) - go(sts, Nil, acc) + go(sts, Nil, acc)(N) case (td @ TypeDef(k, head, rhs)) :: sts => val owner = ctx.outer.inner @@ -2280,14 +2284,14 @@ extends Importer: case R(id) => id case L(d) => raise(d) - return go(sts, Nil, acc) + return go(sts, Nil, acc)(N) if owner.isDefined && !identifierPattern.matches(nme.name) then raise: ErrorReport: msg"Illegal ${k.desc} member name: '${nme.name}'" -> nme.toLoc :: illegalMemberNameTail - return go(sts, Nil, acc) + return go(sts, Nil, acc)(N) val sym = members.getOrElse(nme.name, lastWords(s"Symbol not found: ${nme.name}")) @@ -2450,7 +2454,7 @@ extends Importer: assert(body.isEmpty) val d = given Ctx = newCtx - semantics.TypeDef(alsSym, sym, tps, rhs.map(term(_, Tpe)), N, annotations)(td.toLoc) + semantics.TypeDef(alsSym, sym, tps, rhs.map(term(_, Tpe)), N, annotations)(statementLoc) alsSym.defn = S(d) d case Pat => @@ -2459,7 +2463,7 @@ extends Importer: if pss.length > 1 then raise: ErrorReport: msg"Multiple parameter lists are not supported for this definition." -> - td.toLoc :: Nil + statementLoc :: Nil // Pattern definition should not have a body like class definition. assert(body.isEmpty) val ps = pss.headOption @@ -2482,7 +2486,7 @@ extends Importer: log(s"`${patSym.nme}`'s extraction parameters: ${extractionParams.mkString("[", ", ", "]")}") // Empty pattern body is considered as wildcard patterns. val rhs = td.rhs.getOrElse: - raise(ErrorReport(msg"Pattern definitions must have a body." -> td.toLoc :: Nil)) + raise(ErrorReport(msg"Pattern definitions must have a body." -> statementLoc :: Nil)) Tree.Under() // Elaborate the pattern body with the pattern parameters. val pat = pattern(rhs)(using ctx ++ patternParams.iterator.map(p => p.sym.name -> p.sym)) @@ -2505,7 +2509,7 @@ extends Importer: // `paramsOpt` is set to `N` because we don't want parameters to // appear in the generated class's constructor. val pd = PatternDef(owner, patSym, sym, tps, allParams, - patternParams, extractionParams, pat, annotations)(td.toLoc) + patternParams, extractionParams, pat, annotations)(statementLoc) patSym.defn = S(pd) pd case k: (Mod.type | Obj.type) => @@ -2521,7 +2525,7 @@ extends Importer: val md = val (bod, c) = mkBody(Nil) ModuleOrObjectDef(owner, modSym, sym, - tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations)(outerCtx.scope, td.toLoc) + tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations)(outerCtx.scope, statementLoc) modSym.defn = S(md) md case Cls => @@ -2566,7 +2570,7 @@ extends Importer: case a @ Annot.Modifier(Keyword.`declare`) => a , S(clsSym), - )(td.toLoc) + )(statementLoc) if pss.nonEmpty then sym.tsym = S(ctsym) ctsym.defn = S(ctdef) // Note: do NOT set sym.tsym for constructor(...) classes; they are not callable as functions. @@ -2574,35 +2578,35 @@ extends Importer: else N val cd = val (bod, c) = mkBody(auxCtorPss) - ClassDef(owner, Cls, clsSym, sym, tsym, tps, pss, newOf(td), ObjBody(bod), annotations, comp, auxCtorParams = auxCtorPss)(td.toLoc) + ClassDef(owner, Cls, clsSym, sym, tsym, tps, pss, newOf(td), ObjBody(bod), annotations, comp, auxCtorParams = auxCtorPss)(statementLoc) clsSym.defn = S(cd) cd case Trt | Mxn => lastWords(s"Unexpected type definition kind here: $k") - go(sts, Nil, defn :: acc) + go(sts, Nil, defn :: acc)(N) case Annotated(annotation, target) :: sts => - go(target :: sts, annotations ++ annot(annotation), acc) + go(target :: sts, annotations ++ annot(annotation), acc)(statementLoc) // * With tight right precedence, `#config(args)` is parsed as `App(Directive(config, Tup()), Tup(args))`. // * Reconstruct as `Directive(config, Tup(args))` and re-process. case App(Directive(prefix, _), args) :: sts => - go(Directive(prefix, args) :: sts, annotations, acc) + go(Directive(prefix, args) :: sts, annotations, acc)(statementLoc) case (directive @ Directive(Ident("config"), Tup(args))) :: sts => reportUnusedAnnotations val modify = ConfigParser.parseOverrides(args) - go(sts, Nil, SetConfig(modify)(directive.toLoc) :: acc) + go(sts, Nil, SetConfig(modify)(statementLoc) :: acc)(N) case (directive @ Directive(Ident("lang"), Tup(args))) :: sts => reportUnusedAnnotations val modify = ConfigParser.parseLanguageDirective(args) - go(sts, Nil, SetConfig(modify)(directive.toLoc) :: acc) + go(sts, Nil, SetConfig(modify)(statementLoc) :: acc)(N) case Directive(Ident(name), _) :: sts => raise(ErrorReport( msg"Unknown directive '#${name}'" -> sts.headOption.flatMap(_.toLoc) :: Nil, source = Diagnostic.Source.Compilation)) - go(sts, annotations, acc) + go(sts, annotations, acc)(N) case (dir @ Directive(prefix, _)) :: sts => raise(ErrorReport( msg"Expected a directive name after '#', but found ${prefix.describe}" -> prefix.toLoc :: Nil, source = Diagnostic.Source.Compilation)) - go(sts, annotations, acc) + go(sts, annotations, acc)(N) case (st: Tree) :: sts => // TODO reject plain term statements? Currently, `(1, 2)` is allowed to elaborate (tho it should be rejected in type checking later) val interpretation = if sts.isEmpty then resultInterp else Trm @@ -2610,11 +2614,11 @@ extends Importer: case (acc, ann) => Term.Annotated(ann, acc) sts match case Nil => (mkBlk(acc, S(res), hasResult), ctx) - case _ => go(sts, Nil, res :: acc) + case _ => go(sts, Nil, res :: acc)(N) end go val res = ctx.withMembers(members).givenIn: - go(blk.desugStmts, Nil, Nil) + go(blk.desugStmts, Nil, Nil)(N) // if newResolution then members.valuesIterator.foreach(_.complete()) diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls b/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls index fcadadaab6..9ce0e689e1 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadGenerators.mls @@ -28,8 +28,10 @@ class C with fun f = yield 1 //│ ╔══[COMPILATION ERROR] Generators are not supported on functions without a parameter list +//│ ║ l.27: @generator +//│ ║ ^^^^^^^^^^ //│ ║ l.28: fun f = -//│ ║ ^^^^^^^ +//│ ║ ^^^^^^^^^ //│ ║ l.29: yield 1 //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Yield expressions are not allowed in this context. @@ -40,5 +42,7 @@ class C with @generator fun bad = 1 //│ ╔══[COMPILATION ERROR] Generators are not supported on functions without a parameter list -//│ ║ l.41: fun bad = 1 +//│ ║ l.42: @generator +//│ ║ ^^^^^^^^^^ +//│ ║ l.43: fun bad = 1 //│ ╙── ^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls b/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls index 13018d4d2b..510db34d3b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls +++ b/hkmc2/shared/src/test/mlscript/newres/StoredSpecializations.mls @@ -70,7 +70,7 @@ second[Str] //│ ║ ^^^^^^^^^^^ //│ ╟── Function 'declared' expected 0 type arguments, but got 1 //│ ║ l.57: private fun declared[A](value: A): A = value -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = fun declared //│ first = fun inferred //│ second = fun declared diff --git a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls index 7b6e979b76..f73bb10db5 100644 --- a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls +++ b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls @@ -48,6 +48,8 @@ class A2(x) with @buffered class B0(x) //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. +//│ ║ l.48: @buffered +//│ ║ ^^^^^^^^^ //│ ║ l.49: class B0(x) //│ ╙── ^^^^^^^^^^^ @@ -56,12 +58,16 @@ class B0(x) @buffered class B1(x) //│ ╔══[COMPILATION ERROR] Only one of bufferable annotation is allowed. -//│ ║ l.55: @buffered +//│ ║ l.57: @buffered //│ ║ ^^^^^^^^^ -//│ ║ l.56: @buffered +//│ ║ l.58: @buffered //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.57: class B1(x) +//│ ║ l.57: @buffered +//│ ║ ^^^^^^^^^ +//│ ║ l.58: @buffered +//│ ║ ^^^^^^^^^ +//│ ║ l.59: class B1(x) //│ ╙── ^^^^^^^^^^^ :ge @@ -69,26 +75,30 @@ class B1(x) @bufferable class B2(x) //│ ╔══[COMPILATION ERROR] Only one of bufferable annotation is allowed. -//│ ║ l.68: @buffered +//│ ║ l.74: @buffered //│ ║ ^^^^^^^^^ -//│ ║ l.69: @bufferable +//│ ║ l.75: @bufferable //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.70: class B2(x) +//│ ║ l.74: @buffered +//│ ║ ^^^^^^^^^ +//│ ║ l.75: @bufferable +//│ ║ ^^^^^^^^^^^ +//│ ║ l.76: class B2(x) //│ ╙── ^^^^^^^^^^^ :w @bufferable module B3 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.81: @bufferable +//│ ║ l.91: @bufferable //│ ╙── ^^^^^^^^^^^ :w @bufferable object B3 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.88: @bufferable +//│ ║ l.98: @bufferable //│ ╙── ^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls index 19f9088941..236cdf37fb 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/UnsupportedShapes.mls @@ -251,7 +251,7 @@ fun arityGo(x) = if x is @compile ArityGo(r) then r else "" //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Class definition 'Pair' expected 2 pattern arguments, but got 1 //│ ║ l.14: data class Pair(val first, val second) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Class `Pair` has 2 parameters. //│ ║ l.14: data class Pair(val first, val second) //│ ║ ^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls index 0a86a89fb7..c0dccc581e 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls @@ -10,7 +10,7 @@ pattern LessArity = Pair("meow") //│ ║ ^^^^^^^^^^^^ //│ ╟── Class definition 'Pair' expected 2 pattern arguments, but got 1 //│ ║ l.4: data class Pair[A, B](val first: A, val second: B) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :e pattern MoreArity = Pair("meow", "woof", "moo") @@ -19,4 +19,32 @@ pattern MoreArity = Pair("meow", "woof", "moo") //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class definition 'Pair' expected 2 pattern arguments, but got 3 //│ ║ l.4: data class Pair[A, B](val first: A, val second: B) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// Nested modifiers and annotations belong to the whole declaration span. +open annotations +@untyped +public data class AnnotatedPair(val first, val second) + +:e +pattern WrongAnnotatedArity = AnnotatedPair("one") +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.30: pattern WrongAnnotatedArity = AnnotatedPair("one") +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class definition 'AnnotatedPair' expected 2 pattern arguments, but got 1 +//│ ║ l.26: @untyped +//│ ║ ^^^^^^^^ +//│ ║ l.27: public data class AnnotatedPair(val first, val second) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// A following declaration must not inherit the preceding annotation's span. +data class NextPair(val first, val second) + +:e +pattern WrongNextArity = NextPair("one") +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.44: pattern WrongNextArity = NextPair("one") +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Class definition 'NextPair' expected 2 pattern arguments, but got 1 +//│ ║ l.41: data class NextPair(val first, val second) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 4f483e6bc2d58282962553d91289018236e9d53b Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 30 Sep 2026 14:10:36 +0800 Subject: [PATCH 148/228] Construct folded literals with the original operation location --- .../src/main/scala/hkmc2/codegen/Block.scala | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index d9c5fe174c..a4c9bbc8d5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -1218,9 +1218,9 @@ object Call: case Value.SimpleRef(sym: BuiltinSymbol) => argss match case (Arg(N, arg1: Value) :: Arg(N, arg2: Value) :: Nil) :: Nil => - evalBuiltin(sym, arg1, arg2)(value => return value.withLoc(toLoc)) + evalBuiltin(sym, arg1, arg2)(toLoc)(return _) case (Arg(N, arg1: Value) :: Nil) :: Nil => - evalBuiltin(sym, arg1)(value => return value.withLoc(toLoc)) + evalBuiltin(sym, arg1)(toLoc)(return _) case _ => case _ => raw(fun, argss)(metadata, toLoc) @@ -1232,29 +1232,29 @@ object Call: import Value.Lit - private inline def evalBuiltin(sym: BuiltinSymbol, arg1: Value, arg2: Value)(inline k: Value => Unit): Unit = + private inline def evalBuiltin(sym: BuiltinSymbol, arg1: Value, arg2: Value)(toLoc: Opt[Loc])(inline k: Value => Unit): Unit = (sym.nme, arg1, arg2) match - case ("+", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 + v2))(N)) - case ("+", Lit(Tree.StrLit(v1)), Lit(Tree.StrLit(v2))) => k(Lit(Tree.StrLit(v1 + v2))(N)) - case ("-", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 - v2))(N)) - case ("*", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 * v2))(N)) + case ("+", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 + v2))(toLoc)) + case ("+", Lit(Tree.StrLit(v1)), Lit(Tree.StrLit(v2))) => k(Lit(Tree.StrLit(v1 + v2))(toLoc)) + case ("-", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 - v2))(toLoc)) + case ("*", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.IntLit(v1 * v2))(toLoc)) // * For "/", should check for 0 and return a DecLit. - case ("%", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) if v2 =/= 0 => k(Lit(Tree.IntLit(v1 % v2))(N)) - case ("===", Lit(l1), Lit(l2)) => k(Lit(Tree.BoolLit(l1 == l2))(N)) - case ("!==", Lit(l1), Lit(l2)) => k(Lit(Tree.BoolLit(l1 != l2))(N)) - case ("<", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 < v2))(N)) - case ("<=", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 <= v2))(N)) - case (">", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 > v2))(N)) - case (">=", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 >= v2))(N)) - case ("&&", Lit(Tree.BoolLit(v1)), Lit(Tree.BoolLit(v2))) => k(Lit(Tree.BoolLit(v1 && v2))(N)) - case ("||", Lit(Tree.BoolLit(v1)), Lit(Tree.BoolLit(v2))) => k(Lit(Tree.BoolLit(v1 || v2))(N)) + case ("%", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) if v2 =/= 0 => k(Lit(Tree.IntLit(v1 % v2))(toLoc)) + case ("===", Lit(l1), Lit(l2)) => k(Lit(Tree.BoolLit(l1 == l2))(toLoc)) + case ("!==", Lit(l1), Lit(l2)) => k(Lit(Tree.BoolLit(l1 != l2))(toLoc)) + case ("<", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 < v2))(toLoc)) + case ("<=", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 <= v2))(toLoc)) + case (">", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 > v2))(toLoc)) + case (">=", Lit(Tree.IntLit(v1)), Lit(Tree.IntLit(v2))) => k(Lit(Tree.BoolLit(v1 >= v2))(toLoc)) + case ("&&", Lit(Tree.BoolLit(v1)), Lit(Tree.BoolLit(v2))) => k(Lit(Tree.BoolLit(v1 && v2))(toLoc)) + case ("||", Lit(Tree.BoolLit(v1)), Lit(Tree.BoolLit(v2))) => k(Lit(Tree.BoolLit(v1 || v2))(toLoc)) case _ => - private inline def evalBuiltin(sym: BuiltinSymbol, arg1: Value)(inline k: Value => Unit): Unit = + private inline def evalBuiltin(sym: BuiltinSymbol, arg1: Value)(toLoc: Opt[Loc])(inline k: Value => Unit): Unit = (sym.nme, arg1) match - case ("+", Lit(Tree.IntLit(v1))) => k(Lit(Tree.IntLit(v1))(N)) - case ("-", Lit(Tree.IntLit(v1))) => k(Lit(Tree.IntLit(-v1))(N)) - case ("!", Lit(Tree.BoolLit(v))) => k(Lit(Tree.BoolLit(!v))(N)) + case ("+", Lit(Tree.IntLit(v1))) => k(Lit(Tree.IntLit(v1))(toLoc)) + case ("-", Lit(Tree.IntLit(v1))) => k(Lit(Tree.IntLit(-v1))(toLoc)) + case ("!", Lit(Tree.BoolLit(v))) => k(Lit(Tree.BoolLit(!v))(toLoc)) case _ => end Call From 68b6734b5b3f44c4c93049937f8a4e8b1e00a38e Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 30 Sep 2026 19:55:58 +0800 Subject: [PATCH 149/228] Fix nominal parent scope transport and document Lexer migration blockers --- doc/new-resolution-suite-migration.md | 32 ++++++- doc/new-resolution-type-value-flow.md | 7 ++ .../scala/hkmc2/semantics/NewResolver.scala | 78 +++++++++++----- .../mlscript-compile/apps/parsing/Lexer.mls | 2 +- .../newres/InheritedTypeArguments.mls | 88 +++++++++++++++++++ .../test/mlscript/newres/LexerMigration.mls | 20 +++++ 6 files changed, 202 insertions(+), 25 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/LexerMigration.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 550aceb74b..fc51880f31 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -28,11 +28,11 @@ imply that its dependencies have been migrated. | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | -| Main (including quotes, UPS, and regression fixtures) | 26 | 22 | 48 | +| Main (including quotes, UPS, and regression fixtures) | 27 | 21 | 48 | | Applications | 8 | 12 | 20 | | Nofib | 12 | 27 | 39 | | WASM | 0 | 1 | 1 | -| Total | 46 | 62 | 108 | +| Total | 47 | 61 | 108 | These totals include the new-resolution `NamedFieldLibrary` regression fixture. @@ -78,7 +78,7 @@ The remaining option consumers have these blockers when compiled with | `Iter`, `MutMap`, `ups/EvaluationContext` | Missing public parameter interfaces and unresolved member selections. | | `FingerTreeList` | Compilation exceeds the 25-second test limit. Tuple literals with several spreads, such as `concatMiddle`'s `[...ay1, ...middle, ...ax2]`, produce one candidate per combination of operand candidates, and each is matched again by `toNodes`. Candidate sets compare shapes structurally, which rehashes these deep shapes; identity-based candidate storage is the next step. | | `parsing/Extension`, `ParseRule`, `Test` | Selections on values imported from legacy-resolution modules, such as `Parser.tracer`, have no resolved target. `TreeHelpers` compiles standalone; its consumers remain to be checked. | -| `parsing/Lexer` | Opened binary `~` conflicts with the builtin; calls with trailing contextual parameters leave function values where tokens are expected. | +| `parsing/Lexer` | Calls with trailing contextual parameters leave function values where tokens are expected. The binary `~` is explicitly imported, as required to override the builtin under new open semantics. | | `parsing/Parser` | Pattern-field flow and unresolved nominal members. | | `parsing/ParseRuleVisualizer`, `Rules`, `parsing-web-demo/main` | Missing host/public interfaces and unresolved selections. | | `parsing/Tree` | `JSON.stringify` has no result interface, so selecting `slice` on its result fails. | @@ -87,6 +87,30 @@ To reproduce a blocker, temporarily add the language directive to the named compilation fixture and run `ctest ` or `catest ` as appropriate. The blocked fixtures retain their existing resolution mode. +The 2026-09-30 `Lexer` investigation fixed an assertion encountered before its +listed blockers: ancestor constraints applied a nominal value's caller path to +its parent annotation without first leaving the subclass's instance scope. +The trigger is passing `Lexer.string`'s `[Int, Token.Literal]` result to the +helper expecting `[Int, Token.Token]`. +Parent type arguments now enter that scope before substitution, and ancestor +constraints and inherited member lookup share the corresponding exit. Only +parameters used by the parent are captured, including when recursively observing +a legacy imported type. `newres/InheritedTypeArguments.mls` covers qualified +subclass annotations, tuple transport, multiple parent steps, callback inputs, +and caller separation. A local module's captured outer binder still has a +separate scope failure recorded there with `:fixme`. + +All 61 legacy compilation fixtures were probed individually with their dependencies +in their existing modes. No fixture was newly unblocked by this correction. +`CachedHash` and `parsing/TreeHelpers` compile standalone in new resolution both +before and after the correction; their consumer migrations remain unchecked. +`FingerTreeList` still reaches the test timeout. `Lexer` now explicitly imports +`~`, as intended by the new open semantics, and reaches the contextual-argument +blocker instead of crashing. `newres/LexerMigration.mls` covers the explicit import +and records missing argument insertion. Completing the migration requires contextual +instance lookup and argument insertion in the new resolver; passing every instance +explicitly would bypass that missing feature rather than complete it. + Many fixtures also need source interfaces: for example, `QuoteExample.bind` calls an unannotated callback, and Nofib helpers expose comparators and printers. Check each fixture's public API and its consumers when porting it; the source @@ -157,7 +181,7 @@ recursive UPS matchers. Preserve both runtime results and matcher structure. - Implement automatic contextual argument insertion for `Lexer` calls to `Token.integer`, `symbol`, and related APIs with trailing `using` lists. - Resolve the opened binary `~`/builtin-operator conflict without removing those APIs. + Operators that override builtins must be imported explicitly, as in `Lexer`. - Complete type-argument validation (`basics/GenericClasses`), constructor-value member lookup (`codegen/ParamClasses`, `basics/DynamicInstantiation`), and module/call checks. Decide nominal module-forwarding compatibility for diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 65a143ac25..020cfb80d5 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -131,6 +131,13 @@ marks before installing both variance directions. This applies to constructed receivers and declared nominal views, including multiple inheritance steps and captured enclosing binders (`InheritedTypeArguments.mls`). +A nominal annotation's parent is interpreted inside the subclass instance scope, +whereas the annotated value and its supplied type arguments are outside it. +Capture the arguments used by the parent before substitution, and leave the +subclass scope before applying the value's caller path. `nominalParent` shares +that exit between ancestor constraints and inherited member lookup. Constructed +receivers already carry the exit in their constructor context. + ### Substitute at the occurrence before applying argument variance First interpret an argument expression at its lexical occurrence polarity. A diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 23d42ca2a2..cee0380996 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -452,23 +452,30 @@ class NewResolver: * callbacks and their hosts remain private to each consuming inference graph. */ private def withCanonicalType(tpe: DeclaredType)(listener: ShapeListener[DeclaredType])(using NewResolverState): Unit = - def publish(binders: Set[VarSymbol])(using NewResolverState): Unit = + withTypeDependencies(tpe.resolution): binders => if regularType(tpe.resolution) then listener(declaredType(tpe.resolution, tpe.bindings.filter((symbol, _) => binders(symbol)), tpe.positive) .instantiate(tpe.instances).withOrigin(tpe.origin)) - typeDependencies(tpe.resolution) match - case R(binders) => publish(binders) + + /** Discover binders without normalizing the type. Parent annotations must + * receive their bindings at the original lexical endpoint: normalization + * can move captures into references with their own closed environments. + */ + private def withTypeDependencies(resolution: TypeResolution)(listener: ShapeListener[Set[VarSymbol]]) + (using NewResolverState): Unit = + typeDependencies(resolution) match + case R(binders) => listener(binders) case L(_) => - rstate.pendingTypeDependencies.get(tpe.resolution) match - case S(host) => host.listen(publish) + rstate.pendingTypeDependencies.get(resolution) match + case S(host) => host.listen(listener) case N => val host = new TypeDependencyHost - rstate.pendingTypeDependencies(tpe.resolution) = host - host.listen(publish) - def retry()(using NewResolverState): Unit = typeDependencies(tpe.resolution) match + rstate.pendingTypeDependencies(resolution) = host + host.listen(listener) + def retry()(using NewResolverState): Unit = typeDependencies(resolution) match case R(binders) => host.publish(binders) case L(pending) => pending.foreach: unresolved => - if rstate.dependencySubscriptions.add((tpe.resolution, unresolved)) then + if rstate.dependencySubscriptions.add((resolution, unresolved)) then unresolved.listen(_ => retry()) retry() @@ -912,8 +919,14 @@ class NewResolver: case S(parent) => // Only the parent's declared type is relevant here. Evaluating // its constructor arguments would reintroduce implementation flow. - listenTypeViews(declaredType(typeResolution(parent.cls), bindings)): ext => - publish(NominalInstanceView(defn, bindings, S(ext))(S(tpe.resolution.source))(this)) + // Restrict substitution before capturing: a recursive field + // can supply an argument already inside this class. A parent + // that does not use it must not attempt that scope crossing. + val parentType = typeResolution(parent.cls) + withTypeDependencies(parentType): dependencies => + val local = captureNominalBindings(defn, bindings.filter((symbol, _) => dependencies(symbol))) + listenTypeViews(declaredType(parentType, local)): ext => + publish(NominalInstanceView(defn, bindings, S(ext))(S(tpe.resolution.source))(this)) case TypeShape.Alias(symbol, rhs) => // Revisiting the same alias reference without reaching a structural // shape supplies no additional interface. Track @@ -1328,7 +1341,7 @@ class NewResolver: case nominal: NominalInstanceView => if nominal.defn is cls then cls.tparams.zip(arguments).foreach: (param, pattern) => nominal.bindings.get(param.sym).foreach(constrain(param, _, pattern)) - else nominal.parent.foreach(parent) + else nominalParent(nominal).foreach(parent) case tuple: TupleShape => parent(tuple.arrayParent) case _ => () constrainNominal(value) @@ -1543,12 +1556,12 @@ class NewResolver: // Supplied arguments are outside the class; its parameter annotations // are inside. Enclosing binders keep their own lexical contexts and // enter the class through the annotation's explicit Capture nodes. - val parameters = defn.tparams.map(_.sym).toSet - val local = view.bindings.map: (symbol, bound) => - val transported = provenance match + val transported = view.bindings.map: (symbol, bound) => + val argument = provenance match case NoMarks => bound case provenance: ExitMark => transportType(bound, provenance :: Nil) - symbol -> (if parameters(symbol) && scope.nonEmpty then captureType(transported, defn.sym) else transported) + symbol -> argument + val local = captureNominalBindings(defn, transported) MemberLookup.Declared(member, local, scope.toList.map(ExitMark(_, N, NoMarks)), view.annotation, true) .withMarks(location match case NoMarks => Nil @@ -1563,16 +1576,41 @@ class NewResolver: // annotations: `class Int extends Num` captures `Num` into Int's // scope. An inherited member exits that scope before the receiver // path applies, just like an own member. - val exit = scope.fold[Marks](NoMarks)(ExitMark(_, N, NoMarks)) - val inherited = view.parent.fold[MemberLookup](MemberLookup.Missing): parent => + val inherited = nominalParent(view).fold[MemberLookup](MemberLookup.Missing): parent => val Marked(parentView, inner) = parent - InstanceShape(extremeType(false)).exit(inner).exit(exit).exit(receiver) match + InstanceShape(extremeType(false)).exit(inner).exit(receiver) match case Marked(_, path) => parentView.getMemberThrough(name, path) // The parent interface is not reachable through this receiver; // applying these marks to its members yields no candidates. - case NoShape => parentView.getMember(name).withMarks(inner :: exit :: receiver :: Nil) + case NoShape => parentView.getMember(name).withMarks(inner :: receiver :: Nil) inherited.withAnnotation(view.annotation) + /** Supplied class arguments are outside the instance scope. Both member and + * parent annotations refer to those parameters from inside that scope. + * Enclosing binders instead enter through their own explicit Capture nodes. + */ + private def captureNominalBindings(defn: ClassLikeDef, bindings: Map[VarSymbol, DeclaredType]) + (using NewResolverState): Map[VarSymbol, DeclaredType] = + val parameters = defn.tparams.map(_.sym).toSet + bindings.map: (symbol, bound) => + symbol -> (if parameters(symbol) && !defn.sym.isInstanceOf[ModuleOrObjectSymbol] + then captureType(bound, defn.sym) else bound) + + /** A nominal value lives outside its class's instance scope, but its parent + * annotation is resolved inside that scope. Leave it before applying the + * value's caller path, both for inherited members and ancestor constraints. + * Constructed shapes already carry this exit in their constructor context; + * adding it to those shapes would cross the same boundary twice. + */ + private def nominalParent(view: NominalInstanceView)(using NewResolverState): Opt[TermShape] = + view.parent.flatMap: parent => + val exited = view.defn.sym match + case _: ModuleOrObjectSymbol => parent + case symbol => transportShape(parent, ExitMark(ResolutionBoundary(symbol), N, NoMarks) :: Nil) + exited match + case parent: TermShape => S(parent) + case NoShape => N + /** Split a receiver path into the path from `defn`'s definition to the consumer, * and the innermost exits of scopes not enclosing that definition. Exits are * applied innermost first, so once a path leaves an enclosing scope of `defn`, diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls index e4c2621aca..b4d32a77ea 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls @@ -12,7 +12,7 @@ import "../../Iter.mls" import "./Token.mls" open Stack -open StrOps +open StrOps { concat2, ~ } open Option { Some, None} open Token { LiteralKind, LineLookupTable } diff --git a/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls b/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls index 66ab4b7d3d..8eb4d0c8a0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls @@ -67,3 +67,91 @@ private fun make[T](item: T) = read(new Child) [make(First(8)).first, make(Second(9)).second] //│ = [8, 9] + +// Qualified subclass annotations retain the parent's capture into the class, +// even when the annotated value travels through a tuple and nested calls. +:expect 3 +module Tokens with + class Parent with + fun value: Int = 3 + class Child() extends Parent +private fun run() = + fun make(): [Int, Tokens.Child] = [0, Tokens.Child()] + fun read(pair: [Int, Tokens.Parent]) = pair.1.value + read(make()) +run() +//│ = 3 + +// Parent type arguments must survive more than one ancestry step and keep +// separate calls distinct. +:expect [4, 5] +module Types with + class First(val first: Int) + class Second(val second: Int) + class Parent[A](val item: A) + class Middle[A](item: A) extends Parent[A](item) + class Child[A](item: A) extends Middle[A](item) +private fun make[A](item: A): Types.Child[A] = new Types.Child[A](item) +private fun read[A](parent: Types.Parent[A]) = parent.item +[read(make(Types.First(4))).first, read(make(Types.Second(5))).second] +//│ = [4, 5] + +// A qualified parent under an enclosing generic activation retains the outer +// binder, rather than treating it as one of the child's own type parameters. +// FIXME: Reading the outer binder through the local module still loses the +// intervening read scope after the parent constraint has been transported. +:fixme +:expect [6, 7] +class First(val first: Int) +class Second(val second: Int) +private fun make[A](item: A) = + module Types with + class Parent(val item: A) + class Child(item: A) extends Parent(item) + fun child(): Types.Child = new Types.Child(item) + fun read(parent: Types.Parent) = parent.item + read(child()) +[make(First(6)).first, make(Second(7)).second] +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: term:make⁰ vs term:read⁰ for ContextualShape(AppShape(MarkedShape(member:First⁰, ↗⟨class:First¹⟩%⟨First²⟩ϵ), [6])) + +// Constraints on a callable parent argument must also propagate its input. +:expect 8 +module Types with + class Item(val value: Int) + class Parent[A](val run: A -> Int) + class Child[A](run: A -> Int) extends Parent[A](run) +private fun make[A](run: A -> Int): Types.Child[A] = new Types.Child[A](run) +private fun call[A](parent: Types.Parent[A], item: A) = parent.run(item) +call(make(x => x.value), Types.Item(8)) +//│ = 8 + +// Distinct annotated subclass calls must not mix their ancestor arguments. +:e +module Types with + class First(val first: Int) + class Second(val second: Int) + class Parent[A](val item: A) + class Child[A](item: A) extends Parent[A](item) +private fun make[A](item: A): Types.Child[A] = new Types.Child[A](item) +private fun read[A](parent: Types.Parent[A]) = parent.item +read(make(Types.Second(9))).second +read(make(Types.First(8))).second +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.138: read(make(Types.First(8))).second +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.138: read(make(Types.First(8))).second +//│ ╙── ^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// The imported recursive Stack type supplies an argument to Cons, but Cons's +// Base parent does not use it. Interpreting Base must not capture that argument +// again while a nested pattern is already observing it inside Cons. +:expect 2 +import "../../mlscript-compile/Stack.mls" +open Stack +private fun second(xs: Stack[Int]) = if xs is + Cons(_, Cons(value, _)) then value + else 0 +second(Cons(1, Cons(2, Nil))) +//│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/newres/LexerMigration.mls b/hkmc2/shared/src/test/mlscript/newres/LexerMigration.mls new file mode 100644 index 0000000000..2aaca2631b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/LexerMigration.mls @@ -0,0 +1,20 @@ +:. + +// Explicit imports override the builtin unary ~ under the new open semantics. +:expect "ab" +module Strings with + fun (~) concat(left: Str, right: Str): Str = left + right +open Strings { concat, ~ } +"a" ~ "b" +//│ = "ab" + +// Lexer supplies a LineLookupTable instance, but new resolution does not yet +// insert the trailing contextual argument at calls to Token's factory methods. +:fixme +:expect 5 +class Context(val value: Int) +private fun make(value: Int)(using context: Context): Int = value + context.value +using Context = Context(2) +make(3) +//│ ═══[RUNTIME ERROR] Expected: '5', got: 'fun' +//│ = fun From f6842d801cf2f41fd426f82c3a40b6760756df89 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Wed, 30 Sep 2026 20:44:32 +0800 Subject: [PATCH 150/228] Port two more compilation tests --- hkmc2/shared/src/test/mlscript-compile/CachedHash.mls | 2 ++ .../src/test/mlscript-compile/apps/parsing/TreeHelpers.mls | 2 ++ 2 files changed, 4 insertions(+) diff --git a/hkmc2/shared/src/test/mlscript-compile/CachedHash.mls b/hkmc2/shared/src/test/mlscript-compile/CachedHash.mls index e218273866..be36ef4123 100644 --- a/hkmc2/shared/src/test/mlscript-compile/CachedHash.mls +++ b/hkmc2/shared/src/test/mlscript-compile/CachedHash.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + class CachedHash with let _hash = null fun hash() = if _hash !== null then _hash else diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls index c2ec694112..a5eaaad969 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/TreeHelpers.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "../../Option.mls" import "../../Predef.mls" import "../../Stack.mls" From 0ac2d45de86c2dc5ed8c22a2fe729a48cc9499e7 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 29 Sep 2026 21:23:47 +0800 Subject: [PATCH 151/228] Add disclaimers for slopdoc --- doc/new-resolution-design.md | 5 +++++ doc/new-resolution-future-work.md | 5 +++++ doc/new-resolution-regular-types.md | 5 +++++ doc/new-resolution-suite-migration.md | 5 +++++ doc/new-resolution-type-value-flow.md | 5 +++++ doc/new-resolution-wasm.md | 5 +++++ 6 files changed, 30 insertions(+) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 94a2cf1786..462dc5a4c2 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -1,3 +1,8 @@ +NOTE: This document was written by Codex Astra and has not been deeply reviewed; +it is not meant to be official documentation and +is in fact likely to contain parts that are unintelligible to readers who lack sufficient context. + + # New-resolution implementation invariants This note describes the resolver's implementation contracts. User-facing rules diff --git a/doc/new-resolution-future-work.md b/doc/new-resolution-future-work.md index 27017a6e26..4e725f9ca8 100644 --- a/doc/new-resolution-future-work.md +++ b/doc/new-resolution-future-work.md @@ -1,3 +1,8 @@ +NOTE: This document was written by Codex Astra and has not been deeply reviewed; +it is not meant to be official documentation and +is in fact likely to contain parts that are unintelligible to readers who lack sufficient context. + + # Deferred resolution design work This reference records confirmed implementation gaps and deferred design work. diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index d312b0ee27..d70932ecc2 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -1,3 +1,8 @@ +NOTE: This document was written by Codex Astra and has not been deeply reviewed; +it is not meant to be official documentation and +is in fact likely to contain parts that are unintelligible to readers who lack sufficient context. + + # Regular structural types and canonical references This is an internal reference for structural recursion in new resolution. The diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index fc51880f31..8888be0871 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -1,3 +1,8 @@ +NOTE: This document was written by Codex Astra and has not been deeply reviewed; +it is not meant to be official documentation and +is in fact likely to contain parts that are unintelligible to readers who lack sufficient context. + + # New-resolution suite migration ## Scope and status diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 020cfb80d5..4b3b32935d 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -1,3 +1,8 @@ +NOTE: This document was written by Codex Astra and has not been deeply reviewed; +it is not meant to be official documentation and +is in fact likely to contain parts that are unintelligible to readers who lack sufficient context. + + # Instance types and parameter constraints This internal reference describes the current type-flow representation in new diff --git a/doc/new-resolution-wasm.md b/doc/new-resolution-wasm.md index 8baecd4dc0..e0f38e2901 100644 --- a/doc/new-resolution-wasm.md +++ b/doc/new-resolution-wasm.md @@ -1,3 +1,8 @@ +NOTE: This document was written by Codex Astra and has not been deeply reviewed; +it is not meant to be official documentation and +is in fact likely to contain parts that are unintelligible to readers who lack sufficient context. + + # New resolution and WASM The [migration worklist](new-resolution-suite-migration.md) tracks remaining ports. From 074b67893d42c5d4ba1ef5b608ae71e9eb1e8cd2 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Wed, 30 Sep 2026 20:43:13 +0800 Subject: [PATCH 152/228] Minor --- doc/new-resolution-suite-migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 8888be0871..0b96888b33 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -81,7 +81,7 @@ The remaining option consumers have these blockers when compiled with | --- | --- | | `Block`, `Shape` | Missing nominal members and callback arity mismatches; `Block` also needs a public interface for `showArm`. | | `Iter`, `MutMap`, `ups/EvaluationContext` | Missing public parameter interfaces and unresolved member selections. | -| `FingerTreeList` | Compilation exceeds the 25-second test limit. Tuple literals with several spreads, such as `concatMiddle`'s `[...ay1, ...middle, ...ax2]`, produce one candidate per combination of operand candidates, and each is matched again by `toNodes`. Candidate sets compare shapes structurally, which rehashes these deep shapes; identity-based candidate storage is the next step. | +| `FingerTreeList` | Compilation exceeds the 25-second test limit. Tuple literals with several spreads, such as `concatMiddle`'s `[...ay1, ...middle, ...ax2]`, produce one candidate per combination of operand candidates, and each is matched again by `toNodes`. Candidate sets compare shapes structurally, which rehashes these deep shapes; identity-based candidate storage is the next step. Already fixed on branch `LPTK/new-resolution-no-hashing`. | | `parsing/Extension`, `ParseRule`, `Test` | Selections on values imported from legacy-resolution modules, such as `Parser.tracer`, have no resolved target. `TreeHelpers` compiles standalone; its consumers remain to be checked. | | `parsing/Lexer` | Calls with trailing contextual parameters leave function values where tokens are expected. The binary `~` is explicitly imported, as required to override the builtin under new open semantics. | | `parsing/Parser` | Pattern-field flow and unresolved nominal members. | From 31f24f7d9e0185e84051a76a15bb13916b3261f4 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Wed, 30 Sep 2026 21:14:57 +0800 Subject: [PATCH 153/228] WIP --- .../src/test/mlscript-compile/MutMap.mls | 20 ++++++++++--------- hkmc2/shared/src/test/mlscript/HkScratch.mls | 5 +++++ .../src/test/mlscript/decls/Prelude.mls | 9 +++++++-- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls index 34ebbbf402..a5c869c088 100644 --- a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls +++ b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./Option.mls" import "./Predef.mls" @@ -7,36 +9,36 @@ open Option { Some, None } module MutMap with ... -class MutMap(val underlying) with +class MutMap[A, B](val underlying: Map[A, B]) with set this.(Symbol.iterator) = () => underlying.(Symbol.iterator)() -fun get(key)(m) = +fun get(key)(m: MutMap) = if m.underlying.has(key) then Some of m.underlying.get(key) else None -fun insert(key, value)(m) = +fun insert(key, value)(m: MutMap) = m.underlying.set(key, value) () -fun delete(key)(m) = +fun delete(key)(m: MutMap) = m.underlying.delete(key) () -fun updateWith(key)(op)(m) = +fun updateWith(key)(op: Function)(m: MutMap) = if op(m |> get(key)) is Some(value) then m.underlying.set(key, value) None then m.underlying.delete(key) -fun keysIterator(m) = m.underlying.keys() +fun keysIterator(m: MutMap) = m.underlying.keys() -fun valuesIterator(m) = m.underlying.values() +fun valuesIterator(m: MutMap) = m.underlying.values() -fun values(m) = Array.from of m.underlying.values() +fun values(m: MutMap) = Array.from of m.underlying.values() -fun toMap(entries) = +fun toMap(entries: Array) = let m = empty i = 0 diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index f710778f1d..7c9a50c883 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -186,3 +186,8 @@ class Bar with //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +fun id"lollol" = 1 + +id"lollol" +//│ = 1 + diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index e4d7ca53bb..8e8e25fc58 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -61,7 +61,12 @@ declare class Map[K, V] with fun has(key: K): Bool fun delete(key: K): Bool fun clear(): undefined - // `set` needs support for keyword-named declarations, like Reflect.set below. + fun id"set"(key: K, value: V): Map[K, V] + fun keys(): Iterator[K] + fun values(): Iterator[V] + fun entries(): Iterator[[K, V]] +declare class Iterator[T] with + fun next(): { value: T, done: Bool } declare module Map declare class WeakSet[V] declare module WeakSet @@ -248,7 +253,7 @@ declare module Math with declare module Reflect with fun get - // set // TODO: handle keyword-named members + id"set" has ownKeys getPrototypeOf From c5aeaeed886324cb4e88f2aa449c4272265e3bec Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 30 Sep 2026 21:26:29 +0800 Subject: [PATCH 154/228] Fix calls through builtin Function annotations Recognize the builtin Function nominal interface as callable while keeping its result unknown. Add resolver and MutMap updateWith regression tests. Disambiguate library Iterator constructors from the new prelude declaration and refresh golden diagnostic locations. --- .../scala/hkmc2/semantics/NewResolver.scala | 8 ++ .../test/mlscript-compile/FingerTreeList.mls | 2 +- .../src/test/mlscript-compile/LazyArray.mls | 4 +- .../test/mlscript-compile/LazyFingerTree.mls | 2 +- .../src/test/mlscript/newres/Arrays.mls | 24 +++--- .../test/mlscript/newres/BuiltinFunction.mls | 76 +++++++++++++++++++ .../test/mlscript/newres/GenericMethods.mls | 2 +- .../src/test/mlscript/std/MutMapTest.mls | 35 +++++++++ .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 9 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls create mode 100644 hkmc2/shared/src/test/mlscript/std/MutMapTest.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index cee0380996..9820f5d9be 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2132,6 +2132,14 @@ class NewResolver: return case _ => () lhs match + case Marked(nominal: NominalInstanceView, _) if nominal.isInstanceOfClass(prelude.builtins.Function.defn.get) => + // The builtin Function class guarantees callability, including through + // aliases and subclasses, but supplies neither an arity nor a result + // interface. Check the argument tuple and leave arity checks to runtime; + // the result must remain unknown rather than acquiring dynamic access. + checkArgumentArity(args, 0, true, res, lhs)(_ => ()) + publish(UnknownValueShape.at(res)) + return case Marked(original: CallableTypeShape, context) => val viewed = instantiateShape(original, instances) match case callable: CallableTypeShape => callable diff --git a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls index b9c84587af..3ac92951be 100644 --- a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls +++ b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls @@ -32,7 +32,7 @@ data class View[T](e1: T, rest: FingerTree[T]) class FingerTree[T] extends IterableBase with fun iterator() = let current = this - Iterator of () => if popFront(current) is + Iter.Iterator of () => if popFront(current) is Nil then Result.Done View(head, tail) then set current = tail diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls index d7ac56fe3d..bd5a2910b9 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls @@ -63,7 +63,7 @@ class View(val underlying, val start, val end) extends LazyArr with fun iterator() = let idx = start let until = underlying.length - end - Iterator of () => + Iter.Iterator of () => if idx < until then let next = underlying.at(idx) set idx = idx + 1 @@ -108,7 +108,7 @@ class Splice(val bits, val len) extends LazyArr with set idx += 1 Result.Next of value else Result.Done - Iterator of nextf + Iter.Iterator of nextf fun reify = let counter = 0 diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls index daca2e5c83..79e612ff63 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls @@ -60,7 +60,7 @@ class View(val underlying, val start, val end) extends LazyFT with fun iterator() = let idx = start let until = underlying.length - end - Iterator of () => + Iter.Iterator of () => if idx < until then let next = underlying.at(idx) set idx = idx + 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 5ed8069c1c..4588c1781b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.162: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.167: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -141,15 +141,15 @@ new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.149: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.154: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.150: fun join(...separators): Str +//│ ║ l.155: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.151: fun reverse(): Array[T] +//│ ║ l.156: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.152: fun sort(...compare): Array[T] +//│ ║ l.157: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.153: ... (more lines omitted) ... +//│ ║ l.158: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2] @@ -160,15 +160,15 @@ new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.149: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.154: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.150: fun join(...separators): Str +//│ ║ l.155: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.151: fun reverse(): Array[T] +//│ ║ l.156: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.152: fun sort(...compare): Array[T] +//│ ║ l.157: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.153: ... (more lines omitted) ... +//│ ║ l.158: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.170: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.175: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls new file mode 100644 index 0000000000..00f55e3518 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls @@ -0,0 +1,76 @@ +:. + +// Function annotations expose callability without requiring an observed call. +fun unused(f: Function) = f(1) + +// Arity is unknown: zero, multiple, and spread arguments are all permitted. +:expect 42 +fun call0(f: Function) = f() +fun call2(f: Function) = f(20, 22) +fun spread(f: Function, args: Array[Int]) = f(...args) +call0(() => call2((x, y) => spread((a, b) => a + b, [x, y]))) +//│ = 42 + +// Aliases, stored values, and nested scopes retain builtin identity. +:expect 42 +type Callback = Function +class Holder(val callback: Callback) +fun nested(holder: Holder) = + fun inner() = holder.callback(41) + inner() +nested(Holder((x) => x + 1)) +//│ = 42 + +// A nominal subclass retains the callability of Function. +class CallbackClass extends Function +fun inherited(f: CallbackClass) = f(1) + +// A call does not expose the concrete callback's result members. +:ge +class Item(val value: Int) +fun read(f: Function) = f().value +read(() => Item(42)) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.31: fun read(f: Function) = f().value +//│ ║ ^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── The shape of this value is unknown. +//│ ║ l.31: fun read(f: Function) = f().value +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Nor does it authorize another call on the unknown result. +:ge +fun curried(f: Function) = f(1)(2) +curried((x) => (y) => x + y) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.44: fun curried(f: Function) = f(1)(2) +//│ ║ ^^^^^^^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.44: fun curried(f: Function) = f(1)(2) +//│ ║ ^^^^ +//│ ╟── The shape of this value is unknown. +//│ ║ l.44: fun curried(f: Function) = f(1)(2) +//│ ╙── ^^^^ +//│ = 3 + +// Knowing that a value is callable does not permit arbitrary member access. +:ge +fun missing(f: Function) = f.nonexistent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.59: fun missing(f: Function) = f.nonexistent +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Value of type 'Function' does not contain member 'nonexistent' +//│ ║ l.59: fun missing(f: Function) = f.nonexistent +//│ ╙── ^^^^^^^^ + +// A user-defined class named Function is not the builtin. +:ge +class Function +fun shadowed(f: Function) = f(1) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.70: fun shadowed(f: Function) = f(1) +//│ ║ ^^^^ +//│ ╟── Value of type 'Function' cannot be called like a function. +//│ ║ l.70: fun shadowed(f: Function) = f(1) +//│ ╙── ^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index f217966084..0aa5070220 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.162: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.167: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/std/MutMapTest.mls b/hkmc2/shared/src/test/mlscript/std/MutMapTest.mls new file mode 100644 index 0000000000..7bb835cc73 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/std/MutMapTest.mls @@ -0,0 +1,35 @@ +:js + +import "../../mlscript-compile/MutMap.mls" +import "../../mlscript-compile/Option.mls" + +open Option { Some, None } + +// updateWith receives None for a missing key and inserts a returned Some. +:expect Some(20) +val m = MutMap.empty +m |> MutMap.updateWith("key") of case + None then Some(20) + Some(value) then Some(value + 1) +m |> MutMap.get("key") +//│ = Some(20) +//│ m = MutMap(Map(1) {"key" => 20}) + +// An existing value is passed to the callback and replaced by its result. +:expect Some(42) +m |> MutMap.updateWith("key") of case + None then Some(0) + Some(value) then Some(value + 22) +m |> MutMap.get("key") +//│ = Some(42) + +// Returning None deletes an existing key and leaves an absent key absent. +:expect None +m |> MutMap.updateWith("key") of ((_) => None) +m |> MutMap.get("key") +//│ = None + +:expect None +m |> MutMap.updateWith("missing") of ((_) => None) +m |> MutMap.get("missing") +//│ = None diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 43ed423c2b..0d590cb32d 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.322: fun +//│ ║ l.327: fun //│ ║ ^^^ -//│ ║ l.323: const(c: Int): Int32 +//│ ║ l.328: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 89666cda970c7c68eefe3bfe65359f4e1993f979 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 30 Sep 2026 21:31:42 +0800 Subject: [PATCH 155/228] Rename Iter iterator implementation to IteratorImpl Update internal constructors and the LazyArray, FingerTreeList, and LazyFingerTree callers to distinguish the implementation from the prelude Iterator type. --- .../src/test/mlscript-compile/FingerTreeList.mls | 2 +- hkmc2/shared/src/test/mlscript-compile/Iter.mls | 10 +++++----- hkmc2/shared/src/test/mlscript-compile/LazyArray.mls | 4 ++-- .../src/test/mlscript-compile/LazyFingerTree.mls | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls index 3ac92951be..4143407ce6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls +++ b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls @@ -32,7 +32,7 @@ data class View[T](e1: T, rest: FingerTree[T]) class FingerTree[T] extends IterableBase with fun iterator() = let current = this - Iter.Iterator of () => if popFront(current) is + IteratorImpl of () => if popFront(current) is Nil then Result.Done View(head, tail) then set current = tail diff --git a/hkmc2/shared/src/test/mlscript-compile/Iter.mls b/hkmc2/shared/src/test/mlscript-compile/Iter.mls index 78eb2ca8fd..277cf104b0 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Iter.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Iter.mls @@ -76,7 +76,7 @@ class Iterable(mk) extends IterableBase with // * Implements the `Iterator` protocol. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol -class Iterator(val next) +class IteratorImpl(val next) module Result with class Next(val value) with @@ -100,7 +100,7 @@ fun getIterator(something) = fun adaptIterable(iterable, makeNext) = Iterable of () => let iterator = getIterator of iterable - Iterator of makeNext of iterator + IteratorImpl of makeNext of iterator fun mapping(xs, op) = xs adaptIterable of iterator => () => @@ -128,7 +128,7 @@ fun flattening(xss) = let nextResult = nextIterator.next() nextResult.done then skipEmptyIterables() else Some of [nextIterator, nextResult.value] - Iterator of () => + IteratorImpl of () => if currentIterator is None then Result.Done // No more `Iterator`s means we're done. Some(iterator) and @@ -185,7 +185,7 @@ fun appended(xs, ys) = Iterable of () => let xsIterator = getIterator of xs let currentIterator = xsIterator - Iterator of () => if + IteratorImpl of () => if let next = currentIterator.next() next.done and currentIterator == xsIterator and @@ -268,7 +268,7 @@ fun toArray(view) = Array.from(view) fun fromStack(stack) = Iterable of () => let current = stack - Iterator of () => if current is + IteratorImpl of () => if current is Cons(head, tail) then set current = tail Result.Next of head diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls index bd5a2910b9..eb4ae6cee2 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls @@ -63,7 +63,7 @@ class View(val underlying, val start, val end) extends LazyArr with fun iterator() = let idx = start let until = underlying.length - end - Iter.Iterator of () => + IteratorImpl of () => if idx < until then let next = underlying.at(idx) set idx = idx + 1 @@ -108,7 +108,7 @@ class Splice(val bits, val len) extends LazyArr with set idx += 1 Result.Next of value else Result.Done - Iter.Iterator of nextf + IteratorImpl of nextf fun reify = let counter = 0 diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls index 79e612ff63..32b73bfa5e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls @@ -60,7 +60,7 @@ class View(val underlying, val start, val end) extends LazyFT with fun iterator() = let idx = start let until = underlying.length - end - Iter.Iterator of () => + IteratorImpl of () => if idx < until then let next = underlying.at(idx) set idx = idx + 1 From fc1309c28995d2b11a996d74408f28d772e719d9 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 30 Sep 2026 21:41:59 +0800 Subject: [PATCH 156/228] Preserve Function annotations in call-result diagnostics Keep the unknown result keyed by its call expression while recording the restricting annotation and missing return signature in shared diagnostic provenance. Selections and applications now point to that annotation without duplicate result locations. Cover direct annotations, aliases, captured stored results, and inherited callability in golden diff tests. --- .../scala/hkmc2/semantics/NewResolver.scala | 6 +- .../test/mlscript/newres/BuiltinFunction.mls | 61 +++++++++++++++---- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 9820f5d9be..55b638df5b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2138,7 +2138,11 @@ class NewResolver: // interface. Check the argument tuple and leave arity checks to runtime; // the result must remain unknown rather than acquiring dynamic access. checkArgumentArity(args, 0, true, res, lhs)(_ => ()) - publish(UnknownValueShape.at(res)) + // The call identifies the result in the flow graph, while the annotation + // explains its missing interface. Preserve that distinction so subsequent + // selections and applications both diagnose the restricting type once. + val provenance = ShapeProvenance(msg"The 'Function' type does not specify a return type." -> N :: Nil) + publish(UnknownValueShape(res)(nominal.annotation.fold(provenance)(provenance.withTypeOrigin))) return case Marked(original: CallableTypeShape, context) => val viewed = instantiateShape(original, instances) match diff --git a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls index 00f55e3518..cd19baed56 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls @@ -34,9 +34,9 @@ read(() => Item(42)) //│ ║ l.31: fun read(f: Function) = f().value //│ ║ ^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ╟── The shape of this value is unknown. //│ ║ l.31: fun read(f: Function) = f().value -//│ ╙── ^^^ +//│ ║ ^^^^^^^^ +//│ ╙── The 'Function' type does not specify a return type. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Nor does it authorize another call on the unknown result. @@ -48,20 +48,57 @@ curried((x) => (y) => x + y) //│ ║ ^^^^^^^ //│ ╟── Value of unknown shape cannot be called like a function. //│ ║ l.44: fun curried(f: Function) = f(1)(2) -//│ ║ ^^^^ -//│ ╟── The shape of this value is unknown. -//│ ║ l.44: fun curried(f: Function) = f(1)(2) -//│ ╙── ^^^^ +//│ ║ ^^^^^^^^ +//│ ╙── The 'Function' type does not specify a return type. //│ = 3 +// The diagnostic points to the written alias annotation, not its definition or call. +:ge +type Callback = Function +fun aliased(f: Callback) = + val result = f() + result.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.60: result.value +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.58: fun aliased(f: Callback) = +//│ ║ ^^^^^^^^ +//│ ╙── The 'Function' type does not specify a return type. + +// Storing a call result and capturing it preserves the outer Function annotation. +:ge +fun captured(f: Function) = + val result = f(1) + fun inner() = result(2) + inner() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.73: fun inner() = result(2) +//│ ║ ^^^^^^^^^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.71: fun captured(f: Function) = +//│ ║ ^^^^^^^^ +//│ ╙── The 'Function' type does not specify a return type. + +// Inherited callability retains the subclass annotation as its diagnostic witness. +:ge +fun inheritedResult(f: CallbackClass) = f().value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.85: fun inheritedResult(f: CallbackClass) = f().value +//│ ║ ^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.85: fun inheritedResult(f: CallbackClass) = f().value +//│ ║ ^^^^^^^^^^^^^ +//│ ╙── The 'Function' type does not specify a return type. + // Knowing that a value is callable does not permit arbitrary member access. :ge fun missing(f: Function) = f.nonexistent //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.59: fun missing(f: Function) = f.nonexistent +//│ ║ l.96: fun missing(f: Function) = f.nonexistent //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Function' does not contain member 'nonexistent' -//│ ║ l.59: fun missing(f: Function) = f.nonexistent +//│ ║ l.96: fun missing(f: Function) = f.nonexistent //│ ╙── ^^^^^^^^ // A user-defined class named Function is not the builtin. @@ -69,8 +106,8 @@ fun missing(f: Function) = f.nonexistent class Function fun shadowed(f: Function) = f(1) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.70: fun shadowed(f: Function) = f(1) -//│ ║ ^^^^ +//│ ║ l.107: fun shadowed(f: Function) = f(1) +//│ ║ ^^^^ //│ ╟── Value of type 'Function' cannot be called like a function. -//│ ║ l.70: fun shadowed(f: Function) = f(1) -//│ ╙── ^^^^^^^^ +//│ ║ l.107: fun shadowed(f: Function) = f(1) +//│ ╙── ^^^^^^^^ From 3541d4faa4e2261f47fa4fe71a6f45fefa7c8d6a Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 30 Sep 2026 22:03:07 +0800 Subject: [PATCH 157/228] Check callbacks erased to Function before sealing resolution --- .../scala/hkmc2/semantics/Elaborator.scala | 3 + .../hkmc2/semantics/InterfaceExposure.scala | 20 ++- .../scala/hkmc2/semantics/NewResolver.scala | 13 ++ .../hkmc2/semantics/NewResolverState.scala | 14 ++ .../test/mlscript/newres/BuiltinFunction.mls | 125 +++++++++++++++++- .../mlscript/newres/loose/BuiltinFunction.mls | 24 ++++ 6 files changed, 188 insertions(+), 11 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/loose/BuiltinFunction.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 0344dda822..a0e4a5aa97 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -3002,6 +3002,9 @@ extends Importer: val values = res.stats.collect: case DefineVar(sym: LocalVarSymbol, rhs) => Term.SimpleRef(sym)(new Ident(sym.nme).withLocOf(rhs)) InterfaceExposure(this).check(exports, res.res :: values) + else + // Explicit annotations still check callbacks whose signatures they hide. + InterfaceExposure(this).check(Nil, Nil) rstate.completeBlock(res) (res, ctx) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index 34e502c883..bff048a2da 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -8,6 +8,8 @@ import hkmc2.Message.MessageContext import NewResolverState.Listener object InterfaceExposure: + final case class Input(shape: TermShape, state: NewResolverState)(val provenance: ShapeProvenance) + def isPublic(definition: Definition): Bool = !definition.annotations.contains(Annot.Private) && (definition match case td: TermDefinition => (td.k isnt syntax.LetBind) || td.annotations.contains(Annot.Modifier(syntax.Keyword.`public`)(N)) @@ -74,8 +76,8 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) private def parameter(param: Param, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = val ref = SimpleRef(param.sym)(param.sym.id) - val unknown = UnknownValueShape(ref)(ShapeProvenance( - (msg"Parameter '${param.sym.nme}' admits values of unknown shape." -> param.toLoc) :: path.diagnosticNotes)) + val unknown = UnknownValueShape(ref)(path.via( + msg"Parameter '${param.sym.nme}' admits values of unknown shape." -> param.toLoc)) resolver.parameterSignature(param) match case N => resolver.constrainParameter(param, unknown.enter(marks), marks) case S(tpe) => unknown.enter(marks) match @@ -87,8 +89,8 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) params.params.foreach(parameter(_, marks, path)) params.restParam.foreach: rest => val ref = SimpleRef(rest.sym)(rest.sym.id) - val unknown = UnknownValueShape(ref)(ShapeProvenance( - (msg"Rest parameter '${rest.sym.nme}' admits elements of unknown shape." -> rest.toLoc) :: path.diagnosticNotes)) + val unknown = UnknownValueShape(ref)(path.via( + msg"Rest parameter '${rest.sym.nme}' admits elements of unknown shape." -> rest.toLoc)) val tuple = TupleShape(ref, TupleShape.Unknown(ref, TupleShape.ValueField(unknown, Nil)) :: Nil)(resolver) resolver.constrainParameter(rest, tuple.enter(marks), marks) @@ -191,5 +193,13 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) member(symbol, Nil, ShapeProvenance.empty.via(msg"'${symbol.nme}' is exposed by this compilation unit." -> symbol.toLoc)) values.foreach: value => term(value, Nil, ShapeProvenance.empty.via(msg"This value is exposed by this compilation unit." -> value.toLoc)) - while work.nonEmpty do work.dequeue()() + // Annotated callbacks can escape even from private code. Delay their + // exposure until the block is elaborated, so forward definitions and + // returned closures are available. Processing one root may discover more. + def pending(): Unit = rstate.takeExposureInputs().foreach: input => + emit(input.shape, input.provenance)(using input.state) + pending() + while work.nonEmpty do + work.dequeue()() + pending() finally detach.reverseIterator.foreach(_()) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 55b638df5b..636a4af694 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1247,6 +1247,7 @@ class NewResolver: private def inferTypeArguments(tpe: DeclaredType, value: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = if !typeConstraints.add((tpe, value, marks)) then return + val annotation = tpe.resolution.source def follow(tpe: DeclaredType, args: TypeApplication, captures: Ls[Marks], seen: Set[TypeResolution])(using NewResolverState): Unit = if !seen(tpe.resolution) then val next = seen + tpe.resolution @@ -1310,6 +1311,18 @@ class NewResolver: listenArgumentParts(argument.instantiate(tpe.instances)): parts => follow(if positive then parts.output else parts.input, args, captures, next) case TypeShape.Nominal(cls) => listenInstanceViews(value): value => + // Erasing a callback's signature must not erase the obligation to + // check its implementation. Its unannotated inputs can receive any + // value, independently of the calls observed through Function. + listenTypeViews(tpe): + case Marked(nominal: NominalInstanceView, _) if nominal.isInstanceOfClass(prelude.builtins.Function.defn.get) => + value.enter(captures) match + case actual: TermShape => + val provenance = ShapeProvenance(msg"The 'Function' type does not specify parameter types." -> N :: Nil) + .withTypeOrigin(annotation) + rstate.exposeValue(instantiateShape(actual, rstate.instances), provenance) + case NoShape => () + case _ => () val arguments = typeArguments(tpe, args, cls.tparams).map(transportType(_, captures)) def constrainNominal(value: TermShape)(using NewResolverState): Unit = val ShapeParts(actual, instances, _) = shapeParts(value) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 6d5eab2396..0c0687b05f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -64,6 +64,20 @@ final class NewResolverState private ( def isOwnedSym(symbol: Symbol): Bool = symbol.getState is owner + // Annotation constraints record escaping implementations in the consuming + // unit, including their activation context. The block's exposure pass drains + // these roots before sealing resolution; they are neither copied from imports + // nor retained across worksheet blocks. One witness suffices for each value. + private val exposureInputs = mutable.LinkedHashMap.empty[(TermShape, NewResolverState), ShapeProvenance] + def exposeValue(shape: TermShape, provenance: ShapeProvenance): Unit = + root.exposureInputs.getOrElseUpdate((shape, this), provenance) + def takeExposureInputs(): Ls[InterfaceExposure.Input] = + val inputs = root.exposureInputs.iterator.map: + case ((shape, state), provenance) => InterfaceExposure.Input(shape, state)(provenance) + .toList + root.exposureInputs.clear() + inputs + // A listener carries its defining graph, not its defining mutable resolver. // Views share the consumer's host copies, but consult the source's bindings and // memoized nodes individually. Creating a view never traverses a source map. diff --git a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls index cd19baed56..84448bb524 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls @@ -103,11 +103,124 @@ fun missing(f: Function) = f.nonexistent // A user-defined class named Function is not the builtin. :ge -class Function -fun shadowed(f: Function) = f(1) +module Shadowing with + class Function + fun shadowed(f: Function) = f(1) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.107: fun shadowed(f: Function) = f(1) -//│ ║ ^^^^ +//│ ║ l.108: fun shadowed(f: Function) = f(1) +//│ ║ ^^^^ //│ ╟── Value of type 'Function' cannot be called like a function. -//│ ║ l.107: fun shadowed(f: Function) = f(1) -//│ ╙── ^^^^^^^^ +//│ ║ l.108: fun shadowed(f: Function) = f(1) +//│ ╙── ^^^^^^^^ + +:e +fun test(f: Function) = f(1, 2, 3) +test((x) => x.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.118: test((x) => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.117: fun test(f: Function) = f(1, 2, 3) +//│ ║ ^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.118: test((x) => x.value) +//│ ║ ^ +//│ ╙── The 'Function' type does not specify parameter types. +//│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 3 + +// Erasure checks callback inputs even when a private consumer never calls it. +:ge +private fun discard(f: Function) = () +discard(x => x.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.134: discard(x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.133: private fun discard(f: Function) = () +//│ ║ ^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.134: discard(x => x.value) +//│ ║ ^ +//│ ╙── The 'Function' type does not specify parameter types. + +// Aliases retain the annotation witness, including for unknown rest elements. +:ge +type ErasedCallback = Function +private fun discardRest(f: ErasedCallback) = () +discardRest((...xs) => xs.0.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.150: discardRest((...xs) => xs.0.value) +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.149: private fun discardRest(f: ErasedCallback) = () +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Rest parameter 'xs' admits elements of unknown shape. +//│ ║ l.150: discardRest((...xs) => xs.0.value) +//│ ║ ^^^^^ +//│ ╙── The 'Function' type does not specify parameter types. + +// Explicit callback annotations still provide an interface for their parameters. +:expect 42 +class Payload(val value: Int) +private fun invoke(f: Function) = f(Payload(42)) +invoke((x: Payload) => x.value) +//│ = 42 + +// Exposure follows returned callbacks after forward definitions are available. +:ge +private fun discardReturned(f: Function) = () +discardReturned(() => makeCallback()) +private fun makeCallback() = laterCallback +private fun laterCallback(x) = x.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.174: private fun laterCallback(x) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.171: private fun discardReturned(f: Function) = () +//│ ║ ^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.174: private fun laterCallback(x) = x.value +//│ ║ ^ +//│ ╟── This value is returned here. +//│ ║ l.172: discardReturned(() => makeCallback()) +//│ ║ ^^^^^^^^^^^^^^ +//│ ╙── The 'Function' type does not specify parameter types. + +// A declared result erases a private implementation even without a caller. +:ge +private fun declared(): Function = x => x.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.191: private fun declared(): Function = x => x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.191: private fun declared(): Function = x => x.value +//│ ║ ^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.191: private fun declared(): Function = x => x.value +//│ ║ ^ +//│ ╙── The 'Function' type does not specify parameter types. + +// Erasing an imported parameter's signature checks the consumer's callback. +import "../../mlscript-compile/MutMap.mls" + +:ge +MutMap.empty |> MutMap.updateWith("key") of (x => x.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.207: MutMap.empty |> MutMap.updateWith("key") of (x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ in ../../mlscript-compile/MutMap.mls +//│ ║ l.29: fun updateWith(key)(op: Function)(m: MutMap) = +//│ ║ ^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ in BuiltinFunction.mls:207 +//│ ║ l.207: MutMap.empty |> MutMap.updateWith("key") of (x => x.value) +//│ ║ ^ +//│ ╙── The 'Function' type does not specify parameter types. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Following a callback that returns itself must reach a fixed point. +:expect () +private fun recursive(x) = recursive +private fun discardRecursive(f: Function) = () +discardRecursive(recursive) diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/BuiltinFunction.mls b/hkmc2/shared/src/test/mlscript/newres/loose/BuiltinFunction.mls new file mode 100644 index 0000000000..cd913ec2b0 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/loose/BuiltinFunction.mls @@ -0,0 +1,24 @@ +:. + +// Explicit Function annotations check erased callbacks even without strict exports. +:ge +private fun discard(f: Function) = () +discard(x => x.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.6: discard(x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.5: private fun discard(f: Function) = () +//│ ║ ^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.6: discard(x => x.value) +//│ ║ ^ +//│ ╙── The 'Function' type does not specify parameter types. + +// A callback's own annotation still supplies the interface used by its body. +:expect 42 +class Item(val value: Int) +private fun invoke(f: Function) = f(Item(42)) +invoke((x: Item) => x.value) +//│ = 42 + From 94cbf86fda03cf6e8591fe4e91ac0bb530ab1b36 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 30 Sep 2026 22:15:42 +0800 Subject: [PATCH 158/228] Order interface diagnostics along their provenance paths --- .../src/test/scala/hkmc2/PublisherTest.scala | 8 +-- .../scala/hkmc2/semantics/NewResolver.scala | 34 +++++----- .../main/scala/hkmc2/semantics/Shape.scala | 62 ++++++++--------- .../test/mlscript/newres/BuiltinFunction.mls | 66 +++++++++++++------ .../mlscript/newres/InterfaceExposure.mls | 12 ++-- .../mlscript/newres/loose/BuiltinFunction.mls | 6 +- 6 files changed, 108 insertions(+), 80 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala index f289fc1962..88d80a8c76 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala @@ -180,8 +180,8 @@ class PublisherTest extends AnyFunSuite: assert(Set(first, second).size == 1) assert(RecordShape.Unknown(source)(provenance) == RecordShape.Unknown(source)(provenance)) assert(evaluated == 0) - assert(first.provenance.diagnosticNotes.isEmpty) - assert(first.provenance.diagnosticNotes.isEmpty) + first.provenance.diagnostic(msg"Operation failed", None) + first.provenance.diagnostic(msg"Operation failed", None) assert(evaluated == 1) var noteEvaluated = 0 val extended = first.provenance.via { @@ -189,7 +189,7 @@ class PublisherTest extends AnyFunSuite: msg"A deferred provenance step" -> None } assert(noteEvaluated == 0) - assert(extended.diagnosticNotes.size == 1) - assert(extended.diagnosticNotes.size == 1) + extended.diagnostic(msg"Operation failed", None) + extended.diagnostic(msg"Operation failed", None) assert(noteEvaluated == 1) assert(evaluated == 1) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 636a4af694..07b08c0ac6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1663,8 +1663,8 @@ class NewResolver: case NoShape => () case _ => if rstate.arrayIndexErrors.add(new Identity(sel)) then - resolError(sel, (msg"${receiver.describe.capitalize} does not support checked array indexing." -> receiver.diagnosticLocation) :: - (msg"Use '![...]' for a dynamic access." -> N) :: receiver.diagnosticNotes) + resolError(sel, receiver.diagnostic(msg"${receiver.describe.capitalize} does not support checked array indexing.") ::: + (msg"Use '![...]' for a dynamic access." -> N) :: Nil) private[semantics] def arrayElementType(array: NominalInstanceView): Opt[DeclaredType] = val cls = prelude.builtins.Array.defn.get @@ -1829,8 +1829,8 @@ class NewResolver: if mismatch then if rstate.reportedArities.getOrElseUpdate(reportKey, mutable.Set.empty).add(known) then val count = if unknown then msg"at least ${known}" else msg"${known}" - resolError(src, (msg"${callable.describe.capitalize} expected ${expected} ${ - "argument".pluralized(expected)}, but got ${count}" -> callable.diagnosticLocation) :: callable.diagnosticNotes) + resolError(src, callable.diagnostic(msg"${callable.describe.capitalize} expected ${expected} ${ + "argument".pluralized(expected)}, but got ${count}")) else matched((tuple, marks)) case _ => resolError(src, msg"Expected an argument tuple." -> args.toLoc :: Nil) @@ -1851,7 +1851,7 @@ class NewResolver: true def reject(sh: TermShape)(using NewResolverState): Unit = rstate.markError(res) - resolError(res, (msg"${sh.describe.capitalize} cannot be used as a constructor pattern." -> sh.diagnosticLocation) :: sh.diagnosticNotes) + resolError(res, sh.diagnostic(msg"${sh.describe.capitalize} cannot be used as a constructor pattern.")) def classPattern(cls: ClassLikeDef)(using NewResolverState): Unit = if select(cls.sym) then val assoc = res.arguments match case N => Nil @@ -2203,7 +2203,7 @@ class NewResolver: msg"${lhs.describe.capitalize} cannot receive more argument lists." case _ => msg"${lhs.describe.capitalize} cannot be called like a function." - resolError(res, (message -> lhs.diagnosticLocation) :: lhs.diagnosticNotes) + resolError(res, lhs.diagnostic(message)) if sh.isSaturated then def go(body: Term, mss: Ls[Marks])(using NewResolverState) = listenTerm(body): sh => @@ -2307,7 +2307,7 @@ class NewResolver: msg"Cannot resolve member '$name' of a value with unknown shape." case MemberLookup.Uncertainty.RecordOverwrite => msg"Cannot resolve member '$name' across a computed key or unknown record spread." - resolError(host, (message -> provenance.typeOrigin.flatMap(_.toLoc)) :: provenance.diagnosticNotes) + resolError(host, provenance.diagnostic(message, N)) def unresolvedRef(ref: UnresolvedRef)(using NewResolverState): Unit = ref.prefixes.foreach: prefix => @@ -2396,9 +2396,9 @@ class NewResolver: def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") - def member(info: MemberLookup, description: Message, loc: Opt[Loc], notes: => Ls[(Message, Opt[Loc])], + def member(info: MemberLookup, description: Message, diagnostic: Message => Ls[(Message, Opt[Loc])], instances: TypeSubstitution)(using NewResolverState): Unit = info match - case MemberLookup.Contextual(source, substitution) => member(source, description, loc, notes, instances.withOverrides(substitution)) + case MemberLookup.Contextual(source, substitution) => member(source, description, diagnostic, instances.withOverrides(substitution)) case MemberLookup.Found(bms, marks) if rstate.canResolve(sel) || sel.resolvedMembers.contains(bms.memberSymbol) => log(s"newSel member: bms = ${bms.memberSymbol.showDbg}, mss = ${marks.map(_.showDbg)}") rstate.recordResolution(sel, sel.resolvedMembers.contains(bms.memberSymbol))(sel.resolvedMembers ::= bms.memberSymbol) @@ -2421,7 +2421,7 @@ class NewResolver: publishDynamic(sel, marks) case MemberLookup.Missing if rstate.canResolve(sel) => rstate.markError(sel) - resolError(sel, (msg"$description does not contain member '${sel.id.name}'" -> loc) :: notes) + resolError(sel, diagnostic(msg"$description does not contain member '${sel.id.name}'")) case MemberLookup.Unknown(reason, provenance) => unknownMember(sel, sel.id.name, reason, provenance) // Later activations still transport field values through the compiled // selection, but cannot choose a different member for that old syntax. @@ -2429,7 +2429,7 @@ class NewResolver: sel.cls match case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") - member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnosticLocation, shape.diagnosticNotes, TypeSubstitution.empty) + member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty) case S(cls) => listenClass(cls)(ref => val cd = ref.definition @@ -2458,11 +2458,11 @@ class NewResolver: val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation, true) case _ => info - member(selected, msg"Class '${cd.sym.nme}'", cd.toLoc, Nil, TypeSubstitution.empty) - case _ => member(info, msg"Class '${cd.sym.nme}'", cd.toLoc, Nil, TypeSubstitution.empty)) + member(selected, msg"Class '${cd.sym.nme}'", message => (message -> cd.toLoc) :: Nil, TypeSubstitution.empty) + case _ => member(info, msg"Class '${cd.sym.nme}'", message => (message -> cd.toLoc) :: Nil, TypeSubstitution.empty)) , sh => rstate.markError(sel) - resolError(sel, (msg"${sh.describe.capitalize} cannot be used as a projection class." -> sh.diagnosticLocation) :: sh.diagnosticNotes) + resolError(sel, sh.diagnostic(msg"${sh.describe.capitalize} cannot be used as a projection class.")) ) /** Both constructor references and explicit `new` use the same definition and @@ -2520,7 +2520,7 @@ class NewResolver: , shape => if !rstate.hasError(nw) then rstate.markError(nw) - resolError(nw, (msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'." -> shape.diagnosticLocation) :: shape.diagnosticNotes) + resolError(nw, shape.diagnostic(msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'.")) ) def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term)(loc: Opt[Loc])(using NewResolverState): DefineVar = @@ -2841,8 +2841,8 @@ class NewResolver: case application @ TyApp(underlying, args) => def checkArity(callee: TermShape, count: Int)(using NewResolverState): Unit = if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then - resolError(trm, (msg"${callee.describe.capitalize} expected ${count} type ${ - "argument".pluralized(count)}, but got ${args.length}" -> callee.diagnosticLocation) :: callee.diagnosticNotes) + resolError(trm, callee.diagnostic(msg"${callee.describe.capitalize} expected ${count} type ${ + "argument".pluralized(count)}, but got ${args.length}")) def instantiate(value: TermShape)(receive: Listener)(using NewResolverState): Unit = listenInstanceViews(value): viewed => val ShapeParts(actual, captured, supplied) = shapeParts(viewed) if supplied.nonEmpty then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 75c8ee1461..fc361286b1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -19,32 +19,22 @@ sealed trait Shape extends ShapeEvent, ShapeLike: def describe: Str /** Origin of the value or symbol described by this shape, independently of its use site. */ def toLoc: Opt[Loc] - /** Location supporting a description of this value's interface. A nominal - * view's class declaration and the annotation restricting this value are - * different sources: interface errors use the annotation, while toLoc keeps - * identifying the declaration. No diagnostic text is inspected to choose it. + /** Keep the operation's explanation at its interface witness, after any path + * from the observed value to that witness. Wrappers preserve this ordering. + * An annotation restricting a value and its class declaration are different + * witnesses: interface errors use the annotation, while toLoc identifies the + * declaration. Diagnostic witnesses do not change shape equality. */ - final def diagnosticLocation: Opt[Loc] = this match + final def diagnostic(message: Message): Ls[(Message, Opt[Loc])] = this match case value: TermShape => value.applicationHead._1 match - case view: NominalInstanceView => view.annotation.flatMap(_.toLoc).orElse(toLoc) - case view: ContextualShape => view.source.diagnosticLocation - case view: SpecializedShape => view.declaration.diagnosticLocation - case unknown: UnknownValueShape => unknown.provenance.typeOrigin.flatMap(_.toLoc).orElse(toLoc) - case opaque: OpaqueTypeShape => opaque.provenance.typeOrigin.flatMap(_.toLoc).orElse(toLoc) - case _ => toLoc - case _: SymShape => toLoc - /** Additional explanations, separate from the interface location attached to - * the primary message. Annotation witnesses do not change shape equality. - */ - final def diagnosticNotes: Ls[(Message, Opt[Loc])] = this match - case value: TermShape => value.applicationHead._1 match - case view: ContextualShape => view.source.diagnosticNotes - case view: SpecializedShape => view.declaration.diagnosticNotes - case unknown: UnknownValueShape => unknown.provenance.diagnosticNotes - case opaque: OpaqueTypeShape => opaque.provenance.diagnosticNotes - case rigid: RigidTypeShape => rigid.provenance.diagnosticNotes - case _ => Nil - case _: SymShape => Nil + case view: NominalInstanceView => (message -> view.annotation.flatMap(_.toLoc).orElse(toLoc)) :: Nil + case view: ContextualShape => view.source.diagnostic(message) + case view: SpecializedShape => view.declaration.diagnostic(message) + case unknown: UnknownValueShape => unknown.provenance.diagnostic(message, toLoc) + case opaque: OpaqueTypeShape => opaque.provenance.diagnostic(message, toLoc) + case rigid: RigidTypeShape => rigid.provenance.diagnostic(message, toLoc) + case _ => (message -> toLoc) :: Nil + case _: SymShape => (message -> toLoc) :: Nil def shwDbg(using DebugPrinter): Str = this match // case ds: DefnShape => s"DefnShape(${ds.defn.describe} ${ds.defn.sym.showDbg})" case ds: DefnShape => ds.defn.sym.showDbg @@ -737,18 +727,28 @@ final case class DynShape() extends CoreHeadShape: * locations, and the flattened chain are evaluated only when reporting an error. * Discovery retains one witness per reached shape to bound recursive paths. */ -final class ShapeProvenance private (notes: => Ls[(Message, Opt[Loc])], val typeOrigin: Opt[Term]): - lazy val diagnosticNotes: Ls[(Message, Opt[Loc])] = notes +final class ShapeProvenance private (path: => Ls[(Message, Opt[Loc])], notes: => Ls[(Message, Opt[Loc])], + typeOrigin: Opt[Term]): + private lazy val pathNotes = path + private lazy val originNotes = notes + /** A type origin anchors the operation's explanation: first trace the value + * back to that annotation, then explain the interface it provides. Without + * an annotation witness, report the operation at its fallback location first + * and follow it with the reasons the value's shape is unknown. + */ + def diagnostic(message: Message, fallback: Opt[Loc]): Ls[(Message, Opt[Loc])] = typeOrigin match + case S(source) => pathNotes ::: (message -> source.toLoc.orElse(fallback)) :: originNotes + case N => (message -> fallback) :: pathNotes ::: originNotes def via(note: => (Message, Opt[Loc])): ShapeProvenance = - new ShapeProvenance(note :: diagnosticNotes, typeOrigin) - /** The written type restricting this observation belongs on the operation's - * primary message; storage/exposure reasons remain separate diagnostic notes. + new ShapeProvenance(note :: pathNotes, originNotes, typeOrigin) + /** Keep the written type restricting this observation separate from the path + * through parameters, returns, and storage that led to the observation. */ def withTypeOrigin(source: Term): ShapeProvenance = - new ShapeProvenance(diagnosticNotes, S(source)) + new ShapeProvenance(pathNotes, originNotes, S(source)) object ShapeProvenance: - def apply(notes: => Ls[(Message, Opt[Loc])]): ShapeProvenance = new ShapeProvenance(notes, N) + def apply(notes: => Ls[(Message, Opt[Loc])]): ShapeProvenance = new ShapeProvenance(Nil, notes, N) val empty = ShapeProvenance(Nil) /** An unknown input or the element of an opaque or widened spread can be any diff --git a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls index 84448bb524..6c0c28c90f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls @@ -119,12 +119,12 @@ test((x) => x.value) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.118: test((x) => x.value) //│ ║ ^^^^^^^ -//│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ║ l.117: fun test(f: Function) = f(1, 2, 3) -//│ ║ ^^^^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. //│ ║ l.118: test((x) => x.value) //│ ║ ^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.117: fun test(f: Function) = f(1, 2, 3) +//│ ║ ^^^^^^^^ //│ ╙── The 'Function' type does not specify parameter types. //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 3 @@ -135,12 +135,12 @@ discard(x => x.value) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.134: discard(x => x.value) //│ ║ ^^^^^^^ -//│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ║ l.133: private fun discard(f: Function) = () -//│ ║ ^^^^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. //│ ║ l.134: discard(x => x.value) //│ ║ ^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.133: private fun discard(f: Function) = () +//│ ║ ^^^^^^^^ //│ ╙── The 'Function' type does not specify parameter types. // Aliases retain the annotation witness, including for unknown rest elements. @@ -151,12 +151,12 @@ discardRest((...xs) => xs.0.value) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.150: discardRest((...xs) => xs.0.value) //│ ║ ^^^^^^^^^^ -//│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ║ l.149: private fun discardRest(f: ErasedCallback) = () -//│ ║ ^^^^^^^^^^^^^^ //│ ╟── Rest parameter 'xs' admits elements of unknown shape. //│ ║ l.150: discardRest((...xs) => xs.0.value) //│ ║ ^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.149: private fun discardRest(f: ErasedCallback) = () +//│ ║ ^^^^^^^^^^^^^^ //│ ╙── The 'Function' type does not specify parameter types. // Explicit callback annotations still provide an interface for their parameters. @@ -175,15 +175,15 @@ private fun laterCallback(x) = x.value //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.174: private fun laterCallback(x) = x.value //│ ║ ^^^^^^^ -//│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ║ l.171: private fun discardReturned(f: Function) = () -//│ ║ ^^^^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. //│ ║ l.174: private fun laterCallback(x) = x.value //│ ║ ^ //│ ╟── This value is returned here. //│ ║ l.172: discardReturned(() => makeCallback()) //│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.171: private fun discardReturned(f: Function) = () +//│ ║ ^^^^^^^^ //│ ╙── The 'Function' type does not specify parameter types. // A declared result erases a private implementation even without a caller. @@ -192,12 +192,12 @@ private fun declared(): Function = x => x.value //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.191: private fun declared(): Function = x => x.value //│ ║ ^^^^^^^ -//│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ║ l.191: private fun declared(): Function = x => x.value -//│ ║ ^^^^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. //│ ║ l.191: private fun declared(): Function = x => x.value //│ ║ ^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.191: private fun declared(): Function = x => x.value +//│ ║ ^^^^^^^^ //│ ╙── The 'Function' type does not specify parameter types. // Erasing an imported parameter's signature checks the consumer's callback. @@ -208,14 +208,13 @@ MutMap.empty |> MutMap.updateWith("key") of (x => x.value) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.207: MutMap.empty |> MutMap.updateWith("key") of (x => x.value) //│ ║ ^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.207: MutMap.empty |> MutMap.updateWith("key") of (x => x.value) +//│ ║ ^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ║ in ../../mlscript-compile/MutMap.mls //│ ║ l.29: fun updateWith(key)(op: Function)(m: MutMap) = //│ ║ ^^^^^^^^ -//│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ in BuiltinFunction.mls:207 -//│ ║ l.207: MutMap.empty |> MutMap.updateWith("key") of (x => x.value) -//│ ║ ^ //│ ╙── The 'Function' type does not specify parameter types. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -224,3 +223,32 @@ MutMap.empty |> MutMap.updateWith("key") of (x => x.value) private fun recursive(x) = recursive private fun discardRecursive(f: Function) = () discardRecursive(recursive) + +// Application diagnostics follow the same parameter-to-annotation path as selections. +:ge +private fun discardCallable(f: Function) = () +discardCallable(x => x()) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.230: discardCallable(x => x()) +//│ ║ ^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.230: discardCallable(x => x()) +//│ ║ ^ +//│ ╟── Value of unknown shape cannot be called like a function. +//│ ║ l.229: private fun discardCallable(f: Function) = () +//│ ║ ^^^^^^^^ +//│ ╙── The 'Function' type does not specify parameter types. + +// Propagating an unknown result through a spread retains the annotation's position. +:ge +private fun spreadResult(f: Function) = [...f()].0.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.244: private fun spreadResult(f: Function) = [...f()].0.value +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── This spread has no known element shape. +//│ ║ l.244: private fun spreadResult(f: Function) = [...f()].0.value +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.244: private fun spreadResult(f: Function) = [...f()].0.value +//│ ║ ^^^^^^^^ +//│ ╙── The 'Function' type does not specify a return type. diff --git a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls index 18ebd33ed7..2c488b93f9 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls @@ -249,12 +249,12 @@ spreadRecord({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.247: fun spreadRecord[A](x: A) = ({...x}).a //│ ║ ^^^^^^^^^^ -//│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. -//│ ║ l.247: fun spreadRecord[A](x: A) = ({...x}).a -//│ ║ ^ //│ ╟── This spread has no known element shape. //│ ║ l.247: fun spreadRecord[A](x: A) = ({...x}).a //│ ║ ^ +//│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. +//│ ║ l.247: fun spreadRecord[A](x: A) = ({...x}).a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. //│ ║ l.247: fun spreadRecord[A](x: A) = ({...x}).a //│ ╙── ^ @@ -267,12 +267,12 @@ spreadTuple([{a: 1}]) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.265: fun spreadTuple[A](x: A) = [...x].0.a //│ ║ ^^^^^^^^^^ -//│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ║ l.265: fun spreadTuple[A](x: A) = [...x].0.a -//│ ║ ^ //│ ╟── This spread has no known element shape. //│ ║ l.265: fun spreadTuple[A](x: A) = [...x].0.a //│ ║ ^ +//│ ╟── Cannot resolve member 'a' of a value with unknown shape. +//│ ║ l.265: fun spreadTuple[A](x: A) = [...x].0.a +//│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. //│ ║ l.265: fun spreadTuple[A](x: A) = [...x].0.a //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/BuiltinFunction.mls b/hkmc2/shared/src/test/mlscript/newres/loose/BuiltinFunction.mls index cd913ec2b0..233956ef22 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/BuiltinFunction.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/BuiltinFunction.mls @@ -7,12 +7,12 @@ discard(x => x.value) //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.6: discard(x => x.value) //│ ║ ^^^^^^^ -//│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ║ l.5: private fun discard(f: Function) = () -//│ ║ ^^^^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. //│ ║ l.6: discard(x => x.value) //│ ║ ^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.5: private fun discard(f: Function) = () +//│ ║ ^^^^^^^^ //│ ╙── The 'Function' type does not specify parameter types. // A callback's own annotation still supplies the interface used by its body. From 2a04ec6b8927214d4a5c1eda346cc65b40e0c785 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 1 Oct 2026 17:11:42 +0800 Subject: [PATCH 159/228] Port compilation tests to 0.3 with explicit exposed interfaces --- .../src/test/mlscript-compile/Benchmark.mls | 6 +- .../shared/src/test/mlscript-compile/Char.mls | 4 +- .../test/mlscript-compile/ObjectBuffer.mls | 10 ++-- .../test/mlscript-compile/QuoteExample.mls | 4 +- .../src/test/mlscript-compile/Stack.mls | 6 +- .../src/test/mlscript-compile/TreeTracer.mls | 4 +- .../shared/src/test/mlscript-compile/XML.mls | 6 +- .../mlscript-compile/apps/parsing/Parser.mls | 2 +- .../mlscript-compile/apps/parsing/Rules.mls | 4 +- .../mlscript-compile/apps/parsing/Test.mls | 2 + .../mlscript-compile/apps/parsing/Tree.mls | 4 +- .../mlscript-compile/nofib/NofibPrelude.mls | 60 ++++++++++--------- .../src/test/mlscript-compile/nofib/ansi.mls | 16 ++--- .../src/test/mlscript-compile/nofib/atom.mls | 4 +- .../test/mlscript-compile/nofib/awards.mls | 6 +- .../test/mlscript-compile/nofib/cichelli.mls | 4 +- .../test/mlscript-compile/nofib/circsim.mls | 22 +++---- .../mlscript-compile/nofib/constraints.mls | 18 +++--- .../mlscript-compile/nofib/cryptarithm2.mls | 8 ++- .../src/test/mlscript-compile/nofib/cse.mls | 26 ++++---- .../src/test/mlscript-compile/nofib/eliza.mls | 6 +- .../src/test/mlscript-compile/nofib/fish.mls | 8 ++- .../test/mlscript-compile/nofib/integer.mls | 8 ++- .../test/mlscript-compile/nofib/knights.mls | 10 ++-- .../test/mlscript-compile/nofib/lambda.mls | 6 +- .../src/test/mlscript-compile/nofib/lcss.mls | 4 +- .../src/test/mlscript-compile/nofib/life.mls | 4 +- .../src/test/mlscript-compile/nofib/mate.mls | 22 +++---- .../test/mlscript-compile/nofib/minimax.mls | 12 ++-- .../src/test/mlscript-compile/nofib/para.mls | 14 +++-- .../src/test/mlscript-compile/nofib/power.mls | 6 +- .../test/mlscript-compile/nofib/pretty.mls | 20 ++++--- .../test/mlscript-compile/nofib/primetest.mls | 6 +- .../src/test/mlscript-compile/nofib/scc.mls | 4 +- .../test/mlscript-compile/nofib/secretary.mls | 4 +- .../test/mlscript-compile/nofib/treejoin.mls | 12 ++-- .../ups/EvaluationContext.mls | 22 +++---- .../src/test/mlscript-compile/wasm/Wasm.mls | 12 ++-- 38 files changed, 235 insertions(+), 161 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Benchmark.mls b/hkmc2/shared/src/test/mlscript-compile/Benchmark.mls index 972a341d68..2131c8d240 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Benchmark.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Benchmark.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "benchmark" let bm = benchmark @@ -11,10 +13,10 @@ fun mkSuite() = new! mut bm.Suite() fun setPrefixPath(path) = set prefixPath = path -fun runSuite(suite) = +fun runSuite(suite: { run: Function }) = let settings = { async: false } suite.run(settings) -fun writeResults(suite, path) = +fun writeResults(suite: { map: Function }, path) = let jsonStr = JSON.stringify(suite.map((res, _, _) => res), null, 2) fs.writeFileSync(prefixPath + path, jsonStr) diff --git a/hkmc2/shared/src/test/mlscript-compile/Char.mls b/hkmc2/shared/src/test/mlscript-compile/Char.mls index e2bb1ddc00..beb41e01f6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Char.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Char.mls @@ -1,6 +1,8 @@ +#lang(0.3.x) + module Char with - pattern AnyChar = (Str as s where s.length is 1) => s + pattern AnyChar = (Str as s where (s as Str).length is 1) => s pattern Lowercase = "a" ..= "z" pattern Uppercase = "A" ..= "Z" pattern Letter = Lowercase | Uppercase diff --git a/hkmc2/shared/src/test/mlscript-compile/ObjectBuffer.mls b/hkmc2/shared/src/test/mlscript-compile/ObjectBuffer.mls index 7941a9d780..d031ff9bc1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/ObjectBuffer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/ObjectBuffer.mls @@ -1,11 +1,13 @@ +#lang(0.3.x) + fun max(a, b) = if a < b then b else a module ObjectBuffer with abstract class ObjectBuffer with val buf - fun mkNew(cls) - fun del(cls, inst) + fun mkNew(cls: { size: Int, ctor: (ObjectBuffer, Int) -> Any }) + fun del(cls: { size: Int }, inst) fun read(idx) fun write(idx, v) @@ -17,12 +19,12 @@ module ObjectBuffer with buf.[0] = buf.length buf.[1] = -1 - fun mkNew(cls) = + fun mkNew(cls: { size: Int, ctor: (ObjectBuffer, Int) -> Any }) = if cls.size === 0 do return 0 val idx = _alloc(getBlockSize(cls.size)) cls.ctor(this, idx) - fun del(cls, inst) = + fun del(cls: { size: Int }, inst) = if cls.size !== 0 do _free(inst, getBlockSize(cls.size)) diff --git a/hkmc2/shared/src/test/mlscript-compile/QuoteExample.mls b/hkmc2/shared/src/test/mlscript-compile/QuoteExample.mls index 9013f3ea45..6efa69c1c5 100644 --- a/hkmc2/shared/src/test/mlscript-compile/QuoteExample.mls +++ b/hkmc2/shared/src/test/mlscript-compile/QuoteExample.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./Predef.mls" module QuoteExample with @@ -6,7 +8,7 @@ module QuoteExample with fun power(x) = case 0 then `1.0 n then x `*. power(x)(n - 1) - fun bind(rhs, k) = `let x = rhs `in k(x) + fun bind(rhs, k: Function) = `let x = rhs `in k(x) fun body(x, y) = case 0 then x 1 then y diff --git a/hkmc2/shared/src/test/mlscript-compile/Stack.mls b/hkmc2/shared/src/test/mlscript-compile/Stack.mls index 0865d2a1c5..646711a743 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Stack.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Stack.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + type Stack[A] = Stack.{ Cons[A] | Nil } @@ -17,7 +19,7 @@ fun reverseAndAppend(xs, tail) = if xs is fun reverse(xs) = reverseAndAppend(xs, Nil) -fun fromArray(arr) = +fun fromArray(arr: Array) = let ls = Nil i = arr.length - 1 @@ -70,7 +72,7 @@ fun (:::) concat(xs, ys) = if fun (:+) append(xs, y) = xs ::: y :: Nil -fun filter(xs, f) = if xs is +fun filter(xs, f: Function) = if xs is head :: tail and f(head) then head :: filter(tail, f) else filter(tail, f) diff --git a/hkmc2/shared/src/test/mlscript-compile/TreeTracer.mls b/hkmc2/shared/src/test/mlscript-compile/TreeTracer.mls index 3d50c7675c..d620ea5248 100644 --- a/hkmc2/shared/src/test/mlscript-compile/TreeTracer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/TreeTracer.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./Predef.mls" import "./StrOps.mls" @@ -8,7 +10,7 @@ module TreeTracer with ... class TreeTracer with mut val steps = 0 mut val enabled = false - fun output(outie, innie, innieAlt, message) = if enabled do + fun output(outie, innie, innieAlt, message: Str) = if enabled do let lines = message.split("\n") lines.forEach of (line, i, xs) => if let postfix = if lines.length > 1 then " ↵" else "" diff --git a/hkmc2/shared/src/test/mlscript-compile/XML.mls b/hkmc2/shared/src/test/mlscript-compile/XML.mls index 606fbfdd21..4c78cbba31 100644 --- a/hkmc2/shared/src/test/mlscript-compile/XML.mls +++ b/hkmc2/shared/src/test/mlscript-compile/XML.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./Predef.mls" import "./Iter.mls" @@ -14,12 +16,12 @@ module XML with ... fun serializeValue(value) = if value is Str then JSON.stringify(value) - StyleAttributeValue then value.toValue() + StyleAttributeValue then (value as StyleAttributeValue).toValue() fun joinAttributes(attributes) = attributes mapping of case [name, value] then name + "=" + serializeValue(value) - StyleAttributeValue as style then "style=" + style.toValue() + StyleAttributeValue as style then "style=" + (style as StyleAttributeValue).toValue() Object as record then joinAttributes(Object.entries(record)) joined(" ") diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls index 1b9244f435..4a0f353db5 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls @@ -24,7 +24,7 @@ open Rules { termRule, typeRule, declRule, syntaxKinds } module Parser with ... -val tracer = new TreeTracer.TreeTracer +val tracer: TreeTracer.TreeTracer = new TreeTracer.TreeTracer let termOptions = kind: "term", rule: termRule diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls index 966ed0177b..06476e02f7 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "../../Predef.mls" import "../../Option.mls" import "../../Stack.mls" @@ -28,7 +30,7 @@ fun getRuleByKind(kind: Str): ParseRule.ParseRule[out Anything] = syntaxKinds |> fun define(name: Str)(...choices) = syntaxKinds |> MutMap.updateWith(name) of case None then Some(rule(name, ...choices)) - Some(rule) then Some(rule.extendChoices(choices Iter.toStack())) + Some(rule) then Some((rule as ParseRule.ParseRule).extendChoices(choices Iter.toStack())) fun idFirst(value, _) = value fun idSecond(_, value) = value diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls index e52fa72200..ad1c063e05 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Test.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./Lexer.mls" import "./Keywords.mls" import "./Token.mls" diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls index b6b62e9ccb..bb9b269e9f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Tree.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "../../Iter.mls" import "../../Predef.mls" import "../../Stack.mls" @@ -100,7 +102,7 @@ module Tree with Tuple(trees) then "(" + trees Iter.fromStack() Iter.mapping(go) Iter.joined(", ") + ")" Sequence(trees) then trees Iter.fromStack() Iter.mapping(go) Iter.joined("; ") Literal(LiteralKind.String, value) and - value.length > 5 then JSON.stringify(value.slice(0, 5)).slice(0, -1) + "…\"" + value.length > 5 then (JSON.stringify(value.slice(0, 5)) as Str).slice(0, -1) + "…\"" else JSON.stringify(value) Literal(_, value) then value Match(scrutinee, branches) then mkStr of diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/NofibPrelude.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/NofibPrelude.mls index 944717bcd6..c56a0d199a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/NofibPrelude.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/NofibPrelude.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open Predef @@ -33,7 +35,7 @@ fun _internal_cons_to_str(ls) = if ls is Nil then "" h :: Nil then render(h) h :: t then render(h) + "," + _internal_cons_to_str(t) -fun ltList(xs, ys, lt, gt) = if xs is +fun ltList(xs, ys, lt: Function, gt: Function) = if xs is Nil and ys is Nil then false else true @@ -52,20 +54,20 @@ abstract class LzList[out T]: LzCons[T] | LzNil data class LzCons[out T](head: T, tail: LazyList[T]) extends LzList[T] object LzNil extends LzList -fun ltTup2(t1, t2, lt1, gt1, lt2) = if t1 is [a, b] and t2 is [c, d] and +fun ltTup2(t1, t2, lt1: Function, gt1: Function, lt2: Function) = if t1 is [a, b] and t2 is [c, d] and lt1(a, c) then true gt1(a, c) then false else lt2(b, d) fun eqTup2(t1, t2) = if t1 is [a, b] and t2 is [c, d] then a == c and b == d -fun compose(f, g) = x => f(g(x)) +fun compose(f: Function, g: Function) = x => f(g(x)) fun snd(x) = if x is [f, s] then s fun fst(x) = if x is [f, s] then f -fun until(p, f, i) = if p(i) then i else until(p, f, f(i)) +fun until(p: Function, f: Function, i) = if p(i) then i else until(p, f, f(i)) -fun flip(f, x, y) = f(y)(x) +fun flip[A, B, C](f: B -> A -> C, x: A, y: B): C = f(y)(x) fun power(a, n) = globalThis.Math.pow(a, n) @@ -86,13 +88,13 @@ fun abs(x) = globalThis.Math.abs(x) fun head(l) = if l is h :: t then h fun tail(l) = if l is h :: t then t -fun while_(p, f, x) = if p(x) then while_(p, f, f(x)) else x +fun while_(p: Function, f: Function, x) = if p(x) then while_(p, f, f(x)) else x fun reverse(l) = fun r(l', l) = if l is x :: xs then r(x :: l', xs) else l' r(Nil, l) -fun map(f, xs) = if xs is +fun map[A, B](f: A -> B, xs: List[A]): List[B] = if xs is x :: xs then f(x) :: map(f, xs) Nil then Nil @@ -107,7 +109,7 @@ fun listEq(xs, ys) = if xs is hx :: tx and ys is hy :: ty and (hx == hy) then listEq(tx, ty) else false -fun listEqBy(f, a, b) = if a is +fun listEqBy(f: Function, a, b) = if a is Nil and b is Nil then true x :: xs and b is y :: ys then f(x, y) && listEqBy(f, xs, ys) else false @@ -155,13 +157,13 @@ fun concat(ls) = if ls is Nil then Nil x :: xs then append(x, concat(xs)) -fun filter(f, ls) = if ls is +fun filter(f: Function, ls) = if ls is Nil then Nil h :: t and f(h) then h :: filter(f, t) else filter(f, t) -fun all(p, ls) = if ls is +fun all(p: Function, ls) = if ls is Nil then true h :: t and p(h) then all(p, t) @@ -173,46 +175,46 @@ fun orList(ls) = if ls is h then true else orList(t) -fun leaveWhile(f, ls) = if ls is +fun leaveWhile(f: Function, ls) = if ls is Nil then Nil h :: t and f(h) then leaveWhile(f, t) else h :: t -fun foldl(f, a, xs) = if xs is +fun foldl[A, B](f: (B, A) -> B, a: B, xs: List[A]): B = if xs is Nil then a h :: t then foldl(f, f(a, h), t) -fun scanl(f, q, ls) = if ls is +fun scanl(f: Function, q, ls) = if ls is Nil then q :: Nil x :: xs then q :: scanl(f, f(q, x), xs) -fun scanr(f, q, ls) = if ls is +fun scanr(f: Function, q, ls) = if ls is Nil then q :: Nil x :: xs and scanr(f, q, xs) is q :: t then f(x, q) :: q :: t -fun foldr(f, z, xs) = if xs is +fun foldr[A, B](f: (A, B) -> B, z: B, xs: List[A]): B = if xs is Nil then z h :: t then f(h, foldr(f, z, t)) fun foldl1(f, ls) = if ls is x :: xs then foldl(f, x, xs) -fun foldr1(f, ls) = if ls is +fun foldr1[A](f: (A, A) -> A, ls: List[A]): A = if ls is x :: Nil then x x :: xs then f(x, foldr1(f, xs)) fun maximum(xs) = foldl1((x, y) => if x > y then x else y, xs) -fun nubBy(eq, ls) = if ls is +fun nubBy(eq: Function, ls) = if ls is Nil then Nil h :: t then h :: nubBy(eq, filter(y => not(eq(h, y)), t)) -fun zipWith(f, xss, yss) = if +fun zipWith(f: Function, xss, yss) = if xss is x :: xs and yss is y :: ys then f(x, y) :: zipWith(f, xs, ys) else Nil -fun deleteBy(eq, x, ys) = if ys is +fun deleteBy(eq: Function, x, ys) = if ys is Nil then Nil y :: ys and eq(x, y) then ys @@ -261,31 +263,31 @@ fun transpose(xss) = Nil :: xss then transpose(xss) (x :: xs) :: xss and unzip(lscomp(xss)) is [hds, tls] then combine(x, hds, xs, tls) -fun break_(p, ls) = if ls is +fun break_(p: Function, ls) = if ls is Nil then [Nil, Nil] x :: xs and p(x) then [Nil, x :: xs] break_(p, xs) is [ys, zs] then [x :: ys, zs] -fun flatMap(f, ls) = if ls is +fun flatMap(f: Function, ls) = if ls is Nil then Nil h :: t then append(f(h), flatMap(f, t)) // ===================== -fun map_lz(f, ls) = lazy of () => +fun map_lz(f: Function, ls) = lazy of () => if force(ls) is LzNil then LzNil LzCons(h, t) then LzCons(f(h), map_lz(f, t)) -fun filter_lz(p, ls) = lazy of () => +fun filter_lz(p: Function, ls) = lazy of () => if force(ls) is LzNil then LzNil LzCons(h, t) and p(h) then LzCons(h, filter_lz(p, t)) else force(filter_lz(p, t)) -fun nubBy_lz(eq, ls) = lazy of () => +fun nubBy_lz(eq: Function, ls) = lazy of () => if force(ls) is LzNil then LzNil LzCons(h, t) then LzCons(h, nubBy_lz(eq, filter_lz(y => not(eq(h, y)), t))) @@ -320,15 +322,15 @@ fun zip_lz_lz(xs, ys) = if force(xs) is LzCons(x, xs) and force(ys) is LzCons(y, ys) then lazy of () => LzCons([x, y], zip_lz_lz(xs, ys)) else lazy of () => LzNil -fun zipWith_lz_lz(f, xss, yss) = lazy of () => if +fun zipWith_lz_lz(f: Function, xss, yss) = lazy of () => if force(xss) is LzCons(x, xs) and (force(yss)) is LzCons(y, ys) then LzCons(f(x, y), zipWith_lz_lz(f, xs, ys)) else LzNil -fun zipWith_lz_nl(f, xss, yss) = if +fun zipWith_lz_nl(f: Function, xss, yss) = if force(xss) is LzCons(x, xs) and yss is y :: ys then f(x, y) :: zipWith_lz_nl(f, xs, ys) else Nil -fun iterate(f, x) = lazy of () => LzCons(x, iterate(f, f(x))) +fun iterate(f: Function, x) = lazy of () => LzCons(x, iterate(f, f(x))) fun append_nl_lz(xs, ys) = if xs is Nil then ys @@ -359,8 +361,8 @@ fun tan(x) = globalThis.Math.tan(x) fun sin(x) = globalThis.Math.sin(x) fun cos(x) = globalThis.Math.cos(x) fun round(x) = globalThis.Math.round(x) -fun int_of_char(x) = x.charCodeAt(0) -fun nofibStringToList(s) = +fun int_of_char(x: Str) = x.charCodeAt(0) +fun nofibStringToList(s: Str) = fun go(i) = if i < s.length then s.charAt(i) :: go(i + 1) else Nil go(0) fun nofibListToString(ls) = if ls is diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/ansi.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/ansi.mls index 04bf8af28c..c279d71f72 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/ansi.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/ansi.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" open NofibPrelude @@ -15,21 +17,21 @@ fun highlight(s) = nofibStringToList("ESC[7m") +: s +: nofibStringToList("ESC[0m fun end(xs) = nofibStringToList("") -fun readChar(eof, consume, cs) = if cs is +fun readChar(eof: Function, consume: Function, cs) = if cs is Nil then eof(Nil) c :: cs then consume(c, cs) -fun peekChar(eof, consume, cs) = if cs is +fun peekChar(eof: Function, consume: Function, cs) = if cs is Nil then eof(Nil) c :: cs then consume(c, c :: cs) -fun pressAnyKey(prog, x) = readChar(prog, (c, x) => prog(x), x) +fun pressAnyKey(prog: Function, x) = readChar(prog, (c, x) => prog(x), x) -fun unreadChar(c, prog, cs) = prog(c :: cs) +fun unreadChar(c, prog: Function, cs) = prog(c :: cs) -fun writeChar(c, prog, cs) = c :: prog(cs) +fun writeChar(c, prog: Function, cs) = c :: prog(cs) -fun writeString(s, prog, cs) = s +: prog(cs) +fun writeString(s, prog: Function, cs) = s +: prog(cs) fun writes(ss, a, b) = writeString(concat(ss), a, b) @@ -41,7 +43,7 @@ fun writeAt(x_y, s, a) = if x_y is [x, y] then p => writeString(goto(x, y) +: s, fun moveTo(x_y, a) = if x_y is [x, y] then p => writeString(goto(x, y), a, p) -fun returnn(s, consume) = consume(reverse(s)) +fun returnn(s, consume: Any -> Function) = consume(reverse(s)) //│ ———————————————————————————————————————————————————————————————————————————————— fun deletee(n, s, l, consume, d) = if n > 0 then writeString(nofibStringToList("BS_BS"), loop(n - 1, tail(s), l, consume), d) else ringBell(loop(0, nofibStringToList(""), l, consume), d) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/atom.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/atom.mls index f1ba8582fa..56ce93a2be 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/atom.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/atom.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" open NofibPrelude @@ -34,7 +36,7 @@ fun show(s) = fun propagate(dt, aforce, state) = if state is State(pos, vel) then State(dotPlus(pos, scalarMut(dt, vel)), dotPlus(vel, scalarMut(dt, aforce))) -fun runExperiment(law, dt, param, init) = lazy of () => +fun runExperiment(law: Function, dt, param, init) = lazy of () => let stream = runExperiment(law, dt, param, init) LzCons(init, zipWith_lz_lz((x, y) => propagate(dt, x, y), law(param, stream), stream)) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/awards.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/awards.mls index 85f77c3a66..e8b809b25b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/awards.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/awards.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -17,7 +19,7 @@ fun qsort(le, ls, r) = if ls is x :: Nil then x :: r x :: xs then qpart(le, x, xs, Nil, Nil, r) -fun qpart(le, x, ys, rlt, rge, r) = if ys is +fun qpart(le: Function, x, ys, rlt, rge, r) = if ys is Nil then rqsort(le, rlt, x :: rqsort(le, rge, r)) y :: ys and le(x, y) then qpart(le, x, ys, rlt, y :: rge, r) @@ -28,7 +30,7 @@ fun rqsort(le, ls, r) = if ls is x :: Nil then x :: r x :: xs then rqpart(le, x, xs, Nil, Nil, r) -fun rqpart(le, x, yss, rle, rgt, r) = if yss is +fun rqpart(le: Function, x, yss, rle, rgt, r) = if yss is Nil then qsort(le, rle, x :: qsort(le, rgt, r)) y :: ys and le(y, x) then rqpart(le, x, ys, y :: rle, rgt, r) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/cichelli.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/cichelli.mls index 07e3036bab..b5c3dcae08 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/cichelli.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/cichelli.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -70,7 +72,7 @@ fun maxm(x, y) = if x is fun hash(cvs, k) = if k is K(_, a, z, n) then n + assoc(a, cvs) + assoc(z, cvs) -fun select(p, x, ts_fs) = if ts_fs is [ts, fs] and +fun select(p: Function, x, ts_fs) = if ts_fs is [ts, fs] and p(x) then [x :: ts, fs] else [ts, x :: fs] diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/circsim.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/circsim.mls index c4d20e5ac9..bff4649203 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/circsim.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/circsim.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -30,15 +32,15 @@ object Unit data class PS[T](pid: Int, compType: Componenet, pathDepth: Int, inports: List[[Int, Int, T]], outports: List[[Int, T, Bool, Int, Bool, Int]]) -fun pid(p) = p.pid +fun pid(p: PS) = p.pid -fun compType(p) = p.compType +fun compType(p: PS) = p.compType -fun pathDepth(p) = p.pathDepth +fun pathDepth(p: PS) = p.pathDepth -fun inports(p) = p.inports +fun inports(p: PS) = p.inports -fun outports(p) = p.outports +fun outports(p: PS) = p.outports fun updateOutports(p, noutports) = PS(pid(p), compType(p), pathDepth(p), inports(p), noutports) @@ -58,12 +60,12 @@ fun get(t) = if t is Cell(x) then x :: Nil Node(_, l, r) then get(l) +: get(r) -fun upsweep(f, t) = if t is +fun upsweep(f: Function, t) = if t is Cell(a) then [a, Cell(a)] Node(x, l, r) and upsweep(f, l) is [lv, l_] and upsweep(f, r) is [rv, r_] then [f(lv, rv), Node([lv, rv], l_, r_)] -fun downsweep(g, d, t) = if t is +fun downsweep(g: Function, d, t) = if t is Cell(x) then Cell(d) Node([lv, rv], l, r) and g(lv, rv, d) is [dl, dr] then Node(Unit, downsweep(g, dl, l), downsweep(g, dr, r)) @@ -71,15 +73,15 @@ fun downsweep(g, d, t) = if t is fun sweep_ud(up, down, u, t) = if upsweep(up, t) is [ans, t_] then [ans, downsweep(down, u, t_)] -fun scanL(f, u, xs) = +fun scanL(f: Function, u, xs) = fun down1(l, r, x) = [x, f(x, l)] if sweep_ud(f, down1, u, put(xs)) is [up_ans, t_] then [up_ans, get(t_)] -fun scanR(f, u, xs) = +fun scanR(f: Function, u, xs) = fun down2(l, r, x) = [f(r, x), x] if sweep_ud(f, down2, u, put(xs)) is [up_ans, t_] then [up_ans, get(t_)] -fun scanlr(f, g, lu, ru, xs) = +fun scanlr(f: Function, g: Function, lu, ru, xs) = fun up(f, g, lxly, rxry) = if lxly is [lx, ly] and rxry is [rx, ry] then [f(lx, rx), g(ly, ry)] fun down3(f, g, lxly, rxry, ab) = if lxly is [lx, ly] and rxry is [rx, ry] and ab is [a, b] then diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/constraints.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/constraints.mls index a9ee12304a..05f369b48e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/constraints.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/constraints.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -17,7 +19,7 @@ fun qsort(le, ls, r) = if ls is x :: Nil then x :: r x :: xs then qpart(le, x, xs, Nil, Nil, r) -fun qpart(le, x, ls, rlt, rge, r) = if ls is +fun qpart(le: Function, x, ls, rlt, rge, r) = if ls is Nil then rqsort(le, rlt, x :: rqsort(le, rge, r)) y :: ys and le(x, y) then qpart(le, x, ys, rlt, y :: rge, r) @@ -28,7 +30,7 @@ fun rqsort(le, ls, r) = if ls is x :: Nil then x :: r x :: xs then rqpart(le, x, xs, Nil, Nil, r) -fun rqpart(le, x, ls, rle, rgt, r) = if ls is +fun rqpart(le: Function, x, ls, rle, rgt, r) = if ls is Nil then rqsort(le, rle, x :: qsort(le, rgt, r)) y :: ys and le(y, x) then rqpart(le, x, ys, y :: rle, rgt, r) @@ -54,21 +56,21 @@ data class Node[out T](lab: T, children: List[Node[T]]) fun label(n) = if n is Node(l, _) then l -fun mapTree(f, n) = if n is Node(l, c) then Node(f(l), map((x => mapTree(f, x)), c)) +fun mapTree(f: Function, n) = if n is Node(l, c) then Node(f(l), map((x => mapTree(f, x)), c)) -fun foldTree(f, n) = if n is Node(l, c) then f(l, map((x => foldTree(f, x)), c)) +fun foldTree(f: Function, n) = if n is Node(l, c) then f(l, map((x => foldTree(f, x)), c)) -fun filterTree(p, t) = +fun filterTree(p: Function, t) = fun f1(a, cs) = Node(a, filter(x => p(label(x)), cs)) foldTree(f1, t) -fun prune(p, t) = filterTree(x => not(p(x)), t) +fun prune(p: Function, t) = filterTree(x => not(p(x)), t) fun leaves(t) = if t is Node(leaf, Nil) then leaf :: Nil Node(_, cs) then concat(map(leaves, cs)) -fun initTree(f, x) = Node(x, map(y => initTree(f, y), f(x))) +fun initTree(f: Function, x) = Node(x, map(y => initTree(f, y), f(x))) // -- Figure 3. Simple backtracking solver for CSPs. fun mkTree(csp) = if csp is CSP(vars, vals, rel) then @@ -122,7 +124,7 @@ fun knownSolution(c) = if c is fun checkComplete(csp, s) = if complete(csp, s) then Known(Nil) else Unknown -fun search(labeler, csp) = +fun search(labeler: Function, csp) = map of fst filter of diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/cryptarithm2.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/cryptarithm2.mls index 47915e74fa..4ce36acb73 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/cryptarithm2.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/cryptarithm2.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -17,17 +19,17 @@ fun delete_(xs, e) = deleteBy((x, y) => x === y, e, xs) fun listDiff(a, ls) = foldl(delete_, a, ls) -data class StateT[S, M, A](run) +data class StateT[S, M, A](run: Function) fun runStateT(m, s) = if m is StateT(run) then run(s) -fun bind(m, f) = +fun bind(m, f: Function) = StateT of s => concat of map(case { [a, ss] then runStateT(f(a), ss) }, runStateT(m, s)) fun return_(a) = StateT(s => [a, s] :: Nil) -fun mapM(f, ls) = foldr((a, r) => bind(f(a), x => bind(r, xs => return_(x :: xs))), return_(Nil), ls) +fun mapM(f: Function, ls) = foldr((a, r) => bind(f(a), x => bind(r, xs => return_(x :: xs))), return_(Nil), ls) fun lift(ls) = StateT(s => concat(map(x => [x, s] :: Nil, ls))) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/cse.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/cse.mls index 8f5acc95b1..15d761943f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/cse.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/cse.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -9,13 +11,13 @@ module cse with ... fun retURN(a) = s => [s, a] -fun bind(m, f) = s => if m(s) is [s_, a] then f(a)(s_) +fun bind(m: Function, f: Any -> Function) = s => if m(s) is [s_, a] then f(a)(s_) -fun join(m) = s => if m(s) is [s_, ma] then ma(s_) +fun join(m: Any -> [Any, Function]) = s => if m(s) is [s_, ma] then ma(s_) -fun mmap(f, m) = s => if m(s) is [s_, a] then [s_, f(a)] +fun mmap(f: Function, m: Function) = s => if m(s) is [s_, a] then [s_, f(a)] -fun mmapl(f, aas) = if aas is +fun mmapl(f: Function, aas) = if aas is Nil then retURN(Nil) a :: as_ then bind of @@ -24,7 +26,7 @@ fun mmapl(f, aas) = if aas is mmapl(f, as_) bs => retURN(b :: bs) -fun mmapr(f, xs) = if xs is +fun mmapr(f: Function, xs) = if xs is Nil then retURN(Nil) x :: xs then bind of @@ -33,25 +35,25 @@ fun mmapr(f, xs) = if xs is f(x) y => retURN(y :: ys) -fun mfoldl(f, a, xs) = if xs is +fun mfoldl(f: Function, a, xs) = if xs is Nil then retURN(a) x :: xs then bind(f(a, x), fax => mfoldl(f, fax, xs)) -fun mfoldr(f, a, xs) = if xs is +fun mfoldr(f: Function, a, xs) = if xs is Nil then retURN(a) x :: xs then bind(mfoldr(f, a, xs), y => f(x, y)) fun mif(c, t, f) = bind(c, cond => if cond then t else f) -fun startingWith(m, v) = if m(v) is [final, answer] then answer +fun startingWith(m: Function, v) = if m(v) is [final, answer] then answer fun fetch(s) = [s, s] -fun fetchWith(f) = s => [s, f(s)] +fun fetchWith(f: Function) = s => [s, f(s)] -fun update(f) = s => [f(s), s] +fun update(f: Function) = s => [f(s), s] fun set_(s_) = s => [s_, s] @@ -78,9 +80,9 @@ fun visited(n) = bind of fetch us => if inList(n, us) then retURN(true) else bind(set_(n :: us), _p => retURN(false)) -fun newlyDefined(x, fx, f, y) = if x === y then fx else f(y) +fun newlyDefined(x, fx, f: Function, y) = if x === y then fx else f(y) -fun findCommon(ls) = +fun findCommon[A](ls: List[[Int, A, List[Int]]]) = fun sim(n_s_cs, r_lg) = if n_s_cs is [n, s, cs] and r_lg is [r, lg] then let rcs = map(r, cs) fun lscomp(ls) = if ls is diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/eliza.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/eliza.mls index 3d2106539b..fe7c40943c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/eliza.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/eliza.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -7,9 +9,9 @@ module eliza with ... -fun toUpper(c) = c.toUpperCase() +fun toUpper(c: Str) = c.toUpperCase() -fun lz_map(f, ls) = lazy of () => +fun lz_map(f: Function, ls) = lazy of () => if ls is Nil then LzNil h :: t then LzCons(f(h), lz_map(f, t)) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/fish.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/fish.mls index ebf4c61882..38d1f3bb8f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/fish.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/fish.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" open NofibPrelude @@ -62,13 +64,13 @@ fun grid(m, n, segments, a, b, c) = lscomp(t) lscomp(segments) -fun rot(p, a, b, c) = p(vec_add(a, b), c, vec_sub([0, 0], b)) +fun rot(p: Function, a, b, c) = p(vec_add(a, b), c, vec_sub([0, 0], b)) -fun beside(m, n, p, q, a, b, c) = +fun beside(m, n, p: Function, q: Function, a, b, c) = p(a, scale_vec2(b, m, m + n), c) +: q(vec_add(a, scale_vec2(b, m, m + n)), scale_vec2(b, n, n + m), c) -fun above(m, n, p, q, a, b, c) = +fun above(m, n, p: Function, q: Function, a, b, c) = p(vec_add(a, scale_vec2(c, n, m + n)), b, scale_vec2(c, m, n + m)) +: q(a, b, scale_vec2(c, n, m + n)) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/integer.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/integer.mls index ad8d823795..97c962a944 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/integer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/integer.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -6,7 +8,7 @@ module integer with ... -fun integerbench(op, astart, astep, alim, bstart, bstep, blim) = +fun integerbench(op: Function, astart, astep, alim, bstart, bstep, blim) = fun lscomp1(ls) = if ls is Nil then Nil a :: t1 then @@ -17,7 +19,7 @@ fun integerbench(op, astart, astep, alim, bstart, bstep, blim) = lscomp1(enumFromThenTo(astart, astart + astep, alim)) -fun intbench(op, astart, astep, alim, bstart, bstep, blim) = +fun intbench(op: Function, astart, astep, alim, bstart, bstep, blim) = fun lscomp1(ls) = if ls is Nil then Nil a :: t1 then @@ -55,7 +57,7 @@ fun runalltests(astart, astep, alim, bstart, bstep, blim) = runbench((a, b) => z_gt(a, b), (a, b) => a > b, "(>)", astart, astep, alim, astart, astep, alim); runbench((a, b) => z_geq(a, b), (a, b) => a >= b, "(>=)", astart, astep, alim, astart, astep, alim) -fun testInteger_nofib(n) = runalltests(-2100000000, n, 2100000000, -2100000000, n, -2100000000) +fun testInteger_nofib(n): List[Bool] = runalltests(-2100000000, n, 2100000000, -2100000000, n, -2100000000) // NOTE: original input -2100000000 14000001 2100000000 diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/knights.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/knights.mls index 149f9c9ddd..3748abd52f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/knights.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/knights.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -5,7 +7,7 @@ open NofibPrelude module knights with ... -fun myIsDigit(c) = +fun myIsDigit(c: Str) = c.codePointAt(0) >= 48 && c.codePointAt(0) <= 57 fun intintComp(a_b, c_d) = if a_b is [a, b] and c_d is [c, d] then (a < c) || ((a === c) && (b < d)) @@ -247,16 +249,16 @@ fun inquireFront_lz(h_t) = if force(h_t) is LzCons(h, t) then h fun addAllFront_lz(list, q) = append_lz_lz(list, q) -fun depthSearch(q, growFn, finFn) = +fun depthSearch(q, growFn: Function, finFn: Function) = if emptyQueue_lz(q) then lazy of () => LzNil finFn(inquireFront_lz(q)) then lazy of () => LzCons(inquireFront_lz(q), depthSearch(removeFront_lz(q), growFn, finFn)) else depthSearch(addAllFront_lz(growFn(inquireFront_lz(q)), removeFront_lz(q)), growFn, finFn) fun printTour(ss) = - fun strToInt(y, xs) = if xs is + fun strToInt(y, xs: List[Str]) = if xs is Nil then y - x :: xs then strToInt((10 * y) + (x.codePointAt(0) - 48), xs) + x :: xs then strToInt((10 * y) + ((x as Str).codePointAt(0) - 48), xs) fun pp(xs) = if xs is Nil then Nil [x, y] :: xs then diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/lambda.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/lambda.mls index f15888b431..b0671c6a69 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/lambda.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/lambda.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -14,7 +16,7 @@ data class MyState[S, A](r: S -> [S, A]) fun myRunState(m, s) = if m is MyState(f) then f(s) -fun myBind(m, f) = MyState(s => if myRunState(m, s) is [s_, a] then myRunState(f(a), s_)) +fun myBind(m, f: Function) = MyState(s => if myRunState(m, s) is [s_, a] then myRunState(f(a), s_)) fun myReturn(a) = MyState(s => [s, a]) @@ -60,7 +62,7 @@ fun eqTerm(a, b) = if a is else false //│ ———————————————————————————————————————————————————————————————————————————————— -fun myMaybe(d, f, x) = if x is Some(x) then f(x) +fun myMaybe(d, f: Function, x) = if x is Some(x) then f(x) val incr = myReturn(Unit) //│ incr = MyState([function]) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/lcss.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/lcss.mls index 42c9ae58f1..ab17562cf6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/lcss.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/lcss.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" open NofibPrelude @@ -50,7 +52,7 @@ fun lcss(xs, ys) = algc(listLen(xs), listLen(ys), xs, ys)(Nil) fun lcssMain(a, b ,c, d, e, f) = lcss(enumFromThenTo(a,b,c), enumFromThenTo(d,e,f)) -fun testLCSS_nofib(d) = lcssMain(1,2,60,30,31,90) +fun testLCSS_nofib(d): List[Int] = lcssMain(1,2,60,30,31,90) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/life.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/life.mls index b7da7faf38..33ba9fd762 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/life.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/life.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -25,7 +27,7 @@ fun init(ls) = if ls is a :: t then a :: init(t) else throw Error(ls) -fun zipWith3(f, xs, ys, zs) = if +fun zipWith3(f: Function, xs, ys, zs) = if xs is hx :: tx and ys is hy :: ty and zs is hz :: tz then f(hx, hy, hz) :: zipWith3(f, tx, ty, tz) else Nil diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/mate.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/mate.mls index b4460f6159..b9dc8671ff 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/mate.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/mate.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -7,7 +9,7 @@ module mate with ... //│ ———————————————————————————————————————————————————————————————————————————————— -fun rqpart(le, x, ys, rle, rgt, r) = if ys is +fun rqpart(le: Function, x, ys, rle, rgt, r) = if ys is Nil then qsort(le, rle, x :: qsort(le, rgt, r)) y :: ys and le(y, x) then rqpart(le, x, ys, y :: rle, rgt, r) @@ -18,7 +20,7 @@ fun rqsort(le, xs, r) = if xs is x :: Nil then x :: r x :: xs then rqpart(le, x, xs, Nil, Nil, r) -fun qpart(le, x, ys, rlt, rge, r) = if ys is +fun qpart(le: Function, x, ys, rlt, rge, r) = if ys is Nil then rqsort(le, rlt, x :: rqsort(le, rge, r)) y :: ys and le(x, y) then qpart(le, x, ys, rlt, y :: rge, r) @@ -96,15 +98,15 @@ fun moveInFullCompare(mif1, mif2) = if sc === 0 then moveCompare(m1, m2) else sc else pc -fun maybe(d, f, x) = if x is +fun maybe[A, B](d: B, f: A -> B, x: Option[A]): B = if x is None then d Some(x) then f(x) -fun isUpper(c) = let x = c.charCodeAt(0) in (x >= 65 and x <= 90) +fun isUpper(c: Str) = let x = c.charCodeAt(0) in (x >= 65 and x <= 90) -fun isLower(c) = let x = c.charCodeAt(0) in (x >= 97 and x <= 122) +fun isLower(c: Str) = let x = c.charCodeAt(0) in (x >= 97 and x <= 122) -fun toLower(c) = if isUpper(c) then String.fromCharCode(c.charCodeAt(0) + 32) else c +fun toLower(c: Str) = if isUpper(c) then String.fromCharCode(c.charCodeAt(0) + 32) else c fun words(s) = if leaveWhile(x => x === " ", s) is Nil then Nil @@ -120,7 +122,7 @@ fun lines(s) = if break_(x => x === "\n", s) is [l, s_] then Nil then Nil Cons(_, s__) then lines(s__) -fun any(p, ls) = if ls is +fun any(p: Function, ls) = if ls is Nil then false x :: xs then p(x) || any(p, xs) @@ -170,7 +172,7 @@ fun showSquare(c, x_y) = if x_y is [x, y] then ) +: nofibStringToList(stringOfInt(if c is Black then 9 - y else y)) -fun emptyAtAll(bd, e) = if bd is Board(wkss, bkss) then +fun emptyAtAll(bd, e: Function) = if bd is Board(wkss, bkss) then fun emptyAtAllAnd(b, ls) = if ls is Nil then b [_, s] :: xs then (not(e(s)) and emptyAtAllAnd(b, xs)) @@ -251,7 +253,7 @@ fun sift(c, bd, ms, sqs) = if sqs is else sift(c, bd, Move(sq, Some(p_), None) :: ms, sqs) else sift(c, bd, ms, sqs) -fun moveLine(bd, c, sq, inc, cont) = +fun moveLine(bd, c, sq, inc: Function, cont: Function) = fun ml(sq, ms) = let sq_ = inc(sq) if onboard(sq_) and pieceAt(bd, sq_) is @@ -473,7 +475,7 @@ fun parseProblem(s) = fun readProblem(s) = parseProblem(lines(s)) -fun foldr_lz(f, a, x) = if x is +fun foldr_lz(f: Function, a, x) = if x is h :: t then f(h, lazy of () => foldr_lz(f, a, t)) Nil then a diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/minimax.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/minimax.mls index 83e664d4f0..fc3b10979d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/minimax.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/minimax.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -171,7 +173,7 @@ fun scorePiece(p, score) = if p is Empty then 0 O then -score -fun map2(f, xs, ys) = if xs is +fun map2(f: Function, xs, ys) = if xs is Nil then Nil x :: xs and ys is @@ -184,9 +186,9 @@ fun score(board, win) = eval of sum of map of fun static(board) = interpret(0, map(x => score(board, x), wins)) -fun repTree(f, g, a) = Branch(a, map(x => repTree(g, f, x), f(a))) +fun repTree(f: Function, g, a) = Branch(a, map(x => repTree(g, f, x), f(a))) -fun mapTree(f, t) = if t is Branch(a, l) then Branch(f(a), map(x => mapTree(f, x), l)) +fun mapTree(f: Function, t) = if t is Branch(a, l) then Branch(f(a), map(x => mapTree(f, x), l)) fun prune(n, t) = if t is Branch(a, l) and n === 0 then Branch(a, Nil) @@ -198,7 +200,7 @@ fun opposite(p) = if p is O then X else throw Error("opposite") -fun best(f, bs, ss) = if bs is b :: bs and ss is s :: ss then +fun best(f: Function, bs, ss) = if bs is b :: bs and ss is s :: ss then fun best_(b, s, ls1, ls2) = if ls1 is Nil and ls2 is Nil then [b, s] b_ :: bs and ls2 is s_ :: ss and @@ -227,7 +229,7 @@ fun min_(e1, e2) = if x < y then Score(x) else Score(y) -fun mise(f, g, t) = if t is +fun mise(f, g: Function, t) = if t is Branch(a, Nil) then a Branch(_, l) then foldr(f, g(OWin, XWin), map(x => mise(g, f, x), l)) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/para.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/para.mls index 49e62e4e52..f75db151ae 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/para.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/para.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -14,7 +16,7 @@ fun unwords(ws) = if ws is v :: vs then " " :: (v +: go(vs)) w +: go(ws) -fun break_(p, xs) = if xs is +fun break_(p: Function, xs) = if xs is Nil then [Nil, Nil] x :: xs and p(x) then [Nil, x :: xs] @@ -37,15 +39,15 @@ fun lines(s) = if break_(x => x === "\n", s) is [l, s_] then fun unlines(ls) = concat(map(l => l +: nofibStringToList("\n"), ls)) -fun all(p, xs) = if xs is +fun all(p: Function, xs) = if xs is Nil then true x :: xs then p(x) && all(p, xs) -fun fold1(f, g, xs) = if xs is +fun fold1(f: Function, g: Function, xs) = if xs is a :: Nil then g(a) a :: x then f(a, fold1(f, g, x)) -fun scan1(f, g, xs) = fold1((a, s) => f(a, head(s)) :: s, a => g(a) :: Nil, xs) +fun scan1(f: Function, g: Function, xs) = fold1((a, s) => f(a, head(s)) :: s, a => g(a) :: Nil, xs) fun tails(xs) = scan1((a, s) => a :: s, a => a :: Nil, xs) @@ -53,7 +55,7 @@ fun single(xs) = if xs is a :: Nil then true _ then false -fun minWith(f, xs) = fold1((a, b) => if f(a) < f(b) then a else b, x => x, xs) +fun minWith(f: Function, xs) = fold1((a, b) => if f(a) < f(b) then a else b, x => x, xs) fun new_(w, ls) = (w :: Nil) :: ls @@ -170,7 +172,7 @@ fun startr(a) = if fun ceildiv(n, m) = intDiv(n + m - 1, m) -fun fmtWith(par) = unparse(map(x => par(concat(x)), parse(par))) +fun fmtWith(par: Function) = unparse(map(x => par(concat(x)), parse(par))) fun stepr(w, ps_tw_tl) = if ps_tw_tl is [ps, tw, tl] then let tot_width = w + 1 + tw diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/power.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/power.mls index c8de74388c..a00335f39d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/power.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/power.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -115,7 +117,7 @@ fun integral(fs_) = lazy of () => Pc(0, int1(fs_, 1)) -fun integralLz(fs_) = +fun integralLz(fs_ : Function) = fun int1(fss, n) = lazy of () => if force(fss) is Pz then Pz Pc(f, fs_) then Pc(f/n, int1(fs_, n + 1)) @@ -139,7 +141,7 @@ fun cosx() = minusPs((lazy of () => Pc(1, lazy of () => Pz)), integral(integralL fun sinx() = integral(minusPs((lazy of () => Pc(1, lazy of () => Pz)), integralLz(sinx))) -fun testPower_nofib(p) = +fun testPower_nofib(p): List[Num] = extract(p, minusPs(sinx(), sqrtPs(minusPs(fromIntegerPs(1), powerPs(cosx(), 2))))); extract(p, minusPs(divPs(sinx(), cosx()), revert(integral(divPs(fromIntegerPs(1), addPs(fromIntegerPs(1), powerPs(x_(), 2))))))); extract(p, ts()); diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/pretty.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/pretty.mls index e8a03507a3..94058d7d40 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/pretty.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/pretty.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -69,10 +71,10 @@ fun cShow(seq) = flatten(0, true, seq, Nil) data class MkPrettyRep(cseq: CSeq, n: Int, b1: Bool, b2: Bool) -fun ppShow(width, p) = if p(width, false) is +fun ppShow(width, p: Function) = if p(width, false) is MkPrettyRep(seq, ll, emp, sl) then cShow(seq) -fun ppUnformatted(p) = if p(80, false) is +fun ppUnformatted(p: Function) = if p(80, false) is MkPrettyRep(seq, ll, emp, sl) then cShow(seq) fun ppNil(width, is_vert) = MkPrettyRep(cNil, 0, true, width >= 0) @@ -105,16 +107,16 @@ fun andL(a, b) = if a then b else false fun orL(a, b) = if a then true else b -fun ppBeside(p1, p2, width, is_vert) = if p1(width, false) is +fun ppBeside(p1: Function, p2: Function, width, is_vert) = if p1(width, false) is MkPrettyRep(seq1, ll1, emp1, sl1) and p2(width - ll1, false) is MkPrettyRep(seq2, ll2, emp2, sl2) then MkPrettyRep(cAppend(seq1, cIndent(ll1, seq2)), ll1 + ll2, andL(emp1, emp2), andL(width >= 0, andL(sl1, sl2))) -fun ppBesides(ps) = if ps is +fun ppBesides(ps: List[Function]) = if ps is Nil then ppNil else foldr1((a, b) => (c, d) => ppBeside(a, b, c, d), ps) -fun ppBesideSP(p1, p2, width, is_vert) = if p1(width, false) is MkPrettyRep(seq1, ll1, emp1, sl1) then +fun ppBesideSP(p1: Function, p2: Function, width, is_vert) = if p1(width, false) is MkPrettyRep(seq1, ll1, emp1, sl1) then let li = if emp1 then 0 else ll1 + 1 if p2(width - li, false) is MkPrettyRep(seq2, ll2, emp2, sl2) then let wi = if emp1 then 0 else 1 @@ -123,20 +125,20 @@ fun ppBesideSP(p1, p2, width, is_vert) = if p1(width, false) is MkPrettyRep(seq1 fun ppCat(ps) = if ps is Nil then ppNil else foldr1((a, b) => (c, d) => ppBesideSP(a, b, c, d), ps) -fun ppAbove(p1, p2, width, is_vert) = if p1(width, true) is +fun ppAbove(p1: Function, p2: Function, width, is_vert) = if p1(width, true) is MkPrettyRep(seq1, ll1, emp1, sl1) and p2(width, true) is MkPrettyRep(seq2, ll2, emp2, sl2) then let nl = if orL(emp1, emp2) then cNil else cNL MkPrettyRep(cAppend(seq1, cAppend(nl, seq2)), ll2, andL(emp1, emp2), false) -fun ppAboves(ps, a, b) = if ps is Nil then ppNil(a, b) else foldr1((a, b) => (c, d) => ppAbove(a, b, c, d), ps)(a, b) +fun ppAboves(ps: List[Function], a, b) = if ps is Nil then ppNil(a, b) else foldr1((a, b) => (c, d) => ppAbove(a, b, c, d), ps)(a, b) -fun ppNest(n, p, width, is_vert) = if +fun ppNest(n, p: Function, width, is_vert) = if is_vert and p(width - n, true) is MkPrettyRep(seq, ll, emp, sl) then MkPrettyRep(cIndent(n, seq), ll + n, emp, sl) else p(width, false) -fun ppHang(p1, n, p2, width, is_vert) = if p1(width, false) is +fun ppHang(p1: Function, n, p2: Function, width, is_vert) = if p1(width, false) is MkPrettyRep(seq1, ll1, emp1, sl1) and p2(width - (ll1 + 1), false) is MkPrettyRep(seq2, ll2, emp2, sl2) and p2(width - n, false) is MkPrettyRep(seq2_, ll2_, emp2_, sl2_) and diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/primetest.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/primetest.mls index 8b3ed2275b..1c986e080c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/primetest.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/primetest.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -16,7 +18,7 @@ fun int_val_of_string(s) = h :: t then f(t, 10 * a + int_val_of_char(h)) f(s, 0) -fun break_(p, ls) = if ls is +fun break_(p: Function, ls) = if ls is Nil then [Nil, Nil] x :: xs and p(x) then [Nil, x :: xs] @@ -115,7 +117,7 @@ fun multiTest(k, rs, n) = if ((n <= 1) || even(n)) then [n==2, rs] else mTest(k, rs) -fun doLine(cs, cont, rs) = +fun doLine(cs, cont: Function, rs) = let n = int_val_of_string(cs) if multiTest(100, rs, n) is [t, rs_] and t then "Probably prime" :: (cont(rs_)) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/scc.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/scc.mls index 58805f2a10..b9eefc716b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/scc.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/scc.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -6,7 +8,7 @@ module scc with ... -fun dfs(r, vsns, xs) = if vsns is [vs, ns] and +fun dfs(r: Function, vsns, xs) = if vsns is [vs, ns] and xs is Nil then [vs, ns] x :: xs and diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/secretary.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/secretary.mls index 87d4e567fd..0ec5a3dd2f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/secretary.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/secretary.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -13,7 +15,7 @@ fun infRand(m, s) = f(s) -fun simulate(n, m, proc) = +fun simulate(n, m, proc: Function) = fun lscomp(ls) = if ls is Nil then Nil seed :: t then proc(infRand(m, seed)) :: lscomp(t) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/treejoin.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/treejoin.mls index b0bad99e42..d7044b6aa9 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/treejoin.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/treejoin.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -8,7 +10,7 @@ module treejoin with ... fun isSpace(c) = c === " " || c === "\n" -fun isDigit(c) = +fun isDigit(c: Str) = let n = c.codePointAt(0) in n >= 48 && n <= 57 @@ -41,9 +43,9 @@ fun lookupT(k, t) = if t is Empty then None fun readInt(s) = - fun readInt_(n, cs) = if cs is + fun readInt_(n, cs: List[Str]) = if cs is c :: cs_ and - isDigit(c) then readInt_(n * 10 + c.codePointAt(0) - 48, cs_) + isDigit(c) then readInt_(n * 10 + (c as Str).codePointAt(0) - 48, cs_) else let s_ = leaveWhile(isSpace, c :: cs) in [n, s_] else let s_ = leaveWhile(isSpace, cs) in [n, s_] @@ -57,7 +59,7 @@ fun join(t1, t2, j) = if Some([d, e, f]) then insertT(c, [a, b, c, d, e], j) t1 is Node(k, l, r) then join(l, t2, join(r, t2, j)) -fun readTree(fk, s, t) = if +fun readTree(fk: Function, s, t) = if s is Nil then t readInt(s) is [f, s_] and readInt(s_) is [g, s__] and @@ -67,7 +69,7 @@ fun readTree(fk, s, t) = if readTree(fk, s___, insertT(k, e, t)) -fun testTreejoin_nofib(n) = +fun testTreejoin_nofib(n): Tree[[Int, Int, Int, Int, Int]] = let c1 = nofibStringToList of fs.readFileSync("./hkmc2/shared/src/test/mlscript/nofib/input/1500.1").toString() let c2 = nofibStringToList of fs.readFileSync("./hkmc2/shared/src/test/mlscript/nofib/input/1500.2").toString() let a = readTree(case { [xx, _, _] then xx }, c1, Empty) diff --git a/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls b/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls index 6519116f08..1fcd89f770 100644 --- a/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls +++ b/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "../Option.mls" open Option { Some, None } @@ -15,13 +17,13 @@ data class Term with let _freeVars = None // Compute free variables. Retrieve the cache if it exists. - fun freeVars = if _freeVars is + fun freeVars: Set[Str] = if _freeVars is Some(fv) then fv None then let fv = if this is Var(name) then new Set([name]) - App(lhs, rhs) then lhs.freeVars.union(rhs.freeVars) - Abs(name, body) then body.freeVars.difference(new Set([name])) + App(lhs, rhs) then (lhs as Term).freeVars.union((rhs as Term).freeVars) + Abs(name, body) then (body as Term).freeVars.difference(new Set([name])) set _freeVars = Some(fv) fv @@ -41,20 +43,20 @@ data class Term with fun equals(that) = if this is Var(name) and that is Var(name') then name === name' App(lhs, rhs) and that is App(lhs', rhs') then - lhs.equals(lhs') && rhs.equals(rhs') + (lhs as Term).equals(lhs') && (rhs as Term).equals(rhs') Abs(Var(name), body) and that is Abs(Var(name'), body') then - name === name' && body.equals(body') + name === name' && (body as Term).equals(body') else false // Pretty-print the term. fun show = if this is Var(name) then name - App(Abs as lhs, rhs) then "(" + lhs.show + ") " + rhs.show - App(lhs, App as rhs) then lhs.show + " (" + rhs.show + ")" - App(lhs, rhs) then lhs.show + " " + rhs.show - Abs(Var(name), body) then "λ" + name + ". " + body.show + App(Abs as lhs, rhs) then "(" + (lhs as Term).show + ") " + (rhs as Term).show + App(lhs, App as rhs) then (lhs as Term).show + " (" + (rhs as Term).show + ")" + App(lhs, rhs) then (lhs as Term).show + " " + (rhs as Term).show + Abs(Var(name), body) then "λ" + name + ". " + (body as Term).show -fun subst(term, name, target) = if term is +fun subst(term, name, target: Term) = if term is Var(name') and name === name' then target else term diff --git a/hkmc2/shared/src/test/mlscript-compile/wasm/Wasm.mls b/hkmc2/shared/src/test/mlscript-compile/wasm/Wasm.mls index 9c7dff74c2..0f7d03c61c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/wasm/Wasm.mls +++ b/hkmc2/shared/src/test/mlscript-compile/wasm/Wasm.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "binaryen" import "../Predef.mls" @@ -6,7 +8,7 @@ import "../Runtime.mls" module Wasm with ... // WebAssembly utilities -fun instantiateWasmBinary(modBuf, importObject): WebAssembly.Instance = +fun instantiateWasmBinary(modBuf, importObject): dyn = assert WebAssembly.validate of modBuf let mod = new WebAssembly.Module(modBuf) new WebAssembly.Instance(mod, importObject) @@ -15,7 +17,7 @@ fun dumpWasmModImpExp(wasmMod) = console.log of "Imports: %s", WebAssembly.Module.imports(wasmMod) console.log of "Exports: %s", WebAssembly.Module.exports(wasmMod) -fun dumpWasmInstImpExp(wasmInst) = +fun dumpWasmInstImpExp(wasmInst: dyn) = console.log of "Imports: %s", wasmInst.imports console.log of "Exports: %s", wasmInst.exports @@ -33,7 +35,7 @@ fun binaryenFmtWat(wat, foldExprs): String = mod.dispose() fmtWat -fun binaryenCompileToModule(wat, importObject): WebAssembly.Instance = +fun binaryenCompileToModule(wat, importObject): dyn = let mod = binaryen.parseText of wat, binaryen.Features.All // Note: it seems `mod.validate()` sometimes returns `1` instead of `true`; so we don't use `assert` here @@ -44,7 +46,7 @@ fun binaryenCompileToModule(wat, importObject): WebAssembly.Instance = instantiateWasmBinary of modBuf, importObject -fun unwrapWasmException(err, tag) = +fun unwrapWasmException(err: dyn, tag) = if ( err !== null && err !== undefined && @@ -55,7 +57,7 @@ fun unwrapWasmException(err, tag) = then err.getArg(tag, 0) else err -fun binaryenRunFunc(wat, importObject, func): Any = +fun binaryenRunFunc(wat, importObject, func: Function): Any = let wasmInst = binaryenCompileToModule of wat, importObject let exnTag = wasmInst.exports.mlx_exn Runtime.try_catch of From 3b0214fcdb2e6e5187f7b182e1b65475dfd0755d Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 1 Oct 2026 18:34:32 +0800 Subject: [PATCH 160/228] Fix scope transport for omitted type arguments and port Iter Keep omitted argument inference hosts at their source reference instead of rebasing shared bounds separately for each observation. Preserve ordinary transport of supplied arguments and retain lexical scope assertions. Add regressions for cross-function, nested, partial, and callback inference, and verify that exposed inputs remain unknown. Port Iter with explicit interfaces, complete the host declarations it needs, and include updated golden output. --- .../scala/hkmc2/semantics/NewResolver.scala | 23 ++--- .../shared/src/test/mlscript-compile/Iter.mls | 38 ++++---- .../src/test/mlscript/apps/IterTest.mls | 4 +- .../src/test/mlscript/decls/Prelude.mls | 18 +++- .../src/test/mlscript/newres/ArrayMethods.mls | 33 +++++++ .../src/test/mlscript/newres/Arrays.mls | 4 +- .../test/mlscript/newres/GenericMethods.mls | 2 +- .../mlscript/newres/InferenceHoleScopes.mls | 86 +++++++++++++++++++ .../test/mlscript/newres/InferenceHoles.mls | 2 +- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 10 files changed, 177 insertions(+), 37 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ArrayMethods.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/InferenceHoleScopes.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 07b08c0ac6..00c8e8e64e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -534,7 +534,7 @@ class NewResolver: // Reduce arguments in their caller environment before binding formals. // The alias guard grows only on body expansion, so nested Identity // applications reduce while unproductive recursive aliases stop. - applyAlias(loop(base, bindings, aliases, positive), TypeApplication(arguments.map(loop(_, bindings, aliases, positive)), Nil), Set.empty).getOrElse(retain) + applyAlias(loop(base, bindings, aliases, positive), TypeApplication(arguments.map(loop(_, bindings, aliases, positive))), Set.empty).getOrElse(retain) case _ => retain loop(resolution, bindings, Set.empty, positive) @@ -713,19 +713,22 @@ class NewResolver: declaredType(resolution, Map.empty) }) - /** Supplied arguments and omitted positions belong to the application scope. - * Moving a template must rebase both, even before an omitted host is needed. + /** Supplied arguments belong to the application scope and move back to the + * template's endpoint before substitution. Omitted arguments instead belong + * to the source type reference: all views must read and constrain their shared + * host in that same scope. Rebasing an omitted host with each observation's + * path would reinterpret its bounds in unrelated callers' lexical scopes. */ - private case class TypeApplication(arguments: Ls[DeclaredType], context: Ls[Marks]): + private case class TypeApplication(arguments: Ls[DeclaredType]): def move(marks: Ls[Marks])(using NewResolverState): TypeApplication = - TypeApplication(arguments.map(transportType(_, marks)), context ::: marks) - private val noTypeArguments = TypeApplication(Nil, Nil) + TypeApplication(arguments.map(transportType(_, marks))) + private val noTypeArguments = TypeApplication(Nil) private def typeArguments(source: DeclaredType, application: TypeApplication, parameters: Ls[TyParam]) (using NewResolverState): Ls[DeclaredType] = parameters.zipWithIndex.map: (parameter, index) => application.arguments.lift(index).getOrElse( - transportType(omittedType(source.resolution, parameter.sym).instantiate(source.instances), application.context)) + omittedType(source.resolution, parameter.sym).instantiate(source.instances)) private def typeBindings(source: DeclaredType, arguments: TypeApplication, parameters: Ls[TyParam]) (using NewResolverState): Map[VarSymbol, DeclaredType] = @@ -757,7 +760,7 @@ class NewResolver: follow(reference.tpe.instantiate(current.instances).withOrigin(current.origin), args.move(inverseMarks(reference.marks)), reference.marks ::: captures, next) case TypeShape.Applied(base, arguments) => - follow(declaredType(base, current), TypeApplication(arguments.map(declaredType(_, current)), Nil), captures, next) + follow(declaredType(base, current), TypeApplication(arguments.map(declaredType(_, current))), captures, next) case TypeShape.Alias(symbol, S(rhs)) => follow(declaredType(rhs, typeBindings(current, args, symbol.defn.get.tparams), current.positive), noTypeArguments, captures, next) case TypeShape.Nominal(cls) => @@ -937,7 +940,7 @@ class NewResolver: aliases + current.resolution, publish) case N => publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.AbstractDeclaration(symbol, tpe.resolution.source))) case TypeShape.Applied(base, params) => - follow(declaredType(base, current), TypeApplication(params.map(declaredType(_, current)), Nil), aliases, publish) + follow(declaredType(base, current), TypeApplication(params.map(declaredType(_, current))), aliases, publish) case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match case S(bound) => follow(selectArgument(bound.instantiate(current.instances), current.positive), noTypeArguments, aliases, publish) case N => @@ -1281,7 +1284,7 @@ class NewResolver: follow(reference.tpe.instantiate(tpe.instances).withOrigin(tpe.origin), args.move(inverseMarks(reference.marks)), reference.marks ::: captures, next) case TypeShape.Applied(base, params) => - follow(declaredType(base, tpe), TypeApplication(params.map(declaredType(_, tpe)), Nil), captures, next) + follow(declaredType(base, tpe), TypeApplication(params.map(declaredType(_, tpe))), captures, next) case TypeShape.Alias(symbol, S(rhs)) => follow(declaredType(rhs, typeBindings(tpe, args, symbol.defn.get.tparams), tpe.positive), noTypeArguments, captures, next) case TypeShape.Hole(host) => value.enter(captures) match diff --git a/hkmc2/shared/src/test/mlscript-compile/Iter.mls b/hkmc2/shared/src/test/mlscript-compile/Iter.mls index 277cf104b0..94601d4e30 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Iter.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Iter.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + #config(liftDefns: Some(LiftDefns)) import "./Option.mls" @@ -14,11 +16,11 @@ class IterableBase with fun iterator() = throw new Error("Not implemented: iterator()") // require that subclasses are immutable - fun toArray() = [...this] + fun toArray(): Array = [...this] let array = null - fun asArray = + fun asArray: Array = if array is null then set array = toArray() Object.freeze() array @@ -71,12 +73,12 @@ class IterableBase with // * Implements the `Iterable` protocol. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol -class Iterable(mk) extends IterableBase with +class Iterable(mk: () -> Iterator) extends IterableBase with fun iterator() = mk() // * Implements the `Iterator` protocol. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol -class IteratorImpl(val next) +class IteratorImpl(val next: Function) module Result with class Next(val value) with @@ -87,7 +89,7 @@ module Result with // A helper function to detect if `Symbol.iterator` is on an object, and if not, // indicate the object's class in the error message, which is useful for debugging. -fun getIterator(something) = +fun getIterator(something): Iterator = let test = something.(Symbol.iterator) if test is undefined then throw TypeError of "Not an iterable: " + if @@ -98,12 +100,12 @@ fun getIterator(something) = else prototype.constructor.name else test.call(something) -fun adaptIterable(iterable, makeNext) = Iterable of () => +fun adaptIterable(iterable, makeNext: Function) = Iterable of () => let iterator = getIterator of iterable IteratorImpl of makeNext of iterator -fun mapping(xs, op) = - xs adaptIterable of iterator => () => +fun mapping(xs, op: Function) = + xs adaptIterable of (iterator: Iterator) => () => let next = iterator.next() if next.done then Result.Done @@ -142,8 +144,8 @@ fun flattening(xss) = Result.Done else Result.Next of next.value -fun filtering(xs, op) = - xs adaptIterable of iterator => () => +fun filtering(xs, op: Function) = + xs adaptIterable of (iterator: Iterator) => () => let next = iterator.next() while next.done is false and op(next.value) is false do set next = iterator.next() @@ -174,7 +176,7 @@ fun zippingWithIndex(xs) = set i = i + 1 [x, j] -fun foldingImpl(iterator, acc, op) = +fun foldingImpl(iterator: Iterator, acc, op: Function) = let next = iterator.next() while next.done is false do set acc = op(acc, next.value) @@ -198,18 +200,18 @@ fun appended(xs, ys) = fun withAppended(xs) = _ appended(xs) -fun reduced(xs, op) = +fun reduced(xs, op: Function) = let iterator = getIterator of xs let next = iterator.next() if next.done do throw new Error("Empty iterator") foldingImpl(iterator, next.value, op) -fun folded(xs, z, op) = +fun folded(xs, z, op: Function) = let iterator = getIterator of xs foldingImpl(iterator, z, op) -fun rightFolded(xs, z, op) = +fun rightFolded(xs, z, op: Function) = let iterator = getIterator of xs fun go() = if let next = iterator.next() @@ -229,7 +231,7 @@ fun joined(xs, sep) = String(next.value) (acc, x) => acc + sep + String(x) -fun firstDefined(xs, op) = +fun firstDefined(xs, op: Function) = let iterator = getIterator of xs let next = iterator.next() let result = None @@ -240,7 +242,7 @@ fun firstDefined(xs, op) = result // `exists` is a keyword -fun some(xs, op) = +fun some(xs, op: Function) = let iterator = getIterator of xs let next = iterator.next() while next.done is false do @@ -249,7 +251,7 @@ fun some(xs, op) = false // `forall` is a keyword -fun every(xs, op) = +fun every(xs, op: Function) = let iterator = getIterator of xs let next = iterator.next() while next.done is false do @@ -257,7 +259,7 @@ fun every(xs, op) = set next = iterator.next() true -fun each(xs, op) = +fun each(xs, op: Function) = let iterator = getIterator of xs let next = iterator.next() while next.done is false do diff --git a/hkmc2/shared/src/test/mlscript/apps/IterTest.mls b/hkmc2/shared/src/test/mlscript/apps/IterTest.mls index 60c648ae22..6661a67594 100644 --- a/hkmc2/shared/src/test/mlscript/apps/IterTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/IterTest.mls @@ -115,7 +115,7 @@ open Stack { Cons, Nil } [1, 2, -3, 4, 5, 6] Iter.mapping of x => () => print(x + " was accessed"); x - Iter.some of get => get() < 0 + Iter.some of (get: () -> Int) => get() < 0 //│ > 1 was accessed //│ > 2 was accessed //│ > -3 was accessed @@ -123,7 +123,7 @@ open Stack { Cons, Nil } [1, 2, 3, -4, 5, 6] Iter.mapping of x => () => print(x + " was accessed"); x - Iter.every of get => get() > 0 + Iter.every of (get: () -> Int) => get() > 0 //│ > 1 was accessed //│ > 2 was accessed //│ > 3 was accessed diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 8e8e25fc58..f8538003f8 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -157,6 +157,13 @@ declare class Array[T](val length: Int): Array[T] with fun reverse(): Array[T] fun sort(...compare): Array[T] fun slice(...bounds): Array[T] + fun copyWithin(target: Int, start: Int, ...end): Array[T] + fun entries(): Iterator[[Int, T]] + fun keys(): Iterator[Int] + fun toReversed(): Array[T] + fun toSorted(...compare): Array[T] + fun toSpliced(start: Int, ...items): Array[T] + fun id"with"(index: Int, value: T): Array[T] fun at(index: Int): T | undefined fun fill(value: T, ...bounds): Array[T] fun splice(start: Int, ...items): Array[T] @@ -168,16 +175,24 @@ declare class Array[T](val length: Int): Array[T] with fun map[U](f: (T, Int, Array[T]) -> U): Array[U] fun forEach(f: (T, Int, Array[T]) -> Any): undefined fun filter(f: (T, Int, Array[T]) -> Bool): Array[T] + fun find(f: (T, Int, Array[T]) -> Bool, ...thisArg): T | undefined + fun findIndex(f: (T, Int, Array[T]) -> Bool, ...thisArg): Int + fun findLast(f: (T, Int, Array[T]) -> Bool, ...thisArg): T | undefined + fun findLastIndex(f: (T, Int, Array[T]) -> Bool, ...thisArg): Int + fun flat(...depth): Array[Any] + fun flatMap[U](f: (T, Int, Array[T]) -> (U | Array[U]), ...thisArg): Array[U] fun every(f: (T, Int, Array[T]) -> Bool): Bool fun some(f: (T, Int, Array[T]) -> Bool): Bool // Without an initial value, the first element becomes the accumulator, so the // accumulator type would also have to include T. Methods cannot be overloaded // by arity, so only the form with an initial value is declared. fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U + fun reduceRight[U](f: (U, T, Int, Array[T]) -> U, initial: U): U fun indexOf(value: T, ...from): Int fun lastIndexOf(value: T, ...from): Int fun includes(value: T, ...from): Bool fun toString(): Str + fun toLocaleString(...options): Str declare module Array with fun from(...args): Array[Any] fun @@ -251,12 +266,13 @@ declare module Math with trunc: Num -> Num declare module Reflect with + // Prototype objects may expose arbitrary host properties. + fun getPrototypeOf(target): dyn fun get id"set" has ownKeys - getPrototypeOf apply construct declare module console with diff --git a/hkmc2/shared/src/test/mlscript/newres/ArrayMethods.mls b/hkmc2/shared/src/test/mlscript/newres/ArrayMethods.mls new file mode 100644 index 0000000000..3a6e83b3d6 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ArrayMethods.mls @@ -0,0 +1,33 @@ +:. + +// Exercise the Array interfaces used by Iter's forwarding methods, including +// typed iterator results and callbacks checked against their declared domains. +:expect "[[2,3,3],[0,1],0,2,1,3,2,[3,2,1],[1,2,3],[1,4,3],[1,4,3],6]" +fun inspect(xs: Array[Int]) = [ + (mut [1, 2, 3]).copyWithin(0, 1) + xs.entries().next().value + xs.keys().next().value + xs.find((x, i, a) => x > 1) + xs.findIndex((x, i, a) => x > 1) + xs.findLast((x, i, a) => x > 1) + xs.findLastIndex((x, i, a) => x > 1) + xs.toReversed() + xs.toSorted() + xs.toSpliced(1, 1, 4) + xs.with(1, 4) + xs.reduceRight((acc, x, i, a) => acc + x, 0) +] +JSON.stringify(inspect([1, 2, 3])) +//│ = "[[2,3,3],[0,1],0,2,1,3,2,[3,2,1],[1,2,3],[1,4,3],[1,4,3],6]" + +:expect ["AB", "AB"] +fun flatten(xs: Array[Str]) = + let mapped = xs.flatMap((x, i, a) => [x.toUpperCase()]) + [mapped.join(""), [mapped].flat().join("")] +flatten(["a", "b"]) +//│ = ["AB", "AB"] + +:expect "Array" +fun prototypeName(value) = Reflect.getPrototypeOf(value).constructor.name +prototypeName([1]) +//│ = "Array" diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 4588c1781b..b90e7e8c6c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.167: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.174: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.175: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.188: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 0aa5070220..1994ba7da3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.167: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.174: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/newres/InferenceHoleScopes.mls b/hkmc2/shared/src/test/mlscript/newres/InferenceHoleScopes.mls new file mode 100644 index 0000000000..4c3a090cf8 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/InferenceHoleScopes.mls @@ -0,0 +1,86 @@ +:. + +// An invariant omitted argument receives constraints from both functions. +// Its bounds must keep their source scope when the result flows to another call. +:expect 2 +class Box[T](val item: T) +fun make(x): Box = Box(x) +fun read(box: Box) = box.item +fun call(x) = read(make(x)) +call(2) +//│ = 2 + +// Iterator annotations retain their declared members while inferring elements. +:expect 3 +fun getIterator(xs): Iterator = xs.(Symbol.iterator)() +fun foldingImpl(iterator: Iterator, acc, op: Function) = + let next = iterator.next() + while next.done is false do + set acc = op(acc, next.value) + set next = iterator.next() + acc +fun folded(xs, z, op: Function) = + let iterator = getIterator(xs) + foldingImpl(iterator, z, op) +folded([1, 2], 0, (x, y) => x + y) +//│ = 3 + +// A nested caller must observe the same hole through its own lexical path. +:expect [1, 2] +fun getIterator(xs): Iterator = xs.(Symbol.iterator)() +fun foldingImpl(iterator: Iterator) = iterator.next().value +fun folded(xs) = foldingImpl(getIterator(xs)) +fun flattened(xss) = + let iterableIterator = getIterator(xss) + fun skipEmptyIterables() = + let nextIterableResult = iterableIterator.next() + let nextIterator = getIterator(nextIterableResult.value) + let nextResult = nextIterator.next() + [nextIterator, nextResult.value] + skipEmptyIterables().1 +[flattened([[1]]), folded([2])] +//│ = [1, 2] + +// Partially supplied arguments move with the application; the omitted position +// remains anchored to its source reference, including inside a nested function. +:expect [1, 2] +class Pair[A, B](val first: A, val second: B) +fun make(x): Pair[Int] = Pair(1, x) +fun read(pair: Pair[Int]) = [pair.first, pair.second] +fun call(x) = + fun nested() = read(make(x)) + nested() +call(2) +//│ = [1, 2] + +// Exposure still contributes unknowns to the hole. Keeping scope paths correct +// must not let one local caller narrow the public interface. +:ge +class Item(val value: Int) +class Box[T](val item: T) +fun make(x): Box = Box(x) +fun read(box: Box) = box.item.value +fun call(x) = read(make(x)) +call(Item(2)) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.62: fun read(box: Box) = box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.61: fun make(x): Box = Box(x) +//│ ║ ^ +//│ ╟── 'make' is exposed by this compilation unit. +//│ ║ l.61: fun make(x): Box = Box(x) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.60: class Box[T](val item: T) +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Inference through an explicit callback signature also transports bounds back +// from a generic callee to the caller's omitted argument (as in nofib/cse). +:expect [1, 2] +class Box[T](val item: T) +fun fold[A, B](f: (A, B) -> B, z: B, xs: Box[A]): B = f(xs.item, z) +fun call(xs: Box) = fold((x, z) => [x, z], 2, xs) +call(Box(1)) +//│ = [1, 2] diff --git a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls index 9ed49d802a..0193f874e9 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InferenceHoles.mls @@ -12,7 +12,7 @@ both(Pair([First(1), Second(2)]), Pair([Second(3), First(4)])) // TODO: preserve distinct callers through a recursive hole reference. // This hole must use ordinary inference, not fresh call-site parameter symbols. -// Template argument rebasing currently strips the caller IDs from its bounds. +// Recursive hole references currently conflate bounds from distinct callers. :fixme :expect [5, 6] class First(val first: Int) diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 0d590cb32d..386b19eb6e 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.327: fun +//│ ║ l.343: fun //│ ║ ^^^ -//│ ║ l.328: const(c: Int): Int32 +//│ ║ l.344: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 3202d30dc05832da1ffc16ba250e850db73a3346 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 1 Oct 2026 20:32:46 +0800 Subject: [PATCH 161/228] Refine alias binding shapes in pattern definitions --- .../scala/hkmc2/semantics/Elaborator.scala | 20 ++- .../scala/hkmc2/semantics/NewResolver.scala | 59 ++++++- .../main/scala/hkmc2/semantics/Pattern.scala | 11 +- .../shared/src/test/mlscript-compile/Char.mls | 2 +- .../newres/PatternDefinitionBindings.mls | 145 ++++++++++++++++++ 5 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index a0e4a5aa97..5a81056d0e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -2489,12 +2489,13 @@ extends Importer: raise(ErrorReport(msg"Pattern definitions must have a body." -> statementLoc :: Nil)) Tree.Under() // Elaborate the pattern body with the pattern parameters. - val pat = pattern(rhs)(using ctx ++ patternParams.iterator.map(p => p.sym.name -> p.sym)) + val pat = pattern(rhs, extractionParams.map(p => p.sym.name -> p.sym).toMap)( + using ctx ++ patternParams.iterator.map(p => p.sym.name -> p.sym)) // Report all invalid variables we found in the top-level pattern. pat.variables.report - // Note that the remaining variables have not been bound to any - // `VarSymbol` yet. Thus, we need to pair them with the extraction - // parameters. We only report warnings for unbound variables + // Pair the remaining variables with the extraction parameters. + // Guarded aliases already use these symbols so their elaborated + // references remain valid. We only report warnings for unbound variables // because they are harmless. Variables used in guard conditions // (from `where` clauses) are not considered useless. val guardedNames = pat.varNamesUsedInGuards @@ -2511,6 +2512,7 @@ extends Importer: val pd = PatternDef(owner, patSym, sym, tps, allParams, patternParams, extractionParams, pat, annotations)(statementLoc) patSym.defn = S(pd) + if newResolution then checkPatternDefinition(pd) pd case k: (Mod.type | Obj.type) => val modSym = td.symbol.asInstanceOf[ModuleOrObjectSymbol] // TODO: improve `asInstanceOf` @@ -2770,7 +2772,9 @@ extends Importer: case N => candidate.map(_.ref(id)) case S(elem) => S(elem.ref(id)) - def pattern(t: Tree): Ctxl[Pattern] = + def pattern(t: Tree): Ctxl[Pattern] = pattern(t, Map.empty) + + private def pattern(t: Tree, extractionBindings: Map[Str, VarSymbol]): Ctxl[Pattern] = import ucs.{Ctor, unapply, error}, ucs.extractors.*, Keyword.*, Pattern.*, InvalidReason.* /** String range bounds must be single characters. */ def isInvalidStringBounds(lo: StrLit, hi: StrLit)(using Raise): Bool = @@ -2811,7 +2815,9 @@ extends Importer: /** Elaborate arrow patterns like `p => t`. Meanwhile, report all invalid * variables we found in `p`. */ def arrow(lhs: Tree, rhs: Tree): Ctxl[Pattern] = - val pattern = go(lhs) + // A transform consumes its inner bindings; they are not extraction + // parameters of the surrounding definition, even if their names coincide. + val pattern = this.pattern(lhs) // The symbol allocated here will be bound in `split` to the values // destructed from the scrutinee. val variables = pattern.variables.allocate @@ -2924,7 +2930,7 @@ extends Importer: case _: Tree => Chain(go(p), go(q)) case p where t => val q = go(p) - Guarded(q, term(t, Trm)(using ctx ++ q.variables.allocate)) + Guarded(q, term(t, Trm)(using ctx ++ q.variables.allocate(extractionBindings))) case Under() => Pattern.Wildcard().withLocOf(t) // Singleton blocks like `{1}`. case Block(p :: Nil) => go(p) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 00c8e8e64e..141f3b2b8c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1920,6 +1920,17 @@ class NewResolver: fromBMS(bms, FlowSymbol.pat()(using rstate.owner), sh.markss, valuePattern, lhs, _ => (), false) case sh: TermShape => valuePattern(sh) + /** Pattern bodies are resolved before their runtime matching code is generated. + * Check their bindings against an arbitrary input, so a local caller cannot + * supply undeclared assumptions to guards or transformations. + */ + def checkPatternDefinition(definition: PatternDef)(using NewResolverState): Unit = + val source = Term.MemberRef(definition.bsym)(new syntax.Tree.Ident(definition.bsym.nme).withLocOf(definition), + FlowSymbol.pat()(using rstate.owner)) + val unknown = UnknownValueShape(source)(ShapeProvenance( + msg"Pattern '${definition.sym.nme}' accepts values of unknown shape." -> definition.toLoc :: Nil)) + matchShapePat(unknown, definition.pattern)(_ => ()) + /** Propagate possible values to pattern bindings. Constructor tests filter by * nominal class; guards and literal tests may conservatively retain shapes. * Both scrutinee and constructor shapes can arrive after this registration. */ @@ -1929,6 +1940,21 @@ class NewResolver: listenInstanceViews(value)(matchShapePat(_, pattern)(matched)) return case _ => () + // Some pattern outputs (notably concatenations and rebuilt constructors) + // need a separate output interface. Do not reuse the input's fields for them. + def unknownOutput: UnknownValueShape = UnknownValueShape(Term.Error().withLocOf(pattern))(ShapeProvenance( + msg"The output shape of this pattern is not yet known." -> pattern.toLoc :: Nil)) + def preservesInput(pat: Pattern): Bool = pat match + case Pattern.Wildcard() | Pattern.Literal(_) | Pattern.Range(_, _, _) | Pattern.Negation(_) => true + case Pattern.Alias(inner, _) => preservesInput(inner) + case Pattern.Guarded(inner, _) => preservesInput(inner) + case Pattern.Annotated(inner, _) => preservesInput(inner) + case ctor: Pattern.Constructor => + ctor.resolvedTargets.nonEmpty && ctor.resolvedTargets.forall: + case _: ClassSymbol | _: ModuleOrObjectSymbol => true + case _ => false + && ctor.arguments.forall(_.forall(preservesInput)) + case _ => false pattern match case al @ Pattern.Alias(pat, _) => matchShapePat(shape, pat): sh => @@ -1937,14 +1963,41 @@ class NewResolver: al.symbolOption.foreach: symbol => publishActivated(symbol, sh) matched(sh) - case Pattern.Wildcard() | Pattern.Literal(_) => matched(shape) + case Pattern.Wildcard() | Pattern.Literal(_) | Pattern.Range(_, _, _) => matched(shape) case Pattern.Chain(left, right) => matchShapePat(shape, left)(sh => matchShapePat(sh, right)(matched)) case Pattern.Composition(false, left, right) => - matchShapePat(shape, left)(sh => matchShapePat(sh, right)(matched)) + // Conjunction tests both sides against the input and produces a pair; + // only a chain feeds one pattern's output into the next pattern. + matchShapePat(shape, left)(_ => ()) + matchShapePat(shape, right)(_ => ()) + matched(unknownOutput) case Pattern.Composition(true, left, right) => matchShapePat(shape, left)(matched) matchShapePat(shape, right)(matched) + case Pattern.Negation(pat) => + matchShapePat(shape, pat)(_ => ()) + matched(shape) + case Pattern.Concatenation(left, right) => + // Prefix matching and concatenating transformed results need their own + // shape rules. Still check nested bindings; no output interface is assumed. + val unknown = unknownOutput + matchShapePat(unknown, left)(_ => ()) + matchShapePat(unknown, right)(_ => ()) + matched(unknown) + case Pattern.Record(fields) => + // A field-presence test supplies no field interface by itself. Nested + // class tests can refine these unknowns before guards use their aliases. + val unknown = unknownOutput + fields.foreach((_, pat) => matchShapePat(unknown, pat)(_ => ())) + matched(unknown) + case Pattern.Transform(pat, parameters, transform) => + // The transform is lowered to a separate lambda. Its source references + // already name these parameter symbols, not the aliases used by guards. + parameters.foreach: (binding, parameter) => + pipeTerm(Term.SimpleRef(binding)(binding.id), parameter) + matchShapePat(shape, pat)(_ => ()) + listenTerm(transform)(matched) case Pattern.Guarded(pat, _) => matchShapePat(shape, pat)(matched) // Compilation-strategy annotations do not change the pattern's bindings. case Pattern.Annotated(pat, _) => matchShapePat(shape, pat)(matched) @@ -1984,7 +2037,7 @@ class NewResolver: // from a malformed class (filtered by classPattern above). softAssert(false, "Matched constructor is missing its field") member(sh.getMember(bms.nme), TypeSubstitution.empty) - matched(sh) + matched(if fs.forall((_, pat) => preservesInput(pat)) then sh else unknownOutput) def narrow(sh: TermShape)(using NewResolverState): Unit = sh match case Marked(unknown: UnknownValueShape, _) => // A class test can succeed for an external input. Its fields then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index 3519cb9a8a..a133060a25 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -43,7 +43,12 @@ object Pattern: invalidVars: Ls[(Pattern.Alias, InvalidReason)] ): /** Allocate symbols for all variables. */ - def allocate(using State, TraceLogger): Seq[(Str, VarSymbol)] = + def allocate(using State, TraceLogger): Seq[(Str, VarSymbol)] = allocate(Map.empty) + + /** Extraction parameters already own their binding symbols. Guards must use + * those symbols from the start, before their references are elaborated. + */ + def allocate(preallocated: Map[Str, VarSymbol])(using State, TraceLogger): Seq[(Str, VarSymbol)] = varMap.iterator.map: (name, aliases) => // We need to assign the same symbol to the same name. But I realize // that the following cases will break this constraint: `(x where x > 0) @@ -54,8 +59,10 @@ object Pattern: val symbols = aliases.iterator.flatMap(_.symbolOption).toSet // TODO: The above edge case would fail the following assertion. assert(symbols.size <= 1) + preallocated.get(name).foreach: expected => + assert(symbols.forall(_ is expected), "Guard bindings must retain their extraction parameter symbol") // If no symbol had been created before, create a new symbol now. - val symbol = symbols.headOption.getOrElse(VarSymbol(Ident(name), erasedType = N)) + val symbol = symbols.headOption.orElse(preallocated.get(name)).getOrElse(VarSymbol(Ident(name), erasedType = N)) aliases.foreach: alias => // For guarded patterns (`p where t`), the variables in `p` have to be // allocated before `t` is elaborated. In that case, we don't need to diff --git a/hkmc2/shared/src/test/mlscript-compile/Char.mls b/hkmc2/shared/src/test/mlscript-compile/Char.mls index beb41e01f6..657fbc5db1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Char.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Char.mls @@ -2,7 +2,7 @@ module Char with - pattern AnyChar = (Str as s where (s as Str).length is 1) => s + pattern AnyChar = (Str as s where s.length is 1) => s pattern Lowercase = "a" ..= "z" pattern Uppercase = "A" ..= "Z" pattern Letter = Lowercase | Uppercase diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls new file mode 100644 index 0000000000..864b37c64d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls @@ -0,0 +1,145 @@ +:. + +// A definition's guard sees the same narrowed alias as a direct match. +:expect ["a", "no", "no"] +pattern AnyChar = (Str as s where s.length is 1) => s +fun read(value) = if value is AnyChar(s) then s else "no" +[read("a"), read("ab"), read(1)] +//│ = ["a", "no", "no"] + +// The transformation has its own parameters, which receive the alias's shapes. +:expect "AB" +pattern Upper = Str as s => s.toUpperCase() +fun read(value) = if value is Upper(s) then s else "no" +read("ab") +//│ = "AB" + +// Class declarations supply an interface even without local pattern callers. +:expect 3 +class Box(val value: Str) +pattern Length = Box(Str as text) => text.length +fun read(value) = if value is Length(n) then n else 0 +read(Box("abc")) +//│ = 3 + +// Constructor-field signatures also flow to plain binding patterns. +:expect 3 +class Box(val value: Str) +pattern Length = Box(text) => text.length +fun read(value) = if value is Length(n) then n else 0 +read(Box("abc")) +//│ = 3 + +// Exported extraction parameters and guards must share the binding's flow. +:expect "ab" +pattern Text(s) = Str as s where s.length > 0 +fun read(value) = if value is Text(s) then s else "no" +read("ab") +//│ = "ab" + +// An untested input remains unknown, even when every local call is a string. +:ge +pattern Bad = s => s.length +fun read(value) = if value is Bad(n) then n else 0 +read("abc") +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.42: pattern Bad = s => s.length +//│ ║ ^^^^^^^^ +//│ ╟── Cannot resolve member 'length' of a value with unknown shape. +//│ ╟── Pattern 'Bad' accepts values of unknown shape. +//│ ║ l.42: pattern Bad = s => s.length +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A wrong member is rejected on the narrowed type, rather than left unresolved. +:ge +pattern Bad = Str as s => s.missingMember() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.56: pattern Bad = Str as s => s.missingMember() +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Str' does not contain member 'missingMember' +//│ ║ l.56: pattern Bad = Str as s => s.missingMember() +//│ ╙── ^^^ + +// Deferred constructor resolution must still reach guards and transformations. +:expect 4 +pattern Length = Later(Str as s where s.length > 0) => s.length +class Later(val value: Str) +fun read(value) = if value is Length(n) then n else 0 +read(Later("abcd")) +//│ = 4 + +// Each alternative supplies the same extraction symbol to its own guard. +:expect ["a", "ab", "no"] +pattern Text(s) = (Str as s where s.length is 1) | (Str as s where s.length is 2) +fun read(value) = if value is Text(s) then s else "no" +[read("a"), read("ab"), read("abc")] +//│ = ["a", "ab", "no"] + +// A transform's inner binding is distinct from the outer extraction binding +// with the same spelling. The inner s is a string; the extracted s is its length. +:expect 3 +pattern Length(s) = (Str as s => s.length) as s +fun read(value) = if value is Length(s) then s else 0 +read("abc") +//│ = 3 + +// A nested class test refines a record field before its guard and transformation. +:expect "AB" +pattern Name = ((name: Str as s where s.length > 0)) => s.toUpperCase() +fun read(value) = if value is Name(s) then s else "no" +read((name: "ab")) +//│ = "AB" + +// An outer alias receives the transformed result, not the tested input shape. +:ge +pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.96: pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Value of type 'Int' does not contain member 'toUpperCase' +//│ ║ in ../decls/Prelude.mls +//│ ║ l.123: val length: Int +//│ ╙── ^^^ + +// Conjunction produces a pair, not the string tested by its left operand. +:ge +pattern Bad = ((Str & _) as pair) => pair.toUpperCase() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.107: pattern Bad = ((Str & _) as pair) => pair.toUpperCase() +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'toUpperCase' of a value with unknown shape. +//│ ╟── The output shape of this pattern is not yet known. +//│ ║ l.107: pattern Bad = ((Str & _) as pair) => pair.toUpperCase() +//│ ╙── ^^^^^^^ + +// Rebuilding a constructor must not retain its input's generic field shape. +:ge +class Box[A](val value: A) +pattern Bad = (Box(Str as s => s.length) as box) => box.value.toUpperCase() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.119: pattern Bad = (Box(Str as s => s.length) as box) => box.value.toUpperCase() +//│ ║ ^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── The output shape of this pattern is not yet known. +//│ ║ l.119: pattern Bad = (Box(Str as s => s.length) as box) => box.value.toUpperCase() +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'toUpperCase' has no resolved target +//│ ║ l.119: pattern Bad = (Box(Str as s => s.length) as box) => box.value.toUpperCase() +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ + +// FIXME: Concatenation output interfaces are not yet inferred. +:fixme +:expect 2 +pattern Joined = ("a" ~ "b") as s => s.length +fun read(value) = if value is Joined(n) then n else 0 +read("ab") +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.134: pattern Joined = ("a" ~ "b") as s => s.length +//│ ║ ^^^^^^^^ +//│ ╟── Cannot resolve member 'length' of a value with unknown shape. +//│ ╟── The output shape of this pattern is not yet known. +//│ ║ l.134: pattern Joined = ("a" ~ "b") as s => s.length +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' From 6be0cf1afd4a16cb19d291f0f092ea3938c26143 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 1 Oct 2026 20:39:30 +0800 Subject: [PATCH 162/228] Minor --- hkmc2/shared/src/test/mlscript-compile/XML.mls | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/XML.mls b/hkmc2/shared/src/test/mlscript-compile/XML.mls index 4c78cbba31..4433912538 100644 --- a/hkmc2/shared/src/test/mlscript-compile/XML.mls +++ b/hkmc2/shared/src/test/mlscript-compile/XML.mls @@ -16,8 +16,10 @@ module XML with ... fun serializeValue(value) = if value is Str then JSON.stringify(value) - StyleAttributeValue then (value as StyleAttributeValue).toValue() - + // * TOOD: Allow just `StyleAttributeValue then value.toValue()` + // * Currently, scrutinees are not refined implicitly for resolution. + StyleAttributeValue as value then value.toValue() + fun joinAttributes(attributes) = attributes mapping of case [name, value] then name + "=" + serializeValue(value) From b2cdc1ea5a4095f7d39914f2f2ac7f2d01c09c65 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 1 Oct 2026 21:02:10 +0800 Subject: [PATCH 163/228] Migrate resolution worksheets and cover getter read effects --- doc/new-resolution-suite-migration.md | 200 +++++++++---- .../src/test/mlscript/basics/Classes.mls | 13 +- .../basics/CompanionModules_Functions.mls | 10 +- .../test/mlscript/basics/GenericClasses.mls | 25 +- .../src/test/mlscript/basics/Inheritance.mls | 9 +- .../src/test/mlscript/basics/Overloading.mls | 8 +- .../shared/src/test/mlscript/basics/Puns.mls | 7 +- .../codegen/AuxiliaryConstructors.mls | 13 +- .../src/test/mlscript/codegen/ImportedOps.mls | 3 +- .../src/test/mlscript/codegen/NoFreeze.mls | 1 + .../src/test/mlscript/codegen/PredefUsage.mls | 1 + .../src/test/mlscript/codegen/ReboundLet.mls | 1 + .../src/test/mlscript/codegen/Scoping.mls | 1 + .../mlscript/newres/GetterReadEffects.mls | 105 +++++++ .../ucs/examples/BinarySearchTree.mls | 1 + .../mlscript/ucs/examples/EitherOrBoth.mls | 11 +- .../mlscript/ucs/examples/LeftistTree.mls | 1 + .../test/mlscript/ucs/examples/ListFold.mls | 2 + .../src/test/mlscript/ucs/examples/ULC.mls | 1 + .../mlscript/ucs/general/BooleanPatterns.mls | 1 + .../mlscript/ucs/hygiene/HygienicBindings.mls | 64 +++-- .../ucs/normalization/Deduplication.mls | 266 +++++++++++------- .../InheritanceNormalization.mls | 1 + .../ucs/normalization/OverlapOfPrimitives.mls | 90 +++--- .../normalization/RecordImpliedByClass.mls | 7 +- .../mlscript/ucs/patterns/BooleansOps.mls | 11 +- .../ucs/patterns/ConjunctionPattern.mls | 1 + .../mlscript/ucs/patterns/RecordPattern.mls | 9 +- .../src/test/mlscript/ucs/patterns/String.mls | 1 + .../src/test/mlscript/ucs/syntax/Else.mls | 13 +- .../test/mlscript/ucs/syntax/SimpleUCS.mls | 7 +- .../src/test/mlscript/ups/EmptyJunctions.mls | 1 + .../mlscript/ups/RecursiveTransformations.mls | 7 +- .../ups/examples/BasicSeqStackParse.mls | 5 +- .../ups/examples/DoubleTripleList.mls | 6 +- .../test/mlscript/ups/examples/Negation.mls | 1 + .../examples/PrecedenceClimbStackParse.mls | 8 +- .../src/test/mlscript/ups/examples/Record.mls | 5 +- .../test/mlscript/ups/regex/Identifier.mls | 3 +- .../test/mlscript/ups/syntax/PatternBody.mls | 71 ++--- .../shared/src/test/mlscript/wasm/Basics.mls | 11 +- .../mlscript/wasm/DeadConstructorElim.mls | 1 + .../src/test/mlscript/wasm/DeadParamElim.mls | 1 + 43 files changed, 667 insertions(+), 337 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/GetterReadEffects.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 0b96888b33..d1753dd6e9 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -13,19 +13,19 @@ resolver invariants and regression coverage live in the [resolver design notes](new-resolution-design.md). Backend-specific constraints are in [WASM resolution](new-resolution-wasm.md). -The checked-in worksheet headers give the following status. Counts exclude shared -`.mls` configurations, new-resolution regression tests, and the 51 files under +The checked-in worksheet headers give the following status as of 2026-10-01. +Counts exclude shared `.mls` configurations, new-resolution regression tests, and the 51 files under `ucs/staging`, which the test runner excludes. | Suite | Migrated | Legacy configuration | Active files | | --- | ---: | ---: | ---: | -| basics | 61 | 29 | 90 | -| codegen | 86 | 39 | 125 | -| ucs | 52 | 18 | 70 | -| ups | 49 | 22 | 71 | +| basics | 67 | 23 | 90 | +| codegen | 93 | 33 | 126 | +| ucs | 69 | 1 | 70 | +| ups | 58 | 13 | 71 | | apps | 13 | 3 | 16 | -| Total | 261 | 111 | 372 | -| wasm (separate backend) | 19 | 2 | 21 | +| Total | 300 | 73 | 373 | +| wasm (separate backend) | 22 | 1 | 23 | All twelve `apps/parsing` worksheets use new resolution, but some implementation modules they import still use legacy resolution. Worksheet migration does not @@ -33,13 +33,65 @@ imply that its dependencies have been migrated. | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | -| Main (including quotes, UPS, and regression fixtures) | 27 | 21 | 48 | -| Applications | 8 | 12 | 20 | -| Nofib | 12 | 27 | 39 | -| WASM | 0 | 1 | 1 | -| Total | 47 | 61 | 108 | +| Main (including quotes, UPS, and regression fixtures) | 38 | 10 | 48 | +| Applications | 12 | 8 | 20 | +| Nofib | 37 | 2 | 39 | +| WASM | 1 | 0 | 1 | +| Total | 88 | 20 | 108 | These totals include the new-resolution `NamedFieldLibrary` regression fixture. +They count language directives in source files, not test-runner test cases. +`CachedHash` and `parsing/TreeHelpers` already use new resolution and are included +in the totals; the earlier table undercounted them. + +### Recent compilation ports + +The following 39 fixtures migrated after the previous documentation revision: + +| Suite | Migrated fixtures | +| --- | --- | +| Main | `Benchmark`, `Char`, `Iter`, `MutMap`, `ObjectBuffer`, `QuoteExample`, `Stack`, `TreeTracer`, `XML`, `ups/EvaluationContext` | +| Applications | `parsing/Rules`, `parsing/Test`, `parsing/Tree` | +| Nofib | `NofibPrelude`, `ansi`, `atom`, `awards`, `cichelli`, `circsim`, `constraints`, `cryptarithm2`, `cse`, `eliza`, `fish`, `integer`, `knights`, `lambda`, `lcss`, `life`, `mate`, `minimax`, `para`, `power`, `pretty`, `primetest`, `scc`, `secretary`, `treejoin` | +| WASM | `wasm/Wasm` | + +These ports add exposed callback, collection, and nominal interfaces. The `Iter` +and `cse` investigations also fixed lexical-scope transport for omitted type +arguments; `newres/InferenceHoleScopes` records the regression coverage. +`Char.AnyChar` now uses `s.length` directly: named-pattern definitions propagate +shapes from class tests into alias bindings, guards, and transformation parameters. +The WASM utility fixture declares its JavaScript/WebAssembly boundary explicitly +with `dyn`; its migration does not mean that all host exports have static interfaces. + +### Worksheet retry, 2026-10-01 + +All 115 then-legacy worksheets in the suites above were tried with `:.`, after +rebuilding their compilation fixtures. Forty-one ports were retained: 6 basics, +6 codegen, 17 UCS, 9 UPS, and 3 WASM worksheets. Thirty need only the configuration +change and regenerated goldens. The other eleven need these small changes: + +| Worksheets | Source or expectation changes | +| --- | --- | +| `basics/Classes`, `ucs/syntax/SimpleUCS` | Structural interfaces for the `id` and `get` callbacks used by otherwise unconstrained parameters. | +| `ucs/normalization/Deduplication` | `Str` on the helper that selects `length`. | +| `ups/examples/BasicSeqStackParse` | Array interfaces for the parser stack and token input, and `Str` for the text input. Existing parser results and known limitations are preserved. | +| `codegen/ImportedOps`, `ucs/examples/ListFold` | Explicitly open the binary `~` operator to override the builtin. | +| `ucs/examples/EitherOrBoth` | Explicitly open `~` and correct the binary callback signature to `(A, B) -> C`; runtime checks cover all three fold branches. | +| `ups/examples/DoubleTripleList` | Use `testList!head` and `testList!tail` for the reflectively installed getters in the explicit logging check. Reads through `Cons`'s `val` interface are pure. | +| `basics/Inheritance`, `basics/CompanionModules_Functions`, `basics/Overloading` | Remove resolved `:todo`/`:fixme` expectations. The separate non-inlined overload failures remain covered. | + +Newly migrated pattern worksheets include `ucs/general/BooleanPatterns`, +`ucs/patterns/{BooleansOps,ConjunctionPattern,RecordPattern,String}`, +`ups/RecursiveTransformations`, and `ups/examples/Record`. The WASM ports are +`Basics`, `DeadConstructorElim`, and `DeadParamElim`. + +`basics/CompanionModules_Classes` passed the runner's failure policy but was not +retained: its contextual call returns a function instead of `42` because automatic +`using` insertion remains unsupported. Reviewing successful values as well as +failure markers is necessary when accepting a migration. + +Blocked worksheets keep their original source and goldens. No new failure +suppressions were added to obtain these ports. ## Migration procedure @@ -74,19 +126,21 @@ nodes must not be re-resolved, and erased types must not be queried before erasu Other parser implementation modules retain legacy resolution and need individual migration checks even though their worksheets use new resolution. -The remaining option consumers have these blockers when compiled with -`#lang(0.3.x)`: +The remaining compilation fixtures are listed below. The 2026-10-01 worksheet +retry did not re-probe these fixtures in new resolution; the interface and compiler +issues are investigation leads from earlier compilation attempts, not a claim that +every listed file still needs a compiler change. -| Fixtures | Current blockers | +| Legacy fixtures | Outstanding checks | | --- | --- | -| `Block`, `Shape` | Missing nominal members and callback arity mismatches; `Block` also needs a public interface for `showArm`. | -| `Iter`, `MutMap`, `ups/EvaluationContext` | Missing public parameter interfaces and unresolved member selections. | -| `FingerTreeList` | Compilation exceeds the 25-second test limit. Tuple literals with several spreads, such as `concatMiddle`'s `[...ay1, ...middle, ...ax2]`, produce one candidate per combination of operand candidates, and each is matched again by `toNodes`. Candidate sets compare shapes structurally, which rehashes these deep shapes; identity-based candidate storage is the next step. Already fixed on branch `LPTK/new-resolution-no-hashing`. | -| `parsing/Extension`, `ParseRule`, `Test` | Selections on values imported from legacy-resolution modules, such as `Parser.tracer`, have no resolved target. `TreeHelpers` compiles standalone; its consumers remain to be checked. | -| `parsing/Lexer` | Calls with trailing contextual parameters leave function values where tokens are expected. The binary `~` is explicitly imported, as required to override the builtin under new open semantics. | -| `parsing/Parser` | Pattern-field flow and unresolved nominal members. | -| `parsing/ParseRuleVisualizer`, `Rules`, `parsing-web-demo/main` | Missing host/public interfaces and unresolved selections. | -| `parsing/Tree` | `JSON.stringify` has no result interface, so selecting `slice` on its result fails. | +| `Block`, `Shape` | Nominal members, callback arity, and the public interface for `showArm`. | +| `LazyArray`, `FingerTreeList`, `LazyFingerTree` | Collection interfaces and shape propagation. An earlier `FingerTreeList` probe exceeded the 25-second limit while combining tuple spreads; the candidate-storage fix is on `LPTK/new-resolution-no-hashing`. Retry against the current resolver before attributing a new timeout to that cause. | +| `Runtime`, `Rendering`, `Predef` | Runtime/host interfaces and their consumers; `Predef.use` also depends on contextual arguments. | +| `CSP`, `QuoteExample1` | Quasiquote type selections and wildcard-reference lowering. | +| `apps/Accounting`, `apps/CSV` | Public interfaces and their worksheet consumers. | +| `parsing/Extension`, `ParseRule`, `Parser`, `ParseRuleVisualizer`, `parsing-web-demo/main` | Remaining exposed interfaces and mixed-mode parser dependencies. `Rules`, `Test`, `Tree`, and `TreeHelpers` are already migrated. | +| `parsing/Lexer` | Calls with trailing contextual parameters leave function values where tokens are expected. The binary `~` is explicitly imported. | +| `nofib/lastpiece`, `nofib/sorting` | Remaining Nofib interface and flow issues; all other Nofib fixtures use new resolution. | To reproduce a blocker, temporarily add the language directive to the named compilation fixture and run `ctest ` or `catest ` as appropriate. @@ -105,22 +159,16 @@ subclass annotations, tuple transport, multiple parent steps, callback inputs, and caller separation. A local module's captured outer binder still has a separate scope failure recorded there with `:fixme`. -All 61 legacy compilation fixtures were probed individually with their dependencies -in their existing modes. No fixture was newly unblocked by this correction. -`CachedHash` and `parsing/TreeHelpers` compile standalone in new resolution both -before and after the correction; their consumer migrations remain unchecked. -`FingerTreeList` still reaches the test timeout. `Lexer` now explicitly imports -`~`, as intended by the new open semantics, and reaches the contextual-argument -blocker instead of crashing. `newres/LexerMigration.mls` covers the explicit import -and records missing argument insertion. Completing the migration requires contextual +The earlier 2026-09-30 probe of 61 legacy compilation fixtures found no additional +ports from the ancestor-constraint correction alone. Subsequent interface +annotations and the omitted-type-argument scope fix enabled the compilation ports +listed above. Do not use the old probe as the current migration inventory. + +`newres/LexerMigration.mls` covers the explicit operator import and records missing +contextual argument insertion. Completing that migration requires contextual instance lookup and argument insertion in the new resolver; passing every instance explicitly would bypass that missing feature rather than complete it. -Many fixtures also need source interfaces: for example, `QuoteExample.bind` calls -an unannotated callback, and Nofib helpers expose comparators and printers. -Check each fixture's public API and its consumers when porting it; the source -headers identify the remaining legacy fixtures. - ### Shape propagation and capture precision - **Generators:** `newres/GeneratorResults.mls` records a call whose result is @@ -161,22 +209,50 @@ headers identify the remaining legacy fixtures. and the unresolved receiver in `codegen/ObjectMethodDebinding`. Coordinate cross-block method changes with the separate implementation work before porting. +### Reflective getter instrumentation + +`ups/examples/DoubleTripleList` uses `Object.defineProperty` to replace `Cons.head` +and `Cons.tail` with getters. New resolution identifies their direct reads as +reads of the original `val` members, which are pure and may be discarded when +unused. Reflectively replacing those data properties does not change the contract +of their declared interface. This is not a getter-preservation bug. + +The migrated worksheet uses `testList!head` and `testList!tail` in its explicit +logging check to observe the JavaScript getters dynamically. Both log lines and +all existing matcher results and logs are preserved. `newres/GetterReadEffects.mls` +covers direct and abstract getters, both dynamic-selection forms, and the nominal, +structural, and abstract field interfaces whose unused reads are eliminated. +Ordinary MLscript getter reads remain effectful; an effectful getter cannot be +assumed to satisfy a pure `val` interface merely because it returns the right type. +The abstract-val case also exhibits the same elimination under legacy resolution. + ### Patterns and generated references -Complete record, conjunction/negation, string concatenation, guarded and -transforming pattern flow, and pattern values such as `.unapply`. Representative -worksheets are `ucs/general/BooleanPatterns`, `ucs/patterns/RecordPattern`, -`ucs/patterns/String`, `ups/RecursiveTransformations`, and `ups/examples/HindleyMilner`. -`Char.AnyChar` also loses its string interface before its `length` guard. - -`ucs/examples/EitherOrBoth` and the imported parser implementation expose -synthetic selections entering new resolution. Generated matcher code should retain -the source reference's completed target and receiver path through the `Lowering` -capability, without re-running lookup or cloning listener hosts with no candidates. -Transfer rules must distinguish matched inputs, bound fields, and transformed -outputs. Union alternatives, filter conjunctions successively, and avoid inventing -positive bindings for negation. Recursive patterns need cycle-aware subscriptions -and demonstrated convergence. +Named-pattern definitions now analyze an unknown input through the same shape +matcher used for direct matches. Constructor tests refine `as` bindings; guards +retain their binding symbols, and transformations receive those shapes through +their generated parameters. `newres/PatternDefinitionBindings` covers this flow, +and `Char.AnyChar` no longer needs its redundant `Str` annotation. + +This does not supply every compound pattern's output interface. Conjunctions test +both operands against the input and can produce a pair; chains feed one output +into the next match. Concatenations and rebuilt constructor outputs remain +conservative. Remaining worksheet blockers include: + +- `ups/examples/Computation`: a numeric range does not yet refine its bound + minute value enough for `toString` and `padStart`. +- `ups/examples/ListPredicates`: the transformation in a higher-order pattern + argument leaves the result of `toString` without a resolved `length` selection. +- `ucs/patterns/where`, `ups/MatchResult`, `ups/SimpleTransform`, and several regex + worksheets: pattern values and direct `.unapply`/`.unapplyStringPrefix` access. +- `ups/examples/{EvaluationContext,EvaluationContext2,HindleyMilner}`: unresolved + member selections in worksheet-local definitions, despite the migration of the + separate `ups/EvaluationContext` compilation fixture. + +Generated matcher code must retain source targets and receiver paths through +lowering. Transfer rules must distinguish matched inputs, bound fields, and +transformed outputs; negation must not export positive bindings. Recursive pattern +flow still needs cycle-aware subscriptions and demonstrated convergence. Acceptance coverage should include tuple/record extraction, nested and imported constructors, aliases, guards, transformed results, repeated references, and @@ -187,13 +263,15 @@ recursive UPS matchers. Preserve both runtime results and matcher structure. - Implement automatic contextual argument insertion for `Lexer` calls to `Token.integer`, `symbol`, and related APIs with trailing `using` lists. Operators that override builtins must be imported explicitly, as in `Lexer`. -- Complete type-argument validation (`basics/GenericClasses`), constructor-value - member lookup (`codegen/ParamClasses`, `basics/DynamicInstantiation`), and - module/call checks. Decide nominal module-forwarding compatibility for - `basics/CyclicModuleForwarders` before changing its expected outcomes. -- Extend host interfaces where needed: keyword-named `Map.set` and `Reflect.set`, - and WebAssembly exports. `Array.reduce` requires an initial value because - declared methods cannot be overloaded by arity; the form without one would need +- Complete constructor-value member lookup (`codegen/ParamClasses`, + `basics/DynamicInstantiation`) and module/call checks. `basics/GenericClasses` + now migrates with its type-argument-count diagnostics intact. Decide nominal + module-forwarding compatibility for `basics/CyclicModuleForwarders` before + changing its expected outcomes. +- Extend host interfaces where needed. `Map.set`, `Reflect.set`, and the array + methods needed by `Iter` now have declarations; statically describing arbitrary + WebAssembly exports remains separate work. `Array.reduce` requires an initial + value because declared methods cannot be overloaded by arity; the form without one would need an accumulator type that also includes the element type. `Array.concat` currently returns `Array[Any]`; improving precision needs a declared element constraint on its rest arguments. `Array.splice` must separate @@ -207,8 +285,10 @@ recursive UPS matchers. Preserve both runtime results and matcher structure. Retry legacy worksheets and compilation fixtures against the current compiler and prelude before diagnosing a blocker. Keep deferred fixtures on legacy resolution. -The WASM fixture `wasm/Wasm.mls` needs interfaces for `WebAssembly.Instance.exports` and its exposed -`wasmInst.imports` receiver. +The WASM fixture `wasm/Wasm.mls` is migrated. The remaining legacy WASM worksheet, +`wasm/Binaryen`, still fails when given the common language configuration; it +exercises JavaScript host tooling alongside backend directives and needs its +configuration and backend diagnostics reviewed separately. ## Validation and completion gates diff --git a/hkmc2/shared/src/test/mlscript/basics/Classes.mls b/hkmc2/shared/src/test/mlscript/basics/Classes.mls index ab759b9b37..e4b67f7bc6 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Classes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Classes.mls @@ -1,3 +1,4 @@ +:. :js @@ -21,7 +22,7 @@ class Foo { fun f = 1 } -class Foo(pred) { +class Foo(pred: {id: Any -> Any}) { val r = pred.id(42) fun callId(x) = pred.id(x) } @@ -30,7 +31,7 @@ class Foo(pred) { class Foo(x: Int) val y = x //│ ╔══[PARSE ERROR] Expected end of input; found indented block instead -//│ ║ l.31: val y = x +//│ ║ l.32: val y = x //│ ╙── ^^ class Foo(x: Int) { print("Hello!", x) } @@ -52,7 +53,7 @@ Foo().x :todo class Foo() extends Bar { x = 1 } //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.53: class Foo() extends Bar { x = 1 } +//│ ║ l.54: class Foo() extends Bar { x = 1 } //│ ╙── ^ @@ -60,16 +61,16 @@ class Foo() extends Bar { x = 1 } class Foo() extends Bar { val x = 1 } with val y = this.x //│ ╔══[COMPILATION ERROR] Multiple type definition bodies are not supported. -//│ ║ l.61: val y = this.x +//│ ║ l.62: val y = this.x //│ ╙── ^^^^^^^^^^^^^^ :e class Foo() extends Bar { x = 1 } with val y = this.x //│ ╔══[COMPILATION ERROR] Multiple type definition bodies are not supported. -//│ ║ l.68: val y = this.x +//│ ║ l.69: val y = this.x //│ ╙── ^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.67: class Foo() extends Bar { x = 1 } with +//│ ║ l.68: class Foo() extends Bar { x = 1 } with //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Functions.mls b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Functions.mls index 28b0f72fbc..8ebaec5676 100644 --- a/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Functions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Functions.mls @@ -1,19 +1,11 @@ +:. :js -:fixme // TODO: support // :sjs fun foo(x) = x + 1 module foo with val zero = foo(0) -//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'foo' -//│ ║ l.6: fun foo(x) = x + 1 -//│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ╟── with module of the same name -//│ ║ l.7: module foo with -//│ ║ ^^^^^^^^^^^^^^^ -//│ ║ l.8: val zero = foo(0) -//│ ╙── ^^^^^^^^^^^^^^^^^^^ foo(123) //│ = 124 diff --git a/hkmc2/shared/src/test/mlscript/basics/GenericClasses.mls b/hkmc2/shared/src/test/mlscript/basics/GenericClasses.mls index 7c4b5acf87..b4241eb0b3 100644 --- a/hkmc2/shared/src/test/mlscript/basics/GenericClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/GenericClasses.mls @@ -1,3 +1,4 @@ +:. class Foo[A] @@ -28,15 +29,27 @@ class Foo[A] with :e class Foo[A](x: Foo[A, A]) -//│ ╔══[COMPILATION ERROR] Expected 1 type arguments, got 2 -//│ ║ l.30: class Foo[A](x: Foo[A, A]) -//│ ╙── ^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.31: class Foo[A](x: Foo[A, A]) +//│ ║ ^^^^^^^^^ +//│ ╟── Type 'Foo' accepts at most 1 type argument, but got 2. +//│ ║ l.31: class Foo[A](x: Foo[A, A]) +//│ ║ ^^^^^^^^^ +//│ ╟── The type parameters are declared here. +//│ ║ l.31: class Foo[A](x: Foo[A, A]) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ :e class Foo[A](x: Bar[A]) class Bar(x: Foo[Int]) -//│ ╔══[COMPILATION ERROR] Expected no type arguments, got 1 -//│ ║ l.36: class Foo[A](x: Bar[A]) -//│ ╙── ^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in type application +//│ ║ l.43: class Foo[A](x: Bar[A]) +//│ ║ ^^^^^^ +//│ ╟── Type 'Bar' accepts at most 0 type arguments, but got 1. +//│ ║ l.43: class Foo[A](x: Bar[A]) +//│ ║ ^^^^^^ +//│ ╟── The type parameters are declared here. +//│ ║ l.44: class Bar(x: Foo[Int]) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls b/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls index 5b191127c3..eeb7b5f9e8 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls @@ -1,3 +1,4 @@ +:. :js @@ -8,14 +9,8 @@ class Foo with object Bar extends Foo -:todo // recognize fields that are inherited from superclasses +:expect [10, 11] [Bar.x, Bar.foo] -//│ ╔══[COMPILATION ERROR] Object 'Bar' does not contain member 'x' -//│ ║ l.12: [Bar.x, Bar.foo] -//│ ╙── ^^ -//│ ╔══[COMPILATION ERROR] Object 'Bar' does not contain member 'foo' -//│ ║ l.12: [Bar.x, Bar.foo] -//│ ╙── ^^^^ //│ = [10, 11] let b = id(Bar) in [b.x, b.foo] diff --git a/hkmc2/shared/src/test/mlscript/basics/Overloading.mls b/hkmc2/shared/src/test/mlscript/basics/Overloading.mls index 2cc8de7505..2e054ba1f0 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Overloading.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Overloading.mls @@ -1,18 +1,12 @@ +:. :js :global // :d -:todo fun Foo() = 1 module Foo with { val x = 1 } -//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Foo' -//│ ║ l.8: fun Foo() = 1 -//│ ║ ^^^^^^^^^^^^^ -//│ ╟── with module of the same name -//│ ║ l.9: module Foo with { val x = 1 } -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Foo //│ = class Foo { x: 1 } diff --git a/hkmc2/shared/src/test/mlscript/basics/Puns.mls b/hkmc2/shared/src/test/mlscript/basics/Puns.mls index b379d09c0d..ba5eececc2 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Puns.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Puns.mls @@ -1,3 +1,4 @@ +:. :js @@ -47,7 +48,7 @@ if r is if r is a: x then x //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (infix operator ':'). -//│ ║ l.48: a: x then x +//│ ║ l.49: a: x then x //│ ╙── ^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -89,7 +90,7 @@ foo(:a) :todo // would mean `val a = ‹the old a›`? val =a //│ ╔══[PARSE ERROR] Expected expression after 'val' binding keyword; found '=' keyword instead -//│ ║ l.90: val =a +//│ ║ l.91: val =a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -97,7 +98,7 @@ val =a :re let =a //│ ╔══[PARSE ERROR] Expected expression after 'let' binding keyword; found '=' keyword instead -//│ ║ l.98: let =a +//│ ║ l.99: let =a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls index 34c9c92216..df5255c244 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls @@ -1,3 +1,4 @@ +:. :js @@ -117,7 +118,7 @@ class B extends A(1)(2)(3) :e class B extends A(0) //│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported -//│ ║ l.118: class B extends A(0) +//│ ║ l.119: class B extends A(0) //│ ╙── ^^^^ new B @@ -128,16 +129,16 @@ class C :e class D extends C(0) //│ ╔══[COMPILATION ERROR] Too many parameter lists for parent class -//│ ║ l.129: class D extends C(0) +//│ ║ l.130: class D extends C(0) //│ ╙── ^^^^ :e new A(1)(2) with fun a = 0 //│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported -//│ ║ l.135: new A(1)(2) with +//│ ║ l.136: new A(1)(2) with //│ ║ ^^^^^^^^^^^^^^^^ -//│ ║ l.136: fun a = 0 +//│ ║ l.137: fun a = 0 //│ ╙── ^^^^^^^^^^^ //│ = $anon { x: 1 } @@ -146,8 +147,8 @@ new A(1)(2)(3)(4) with fun a = 0 //? = $anon { x: 1 } //│ ╔══[COMPILATION ERROR] Too many parameter lists for parent class -//│ ║ l.145: new A(1)(2)(3)(4) with +//│ ║ l.146: new A(1)(2)(3)(4) with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.146: fun a = 0 +//│ ║ l.147: fun a = 0 //│ ╙── ^^^^^^^^^^^ //│ = $anon { x: 1 } diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls index c08d02b2b6..262f9f72b2 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls @@ -1,10 +1,11 @@ +:. :js module M with fun (~) concat(a, b) = a + b -open M +open M { concat, ~ } :sjs fun foo() = diff --git a/hkmc2/shared/src/test/mlscript/codegen/NoFreeze.mls b/hkmc2/shared/src/test/mlscript/codegen/NoFreeze.mls index 408783a04a..3a9e185a20 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/NoFreeze.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/NoFreeze.mls @@ -1,3 +1,4 @@ +:. :js :ssjs diff --git a/hkmc2/shared/src/test/mlscript/codegen/PredefUsage.mls b/hkmc2/shared/src/test/mlscript/codegen/PredefUsage.mls index b4a2926f36..d03163ebce 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PredefUsage.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PredefUsage.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls b/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls index 48c5e6d59e..5b309a7a46 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/Scoping.mls b/hkmc2/shared/src/test/mlscript/codegen/Scoping.mls index 7f55899ea7..73b44f685e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Scoping.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Scoping.mls @@ -1,3 +1,4 @@ +:. :js :expect 4 diff --git a/hkmc2/shared/src/test/mlscript/newres/GetterReadEffects.mls b/hkmc2/shared/src/test/mlscript/newres/GetterReadEffects.mls new file mode 100644 index 0000000000..7b35ec7fed --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/GetterReadEffects.mls @@ -0,0 +1,105 @@ +:. + +// A source-level getter is effectful even when its result is discarded. +:expect 1 +fun test() = + let calls = 0 + class Box with + fun value: Int = + set calls += 1 + 42 + let box = new Box + box.value + calls +test() +//│ = 1 + +// Replacing a data field with a JS getter does not change its declared val +// contract. Reads through that nominal interface are pure and may be eliminated. +:expect 0 +fun test() = + let calls = 0 + class Box(val value: Int) + let box = new mut Box(42) + Object.defineProperty(box, "value", (get: () => + set calls += 1 + 42)) + box.value + calls +test() +//│ = 0 + +// Dynamic reads make no field-purity assumption and must retain getter effects. +:expect 1 +fun test() = + let calls = 0 + class Box(val value: Int) + val box: dyn = new mut Box(42) + Object.defineProperty(box, "value", (get: () => + set calls += 1 + 42)) + box.value + calls +test() +//│ = 1 + +// An explicit dynamic selection also retains effects on a nominal receiver. +:expect 1 +fun test() = + let calls = 0 + class Box(val value: Int) + let box = new mut Box(42) + Object.defineProperty(box, "value", (get: () => + set calls += 1 + 42)) + box!value + calls +test() +//│ = 1 + +// Structural field interfaces likewise describe pure reads. Passing a host +// accessor through this interface does not preserve its undeclared effects. +:expect 0 +fun test() = + let calls = 0 + val box: {value: Int} = Object.defineProperty(mut {}, "value", (get: () => + set calls += 1 + 42)) + box.value + calls +test() +//│ = 0 + +// The abstract val interface also promises a pure read. This getter violates +// that promise; its side effects cannot be relied on through the base interface. +// The same discarded-read behavior occurs under legacy resolution. +:expect 0 +fun test() = + let calls = 0 + abstract class Base with + val value: Int + class Child extends Base with + fun value: Int = + set calls += 1 + 42 + val box: Base = new Child + box.value + calls +test() +//│ = 0 + +// An abstract getter interface preserves effects when dispatching to its child. +:expect 1 +fun test() = + let calls = 0 + abstract class Base with + fun value: Int + class Child extends Base with + fun value: Int = + set calls += 1 + 42 + val box: Base = new Child + box.value + calls +test() +//│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/BinarySearchTree.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/BinarySearchTree.mls index f056d748ca..9625d98348 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/BinarySearchTree.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/BinarySearchTree.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls index 2e448387d2..940d4c4b1d 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/StrOps.mls" @@ -5,6 +6,7 @@ import "../../../mlscript-compile/Option.mls" open Option open StrOps +open StrOps { concat2, ~ } abstract class EitherOrBoth[out A, out B]: (Left[A, B] | Right[A, B] | Both[A, B]) data class Left[out A, out B](value: A) extends EitherOrBoth[A, B] @@ -49,7 +51,7 @@ fun map[A, B, C, D](eob: EitherOrBoth[A, B], f: A -> C, g: B -> D): EitherOrBoth Right(right) then Right(g(right)) Both(left, right) then Both(f(left), g(right)) -fun fold[A, B, C](eob: EitherOrBoth[A, B], f: A -> C, g: B -> C, h: [A, B] -> C): C = +fun fold[A, B, C](eob: EitherOrBoth[A, B], f: A -> C, g: B -> C, h: (A, B) -> C): C = if eob is Left(left) then f(left) Right(right) then g(right) @@ -78,3 +80,10 @@ fun eobToString[A, B](eob: EitherOrBoth[A, B]): Str = Left(left) then "Left(" ~ StrOps.from(left) ~ ")" Right(right) then "Right(" ~ StrOps.from(right) ~ ")" Both(left, right) then "Both(" ~ StrOps.from(left) ~ ", " ~ StrOps.from(right) ~ ")" + +// The two-value branch uses a binary callback. +:expect [2, 3, 5] +[fold(Left(2), x => x, y => y, (x, y) => x + y), + fold(Right(3), x => x, y => y, (x, y) => x + y), + fold(Both(2, 3), x => x, y => y, (x, y) => x + y)] +//│ = [2, 3, 5] diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/LeftistTree.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/LeftistTree.mls index 1a6f26f4b9..568b11e80f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/LeftistTree.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/LeftistTree.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/ListFold.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/ListFold.mls index 0ddc2be534..1d86cec9e9 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/ListFold.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/ListFold.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Stack.mls" @@ -7,6 +8,7 @@ import "../../../mlscript-compile/StrOps.mls" open Stack open Option open StrOps +open StrOps { concat2, ~ } fun (|>) pipe(x, f) = f(x) diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/ULC.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/ULC.mls index d6799fd716..823dfebe53 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/ULC.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/ULC.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/BooleanPatterns.mls b/hkmc2/shared/src/test/mlscript/ucs/general/BooleanPatterns.mls index 2474405b55..d9176900c7 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/BooleanPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/BooleanPatterns.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls b/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls index 42e03ea869..c55d5c88e4 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Option.mls" @@ -40,16 +41,27 @@ fun h1(a) = a is None then 0 //│ Normalized: //│ > ‹if|while› -//│ > a is Option.Some(arg$Some$0$) and -//│ > let x = tmp:arg$Some$0$⁰ -//│ > x is Left(arg$Left$0$) and +//│ > let tmp:scrut = a⁰ +//│ > let-split σ = +//│ > let tmp:scrut = a⁰ +//│ > tmp:scrut is member:Option⁰.Some^term:h1⁰(arg$Some$0$) and +//│ > let y = tmp:arg$Some$0$⁰ +//│ > let tmp:scrut = y⁰ +//│ > tmp:scrut is member:Right⁰^term:h1⁰(arg$Right$0$) and +//│ > let z = tmp:arg$Right$0$⁰ +//│ > else z⁰ +//│ > let tmp:scrut = a⁰ +//│ > tmp:scrut is member:Option⁰.None^term:h1⁰ then 0 +//│ > let tmp:scrut = a⁰ +//│ > tmp:scrut is member:Option⁰.None^term:h1⁰ then 0 +//│ > tmp:scrut is member:Option⁰.Some^term:h1⁰(arg$Some$0$) and +//│ > let x = tmp:arg$Some$0$¹ +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is member:Left⁰^term:h1⁰(arg$Left$0$) and //│ > let y = tmp:arg$Left$0$⁰ -//│ > else y⁰ -//│ > let y = tmp:arg$Some$0$⁰ -//│ > y is Right(arg$Right$0$) and -//│ > let z = tmp:arg$Right$0$⁰ -//│ > else z⁰ -//│ > a is Option.None then 0 +//│ > else y¹ +//│ > $σ +//│ > $σ h1(Some(Left(42))) //│ = 42 @@ -72,18 +84,30 @@ fun h2(a) = a is None then 0 //│ Normalized: //│ > ‹if|while› -//│ > a is Option.Some(arg$Some$0$) and -//│ > let x = tmp:arg$Some$0$¹ -//│ > let x' = x⁰ -//│ > x' is Left(arg$Left$0$) and +//│ > let tmp:scrut = a¹ +//│ > let-split σ = +//│ > let tmp:scrut = a¹ +//│ > tmp:scrut is member:Option⁰.Some^term:h2⁰(arg$Some$0$) and +//│ > let y = tmp:arg$Some$0$² +//│ > let y' = y² +//│ > let tmp:scrut = y'⁰ +//│ > tmp:scrut is member:Right⁰^term:h2⁰(arg$Right$0$) and +//│ > let z = tmp:arg$Right$0$¹ +//│ > else z¹ +//│ > let tmp:scrut = a¹ +//│ > tmp:scrut is member:Option⁰.None^term:h2⁰ then 0 +//│ > let tmp:scrut = a¹ +//│ > tmp:scrut is member:Option⁰.None^term:h2⁰ then 0 +//│ > tmp:scrut is member:Option⁰.Some^term:h2⁰(arg$Some$0$) and +//│ > let x = tmp:arg$Some$0$³ +//│ > let tmp:scrut = x¹ +//│ > let x' = tmp:scrut⁰ +//│ > let tmp:scrut = x'⁰ +//│ > tmp:scrut is member:Left⁰^term:h2⁰(arg$Left$0$) and //│ > let y = tmp:arg$Left$0$¹ -//│ > else y¹ -//│ > let y = tmp:arg$Some$0$¹ -//│ > let y' = y² -//│ > y' is Right(arg$Right$0$) and -//│ > let z = tmp:arg$Right$0$¹ -//│ > else z¹ -//│ > a is Option.None then 0 +//│ > else y³ +//│ > $σ +//│ > $σ // :w fun h3(x, y, f, p) = diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls index a186b3fcbb..febe2ccb32 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls @@ -1,3 +1,4 @@ +:. :js :patMatConsequentSharingThreshold 5 @@ -58,11 +59,15 @@ if x is 1 then "1" else "" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ match x⁰ +//│ let scrut, scrut1, scrut2; +//│ set scrut = x⁰; +//│ match scrut //│ 0 => -//│ match y⁰ +//│ set scrut1 = y⁰; +//│ match scrut1 //│ 0 => -//│ match z⁰ +//│ set scrut2 = z⁰; +//│ match scrut2 //│ 0 => //│ return "000" //│ 1 => @@ -94,11 +99,15 @@ if x is 1 then "1-------------------------------------------------------------------------" else "-------------------------------------------------------------------------" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ match x⁰ +//│ let scrut, scrut1, scrut2; +//│ set scrut = x⁰; +//│ match scrut //│ 0 => -//│ match y⁰ +//│ set scrut1 = y⁰; +//│ match scrut1 //│ 0 => -//│ match z⁰ +//│ set scrut2 = z⁰; +//│ match scrut2 //│ 0 => //│ return "000-------------------------------------------------------------------------" //│ 1 => @@ -129,12 +138,15 @@ let qqq = if x is 1 then "1" else "" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp; -//│ match x⁰ +//│ let scrut, scrut1, scrut2, tmp; +//│ set scrut = x⁰; +//│ match scrut //│ 0 => -//│ match y⁰ +//│ set scrut1 = y⁰; +//│ match scrut1 //│ 0 => -//│ match z⁰ +//│ set scrut2 = z⁰; +//│ match scrut2 //│ 0 => //│ set tmp = "000"; //│ end @@ -173,12 +185,15 @@ print of if x is 1 then "1" else "" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp; -//│ match x⁰ +//│ let scrut, scrut1, scrut2, tmp; +//│ set scrut = x⁰; +//│ match scrut //│ 0 => -//│ match y⁰ +//│ set scrut1 = y⁰; +//│ match scrut1 //│ 0 => -//│ match z⁰ +//│ set scrut2 = z⁰; +//│ match scrut2 //│ 0 => //│ set tmp = "000"; //│ return Predef⁰.print⁰(tmp) @@ -218,11 +233,15 @@ fun foo(x, y, z) = else "" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define foo⁰ as fun foo¹(x, y, z) { -//│ match x +//│ let scrut, scrut1, scrut2; +//│ set scrut = x; +//│ match scrut //│ 0 => -//│ match y +//│ set scrut1 = y; +//│ match scrut1 //│ 0 => -//│ match z +//│ set scrut2 = z; +//│ match scrut2 //│ 0 => //│ return "000" //│ 1 => @@ -258,54 +277,66 @@ print of if y is 2 then "_2_" else "___" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp; -//│ match x⁰ -//│ 0 => -//│ match y⁰ +//│ let scrut, scrut1, scrut2, scrut3, scrut4, scrut5, scrut6, tmp, tmp1, tmp2; +//│ set scrut = x⁰; +//│ block σ$x: +//│ block σ: +//│ match scrut //│ 0 => -//│ match z⁰ -//│ 0 => -//│ set tmp = "000"; -//│ return Predef⁰.print⁰(tmp) -//│ 1 => -//│ set tmp = "0_1"; -//│ return Predef⁰.print⁰(tmp) -//│ else -//│ set tmp = "___"; -//│ return Predef⁰.print⁰(tmp) -//│ end -//│ 1 => -//│ set tmp = "01_"; -//│ return Predef⁰.print⁰(tmp) -//│ else -//│ match z⁰ -//│ 1 => -//│ set tmp = "0_1"; -//│ return Predef⁰.print⁰(tmp) -//│ else -//│ match y⁰ -//│ 2 => -//│ set tmp = "_2_"; -//│ return Predef⁰.print⁰(tmp) +//│ set scrut1 = y⁰; +//│ block σ$x1: +//│ block σ1: +//│ match scrut1 +//│ 0 => +//│ set scrut2 = z⁰; +//│ match scrut2 +//│ 0 => +//│ set tmp1 = "000"; +//│ break σ$x1 +//│ else +//│ break σ1 +//│ end //│ else -//│ set tmp = "___"; -//│ return Predef⁰.print⁰(tmp) +//│ break σ1 //│ end -//│ end -//│ end -//│ 1 => -//│ set tmp = "1__"; -//│ return Predef⁰.print⁰(tmp) -//│ else -//│ match y⁰ -//│ 2 => -//│ set tmp = "_2_"; -//│ return Predef⁰.print⁰(tmp) +//│ set scrut3 = y⁰; +//│ match scrut3 +//│ 1 => +//│ set tmp1 = "01_"; +//│ break σ$x1 +//│ else +//│ set scrut4 = z⁰; +//│ match scrut4 +//│ 1 => +//│ set tmp1 = "0_1"; +//│ break σ$x1 +//│ else +//│ break σ +//│ end +//│ end +//│ set tmp = tmp1; +//│ break σ$x //│ else -//│ set tmp = "___"; -//│ return Predef⁰.print⁰(tmp) +//│ break σ //│ end -//│ end +//│ set scrut5 = x⁰; +//│ match scrut5 +//│ 1 => +//│ set tmp = "1__"; +//│ break σ$x +//│ else +//│ set scrut6 = y⁰; +//│ match scrut6 +//│ 2 => +//│ set tmp = "_2_"; +//│ break σ$x +//│ else +//│ set tmp = "___"; +//│ break σ$x +//│ end +//│ end +//│ set tmp2 = tmp; +//│ return Predef⁰.print⁰(tmp2) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > 000 @@ -322,20 +353,29 @@ fun foo(x, y, z) = if else "___" //│ Normalized: //│ > ‹if|while› -//│ > x is 0 and -//│ > y is 0 and -//│ > z is 0 then "000" -//│ > z is 1 then "0_1" -//│ > else "___" -//│ > y is 1 then "01_" -//│ > z is 1 then "0_1" -//│ > y is 2 then "_2_" +//│ > let tmp:scrut = x¹ +//│ > let-split σ = +//│ > let tmp:scrut = x¹ +//│ > tmp:scrut is 1 then "1__" +//│ > let tmp:scrut = y¹ +//│ > tmp:scrut is 2 then "_2_" //│ > else "___" -//│ > x is 1 then "1__" -//│ > y is 2 then "_2_" -//│ > else "___" +//│ > tmp:scrut is 0 and +//│ > let tmp:scrut = y¹ +//│ > let-split σ = +//│ > let tmp:scrut = y¹ +//│ > tmp:scrut is 1 then "01_" +//│ > let tmp:scrut = z¹ +//│ > tmp:scrut is 1 then "0_1" +//│ > $σ +//│ > tmp:scrut is 0 and +//│ > let tmp:scrut = z¹ +//│ > tmp:scrut is 0 then "000" +//│ > $σ +//│ > $σ +//│ > $σ -fun expensive_call(str) = Math.pow(2, str.length) +fun expensive_call(str: Str) = Math.pow(2, str.length) :sir fun foo(x, y, z) = if x is @@ -350,31 +390,34 @@ fun foo(x, y, z) = if x is expensive_call(value) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define foo² as fun foo³(x, y, z) { -//│ let value; -//│ match x +//│ let scrut, scrut1, scrut2, value; +//│ set scrut = x; +//│ match scrut //│ 0 => -//│ match y +//│ set scrut1 = y; +//│ match scrut1 //│ 0 => -//│ match z +//│ set scrut2 = z; +//│ match scrut2 //│ 0 => //│ return "000" //│ 1 => //│ return "001" //│ else //│ set value = "hello"; -//│ return expensive_call⁰(value) +//│ return expensive_call⁰((value as! Str⁰)) //│ end //│ 1 => //│ return "01" //│ else //│ set value = "hello"; -//│ return expensive_call⁰(value) +//│ return expensive_call⁰((value as! Str⁰)) //│ end //│ 1 => //│ return "1" //│ else //│ set value = "hello"; -//│ return expensive_call⁰(value) +//│ return expensive_call⁰((value as! Str⁰)) //│ end //│ }; //│ end @@ -394,7 +437,17 @@ fun foo(x, y, z) = if x is A then "Hello" else "Goodbye" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define foo⁴ as fun foo⁵(x, y, z) { match x A⁰ => return "Hello" else return "Goodbye" end }; end +//│ define foo⁴ as fun foo⁵(x, y, z) { +//│ let scrut; +//│ set scrut = x; +//│ match scrut +//│ A⁰ => +//│ return "Hello" +//│ else +//│ return "Goodbye" +//│ end +//│ }; +//│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :sir @@ -414,11 +467,15 @@ fun foo(x, y, z) = else "Goodbye" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define foo⁶ as fun foo⁷(x, y, z) { -//│ match x +//│ let scrut, scrut1, scrut2; +//│ set scrut = x; +//│ match scrut //│ A⁰ => -//│ match y +//│ set scrut2 = y; +//│ match scrut2 //│ B⁰ => -//│ match z +//│ set scrut1 = z; +//│ match scrut1 //│ C⁰ => //│ return "Hello" //│ else @@ -440,11 +497,15 @@ fun foo(x, y, z) = if x is A and y is B and z is C then "Hello" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define foo⁸ as fun foo⁹(x, y, z) { -//│ match x +//│ let scrut, scrut1, scrut2; +//│ set scrut = x; +//│ match scrut //│ A⁰ => -//│ match y +//│ set scrut2 = y; +//│ match scrut2 //│ B⁰ => -//│ match z +//│ set scrut1 = z; +//│ match scrut1 //│ C⁰ => //│ return "Hello" //│ else @@ -484,7 +545,7 @@ let y = if true then 1 else 2 //│ else //│ set tmp = 2; //│ end -//│ set y¹ = tmp; +//│ set y² = tmp; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ y = 1 @@ -499,12 +560,14 @@ fun foo1(x, y, z) = if else print of 3 //│ Normalized: //│ > ‹if|while› +//│ > let tmp:scrut = x² //│ > let-split σ = -//│ > z is A then (member:Predef¹.)print‹member:print›(2) -//│ > else (member:Predef¹.)print‹member:print›(3) -//│ > x is A and -//│ > let tmp:scrut = member:pred⁰(x¹) -//│ > tmp:scrut is true then (member:Predef¹.)print‹member:print›(1) +//│ > let tmp:scrut = z² +//│ > tmp:scrut is member:A¹^term:foo1⁰ then member:Predef¹.print^term:foo1⁰(2) +//│ > else member:Predef¹.print^term:foo1⁰(3) +//│ > tmp:scrut is member:A¹^term:foo1⁰ and +//│ > let tmp:scrut = member:pred⁰^term:foo1⁰(x²) +//│ > tmp:scrut is true then member:Predef¹.print^term:foo1⁰(1) //│ > $σ //│ > $σ @@ -517,17 +580,20 @@ fun foo2(x, y, z) = if else print of 4 //│ Normalized: //│ > ‹if|while› +//│ > let tmp:scrut = x³ //│ > let-split σ = +//│ > let tmp:scrut = y³ //│ > let-split σ = -//│ > z is A then (member:Predef¹.)print‹member:print›(3) -//│ > else (member:Predef¹.)print‹member:print›(4) -//│ > y is A and -//│ > let tmp:scrut = member:pred⁰(y²) -//│ > tmp:scrut is true then (member:Predef¹.)print‹member:print›(2) +//│ > let tmp:scrut = z³ +//│ > tmp:scrut is member:A¹^term:foo2⁰ then member:Predef¹.print^term:foo2⁰(3) +//│ > else member:Predef¹.print^term:foo2⁰(4) +//│ > tmp:scrut is member:A¹^term:foo2⁰ and +//│ > let tmp:scrut = member:pred⁰^term:foo2⁰(y³) +//│ > tmp:scrut is true then member:Predef¹.print^term:foo2⁰(2) //│ > $σ //│ > $σ -//│ > x is A and -//│ > let tmp:scrut = member:pred⁰(x²) -//│ > tmp:scrut is true then (member:Predef¹.)print‹member:print›(1) +//│ > tmp:scrut is member:A¹^term:foo2⁰ and +//│ > let tmp:scrut = member:pred⁰^term:foo2⁰(x³) +//│ > tmp:scrut is true then member:Predef¹.print^term:foo2⁰(1) //│ > $σ //│ > $σ diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/InheritanceNormalization.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/InheritanceNormalization.mls index 34ff6df09f..e618853c1c 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/InheritanceNormalization.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/InheritanceNormalization.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls index 7f80a6dac2..765608e89c 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls @@ -1,3 +1,4 @@ +:. :js :ucs normalized @@ -5,7 +6,9 @@ fun test(x) = if x is Int then "Int" 0 then "Zero" //│ Normalized: -//│ > ‹if|while› x is Int then "Int" +//│ > ‹if|while› +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is member:Int⁰^term:test⁰ then "Int" test(0) //│ = "Int" @@ -16,10 +19,11 @@ fun test(x, p) = if x is else "qax" //│ Normalized: //│ > ‹if|while› -//│ > x is refined Int and -//│ > let tmp:scrut = p⁰(x⁰) +//│ > let tmp:scrut = x¹ +//│ > tmp:scrut is refined member:Int⁰^term:test¹ and +//│ > let tmp:scrut = p⁰(x¹) //│ > tmp:scrut is true then "foo" -//│ > x is 0 then "bar" +//│ > tmp:scrut is 0 then "bar" //│ > else "qax" //│ > else "qax" @@ -29,10 +33,11 @@ fun test(x, p) = if x is else "qax" //│ Normalized: //│ > ‹if|while› -//│ > x is refined Str and -//│ > let tmp:scrut = p¹(x¹) +//│ > let tmp:scrut = x² +//│ > tmp:scrut is refined member:Str⁰^term:test² and +//│ > let tmp:scrut = p¹(x²) //│ > tmp:scrut is true then "foo" -//│ > x is "lol" then "bar" +//│ > tmp:scrut is "lol" then "bar" //│ > else "qax" //│ > else "qax" @@ -55,11 +60,12 @@ fun test(x, p) = if x is else "other" //│ Normalized: //│ > ‹if|while› -//│ > x is refined Num and -//│ > let tmp:scrut = p²(x²) +//│ > let tmp:scrut = x³ +//│ > tmp:scrut is refined member:Num⁰^term:test³ and +//│ > let tmp:scrut = p²(x³) //│ > tmp:scrut is true then "great" -//│ > x is 2.71828 then "E" -//│ > x is 3.14159 then "PI" +//│ > tmp:scrut is 2.71828 then "E" +//│ > tmp:scrut is 3.14159 then "PI" //│ > else "other" //│ > else "other" @@ -68,11 +74,13 @@ fun test(x, p) = if x is true then "false" false then "true" //│ Normalized: -//│ > ‹if|while› x is refined Bool and -//│ > let tmp:scrut = p³(x³) -//│ > tmp:scrut is true then "great" -//│ > x is true then "false" -//│ > x is false then "true" +//│ > ‹if|while› +//│ > let tmp:scrut = x⁴ +//│ > tmp:scrut is refined member:Bool⁰^term:test⁴ and +//│ > let tmp:scrut = p³(x⁴) +//│ > tmp:scrut is true then "great" +//│ > tmp:scrut is true then "false" +//│ > tmp:scrut is false then "true" fun test(x, p) = if x is Object and p(x) then "great" @@ -81,14 +89,15 @@ fun test(x, p) = if x is false then "true" //│ Normalized: //│ > ‹if|while› -//│ > x is Object and -//│ > let tmp:scrut = p⁴(x⁴) +//│ > let tmp:scrut = x⁵ +//│ > tmp:scrut is member:Object⁰^term:test⁵ and +//│ > let tmp:scrut = p⁴(x⁵) //│ > tmp:scrut is true then "great" -//│ > x is refined Bool and -//│ > let tmp:scrut = p⁴(x⁴) +//│ > tmp:scrut is refined member:Bool⁰^term:test⁵ and +//│ > let tmp:scrut = p⁴(x⁵) //│ > tmp:scrut is true then "great, again" -//│ > x is true then "false" -//│ > x is false then "true" +//│ > tmp:scrut is true then "false" +//│ > tmp:scrut is false then "true" object Foo @@ -99,9 +108,10 @@ fun f(arg) = if Object then String(arg) //│ Normalized: //│ > ‹if|while› -//│ > arg is undefined then "undefined" -//│ > arg is null then "null" -//│ > arg is Object then member:String⁰(arg⁰) +//│ > let tmp:scrut = arg⁰ +//│ > tmp:scrut is undefined then "undefined" +//│ > tmp:scrut is null then "null" +//│ > tmp:scrut is member:Object⁰^term:f⁰ then member:String⁰^term:f⁰(arg⁰) f of undefined //│ = "undefined" @@ -119,9 +129,10 @@ fun f(arg) = if null then "null" //│ Normalized: //│ > ‹if|while› -//│ > arg is Object then member:String⁰(arg¹) -//│ > arg is undefined then "undefined" -//│ > arg is null then "null" +//│ > let tmp:scrut = arg¹ +//│ > tmp:scrut is member:Object⁰^term:f¹ then member:String⁰^term:f¹(arg¹) +//│ > tmp:scrut is undefined then "undefined" +//│ > tmp:scrut is null then "null" f of undefined //│ = "undefined" @@ -136,7 +147,9 @@ fun f(arg) = if arg is Object then String(arg) Foo then "just Foo" //│ Normalized: -//│ > ‹if|while› arg is Object then member:String⁰(arg²) +//│ > ‹if|while› +//│ > let tmp:scrut = arg² +//│ > tmp:scrut is member:Object⁰^term:f² then member:String⁰^term:f²(arg²) import "../../../mlscript-compile/Runtime.mls" @@ -144,28 +157,33 @@ fun f(arg) = if arg is Object then String(arg) Runtime.MatchSuccess then "just Foo" //│ Normalized: -//│ > ‹if|while› arg is Object then member:String⁰(arg³) +//│ > ‹if|while› +//│ > let tmp:scrut = arg³ +//│ > tmp:scrut is member:Object⁰^term:f³ then member:String⁰^term:f³(arg³) fun f(arg) = if arg is Foo then "just Foo" Object then String(arg) //│ Normalized: //│ > ‹if|while› -//│ > arg is Foo then "just Foo" -//│ > arg is Object then member:String⁰(arg⁴) +//│ > let tmp:scrut = arg⁴ +//│ > tmp:scrut is member:Foo⁰^term:f⁴ then "just Foo" +//│ > tmp:scrut is member:Object⁰^term:f⁴ then member:String⁰^term:f⁴(arg⁴) fun f(arg) = if arg is Int then "Int" Object then String(arg) //│ Normalized: //│ > ‹if|while› -//│ > arg is Int then "Int" -//│ > arg is Object then member:String⁰(arg⁵) +//│ > let tmp:scrut = arg⁵ +//│ > tmp:scrut is member:Int⁰^term:f⁵ then "Int" +//│ > tmp:scrut is member:Object⁰^term:f⁵ then member:String⁰^term:f⁵(arg⁵) fun f(arg) = if arg is Object then String(arg) Int then "Int" //│ Normalized: //│ > ‹if|while› -//│ > arg is Object then member:String⁰(arg⁶) -//│ > arg is Int then "Int" +//│ > let tmp:scrut = arg⁶ +//│ > tmp:scrut is member:Object⁰^term:f⁶ then member:String⁰^term:f⁶(arg⁶) +//│ > tmp:scrut is member:Int⁰^term:f⁶ then "Int" diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/RecordImpliedByClass.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/RecordImpliedByClass.mls index b76487e624..d14c5b86b8 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/RecordImpliedByClass.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/RecordImpliedByClass.mls @@ -1,3 +1,4 @@ +:. :js @@ -9,8 +10,10 @@ fun f(x) = else 0 //│ Normalized: //│ > ‹if|while› -//│ > x is Foo and -//│ > x is { a: tmp:field_a$, b: tmp:field_b$ } and +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is member:Foo⁰^term:f⁰ and +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is { a: tmp:field_a$, b: tmp:field_b$ } and //│ > let bv = tmp:field_b$⁰ //│ > let av = tmp:field_a$⁰ //│ > else builtin:+⁰(av⁰, bv⁰) diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls index 0e0f3bee93..de1e03cc59 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls @@ -1,3 +1,4 @@ +:. :js @@ -7,7 +8,7 @@ let n = 0 :e n is 0 | 1 //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) -//│ ║ l.8: n is 0 | 1 +//│ ║ l.9: n is 0 | 1 //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -18,13 +19,13 @@ n is (0 | 1) :e if 5 is a & b then a //│ ╔══[COMPILATION ERROR] Name not found: b -//│ ║ l.19: if 5 is a & b then a +//│ ║ l.20: if 5 is a & b then a //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.19: if 5 is a & b then a +//│ ║ l.20: if 5 is a & b then a //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (composition) -//│ ║ l.19: if 5 is a & b then a +//│ ║ l.20: if 5 is a & b then a //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -35,7 +36,7 @@ if 5 is (a & b) as c then [a, b, c] :e if 5 is ~a then a //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.36: if 5 is ~a then a +//│ ║ l.37: if 5 is ~a then a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls index 3c34ebde1b..9dd43c2f4d 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Stack.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/RecordPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/RecordPattern.mls index 54602c4250..897e4f3692 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/RecordPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/RecordPattern.mls @@ -1,3 +1,4 @@ +:. :js @@ -43,7 +44,7 @@ f(a: 1) let f = case { a: 1 } then a //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.44: { a: 1 } then a +//│ ║ l.45: { a: 1 } then a //│ ╙── ^ //│ f = fun @@ -63,10 +64,10 @@ let r = {x: 2, y : 4} if r is {x:a, y:a} then a //│ ╔══[COMPILATION ERROR] Duplicated pattern variable. -//│ ║ l.64: {x:a, y:a} then a +//│ ║ l.65: {x:a, y:a} then a //│ ║ ^ //│ ╟── The previous definition is as follows. -//│ ║ l.64: {x:a, y:a} then a +//│ ║ l.65: {x:a, y:a} then a //│ ╙── ^ //│ = 4 @@ -150,7 +151,7 @@ foo(1 :: Nil) :ge foo(1 :: "this is invalid") //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Str' at an unrelated type 'Stack.Base' -//│ ║ l.151: foo(1 :: "this is invalid") +//│ ║ l.152: foo(1 :: "this is invalid") //│ ╙── ^^^^^^^^^^^^^^^^^ //│ = "yeah we're done" diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/String.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/String.mls index 2768956a89..243b6ab7de 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/String.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/String.mls @@ -1,3 +1,4 @@ +:. :js fun parseIntegerSimple(str) = if str is diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls index 3add0d8c64..7b8d02a6e0 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls @@ -1,3 +1,4 @@ +:. :js // Else.mls @@ -76,7 +77,7 @@ fun f(x, y, z) = if x then y else z //│ ╔══[PARSE ERROR] Unexpected 'else' keyword here -//│ ║ l.77: x then y else z +//│ ║ l.78: x then y else z //│ ╙── ^^^^ @@ -107,7 +108,7 @@ else else 1 ) + 3 //│ ╔══[PARSE ERROR] Unexpected 'else' keyword here -//│ ║ l.107: else 1 +//│ ║ l.108: else 1 //│ ╙── ^^^^ //│ = 8 @@ -117,7 +118,7 @@ else 5 else 1 ) + 3 //│ ╔══[PARSE ERROR] Unexpected 'else' keyword here -//│ ║ l.117: 5 else 1 +//│ ║ l.118: 5 else 1 //│ ╙── ^^^^ //│ = 8 @@ -132,11 +133,11 @@ fun foo(x) = if x is else "I don't want to tell you what's inside the box." else "I shouldn't tell you what's inside the box." //│ ╔══[WARNING] This catch-all clause makes the following branches unreachable. -//│ ║ l.131: else "I can't tell you what's inside the box." +//│ ║ l.132: else "I can't tell you what's inside the box." //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── This branch is unreachable. -//│ ║ l.132: else "I don't want to tell you what's inside the box." +//│ ║ l.133: else "I don't want to tell you what's inside the box." //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── This branch is unreachable. -//│ ║ l.133: else "I shouldn't tell you what's inside the box." +//│ ║ l.134: else "I shouldn't tell you what's inside the box." //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/SimpleUCS.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/SimpleUCS.mls index 0ad3a832d9..fb9982713c 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/SimpleUCS.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/SimpleUCS.mls @@ -1,3 +1,4 @@ +:. :js abstract class Option[A]: Some[A] | None @@ -181,7 +182,7 @@ data class IntVal(value: Int) extends ValBase data class BoolVal(value: Bool) extends ValBase data class Lit(value: ValBase) -fun p(e, context) = +fun p(e, context: {get: Str -> Option[ValBase]}) = if e is Var(x) and context.get(x) is Some(IntVal(v)) then Left(v) @@ -199,9 +200,9 @@ fun f(x) = 0 :: Nil() then "oh" //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (operator application). -//│ ║ l.199: 0 :: +//│ ║ l.200: 0 :: //│ ║ ^^^^ -//│ ║ l.200: Nil() then "oh" +//│ ║ l.201: Nil() then "oh" //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/EmptyJunctions.mls b/hkmc2/shared/src/test/mlscript/ups/EmptyJunctions.mls index 1046092387..cd3452b0b2 100644 --- a/hkmc2/shared/src/test/mlscript/ups/EmptyJunctions.mls +++ b/hkmc2/shared/src/test/mlscript/ups/EmptyJunctions.mls @@ -1,3 +1,4 @@ +:. :js open annotations diff --git a/hkmc2/shared/src/test/mlscript/ups/RecursiveTransformations.mls b/hkmc2/shared/src/test/mlscript/ups/RecursiveTransformations.mls index 52f6a8c865..eaba4ce345 100644 --- a/hkmc2/shared/src/test/mlscript/ups/RecursiveTransformations.mls +++ b/hkmc2/shared/src/test/mlscript/ups/RecursiveTransformations.mls @@ -1,3 +1,4 @@ +:. :js @@ -64,14 +65,14 @@ arr is MultiSet(3, MultiSet(0, _)) :fixme assert arr is MultiSet(1, rest), rest //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'rest' -//│ ║ l.65: assert arr is MultiSet(1, rest), rest +//│ ║ l.66: assert arr is MultiSet(1, rest), rest //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] ReferenceError: rest is not defined :e assert arr is MultiSet as res, res //│ ═══[COMPILATION ERROR] Expected two pattern arguments, but found only zero pattern arguments. -//│ ═══[RUNTIME ERROR] Error: Assertion failed (RecursiveTransformations.mls:73) +//│ ═══[RUNTIME ERROR] Error: Assertion failed (RecursiveTransformations.mls:74) pattern Id = _ @@ -82,7 +83,7 @@ if [1, 2, 3] is //│ ═══[COMPILATION ERROR] No definition found in scope for member 'arg$Id$0$' //│ ═══[COMPILATION ERROR] No definition found in scope for member 'arg$Id$0$' //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' -//│ ║ l.81: MultiSet(2, MultiSet(1, Id(x))) do print(x) +//│ ║ l.82: MultiSet(2, MultiSet(1, Id(x))) do print(x) //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/BasicSeqStackParse.mls b/hkmc2/shared/src/test/mlscript/ups/examples/BasicSeqStackParse.mls index d02fb5fa98..2effecf635 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/BasicSeqStackParse.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/BasicSeqStackParse.mls @@ -1,3 +1,4 @@ +:. :js @@ -20,7 +21,7 @@ data class Expr with Op(lhs: Expr, op: Str, rhs: Expr) -fun go(stack, input) = if stack is +fun go(stack: Array[Any], input: Array[Str]) = if stack is [..rest, Expr as e1, "+", Expr as e2] then go([..rest, Op(e1, "+", e2)], input) [..rest, Ident as id] then @@ -38,7 +39,7 @@ fun go(stack, input) = if stack is Error: stack let Space = new RegExp of "(\\s+)" -fun parse(input) = +fun parse(input: Str) = let input = input.split(Space) \filtering of (_ !== "") \filtering of (_ \every of (_ !== " ")) diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/DoubleTripleList.mls b/hkmc2/shared/src/test/mlscript/ups/examples/DoubleTripleList.mls index e8610ea98a..ec867c5b61 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/DoubleTripleList.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/DoubleTripleList.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -102,8 +103,9 @@ let testList = 1 :: 2 :: Nil //│ testList = Cons(1, Cons(2, Nil)) withLog of () => - testList.head - testList.tail + // Observe the installed JS getters dynamically; Cons's val reads are pure. + testList!head + testList!tail () //│ > access the `head` of [1, 2] //│ > access the `tail` of [1, 2] diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/Negation.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Negation.mls index 0482b5c0ae..42982fcf68 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/Negation.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/Negation.mls @@ -1,3 +1,4 @@ +:. :js pattern NonZero = ~0 diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/PrecedenceClimbStackParse.mls b/hkmc2/shared/src/test/mlscript/ups/examples/PrecedenceClimbStackParse.mls index 81362e3792..0175a9b9bd 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/PrecedenceClimbStackParse.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/PrecedenceClimbStackParse.mls @@ -1,3 +1,4 @@ +:. :js @@ -139,12 +140,9 @@ f("*") // * Note: this fails due to `,` terminating the function scope... :e fun f(x) = assert x is str as AtPrec as prec, [str, prec] -//│ ╔══[COMPILATION ERROR] Name not found: str -//│ ║ l.141: fun f(x) = assert x is str as AtPrec as prec, [str, prec] +//│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: str +//│ ║ l.142: fun f(x) = assert x is str as AtPrec as prec, [str, prec] //│ ╙── ^^^ -//│ ╔══[COMPILATION ERROR] Name not found: prec -//│ ║ l.141: fun f(x) = assert x is str as AtPrec as prec, [str, prec] -//│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/Record.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Record.mls index d32ccc54b9..20e6f755b5 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/Record.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/Record.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -38,7 +39,7 @@ fun bmi(record) = if record is MatchOrNone(RoundedBMI) as value then value // alternatively? fun bmi'(record) = if record is MatchOrNone[RoundedBMI] as value then value //│ ╔══[COMPILATION ERROR] Unrecognized pattern (application). -//│ ║ l.39: fun bmi'(record) = if record is MatchOrNone[RoundedBMI] as value then value +//│ ║ l.40: fun bmi'(record) = if record is MatchOrNone[RoundedBMI] as value then value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ :expect Some(23) @@ -51,7 +52,7 @@ bmi of height: 1.75 :todo // Make it happen! fun bmi'(record) = if record is (@compile MatchOrNone(RoundedBMI)) as value then value //│ ╔══[COMPILATION ERROR] Pattern chaining is not supported in pattern compilation. -//│ ║ l.20: pattern RoundedBMI = CalculateBMI as Rounded +//│ ║ l.21: pattern RoundedBMI = CalculateBMI as Rounded //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ // Calculate the area of different shapes diff --git a/hkmc2/shared/src/test/mlscript/ups/regex/Identifier.mls b/hkmc2/shared/src/test/mlscript/ups/regex/Identifier.mls index dd52222cdf..7c18e5096f 100644 --- a/hkmc2/shared/src/test/mlscript/ups/regex/Identifier.mls +++ b/hkmc2/shared/src/test/mlscript/ups/regex/Identifier.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -138,7 +139,7 @@ pattern Word = Letter ~ (Word | "") :e fun isWord(x) = x is @compile Word //│ ╔══[COMPILATION ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.120: pattern Word = Letter ~ (Word | "") +//│ ║ l.121: pattern Word = Letter ~ (Word | "") //│ ╙── ^^^^^^^^^^^^^^^^^^^ // Unsupported patterns are equivalent to `Never`. diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls index c411e53c2b..2d804de251 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls @@ -1,9 +1,10 @@ +:. :js :todo // Patterns at parameter position are not supported yet. 0 => false //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found integer literal -//│ ║ l.4: 0 => false +//│ ║ l.5: 0 => false //│ ╙── ^ //│ = fun @@ -19,17 +20,17 @@ class Pair[A, B](val fst: A, val snd: B) :ucs ups pattern PlainList(pattern T) = (null => Nil) | (Pair(T as hd, PlainList(T) as tl) => hd :: tl) -//│ elaborated pattern body: null => (member:Stack⁰.)Nil‹member:Nil› ∨ Pair(T as hd‹hd⁰›, PlainList(T) as tl‹tl⁰›) => (member:Stack⁰.)Cons‹member:Cons›(hd¹, tl¹) +//│ elaborated pattern body: null => Nil‹open› ∨ member:Pair⁰(T⁰ as hd‹hd⁰›, member:PlainList⁰(T⁰) as tl‹tl⁰›) => ::‹open›(hd¹, tl¹) :ucs ups // TODO: Decide on the relative operator precedence between `as` and `=>`. // This is elaborated into `T as (x => x)`, which might not be what we want. pattern Identity(pattern T) = T as x => x -//│ elaborated pattern body: T >> (x‹x⁰› => x¹) +//│ elaborated pattern body: T¹ >> (x‹x⁰› => x¹) :ucs ups pattern Identity(pattern T) = (T as x) => x -//│ elaborated pattern body: (T as x‹x²›) => x³ +//│ elaborated pattern body: (T² as x‹x²›) => x³ pattern Identity = x => x @@ -43,43 +44,43 @@ pattern PairLike = [fst, snd] => Pair(fst, snd) :ucs ups pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] -//│ elaborated pattern body: Pair(fst‹fst²›, snd‹snd²›) ∨ [fst‹fst²›, snd‹snd²›] +//│ elaborated pattern body: member:Pair⁰(fst‹fst²›, snd‹snd²›) ∨ [fst‹fst²›, snd‹snd²›] :w pattern UselessParameter = x //│ ╔══[WARNING] Unused pattern binding: x. -//│ ║ l.49: pattern UselessParameter = x +//│ ║ l.50: pattern UselessParameter = x //│ ╙── ^ :ucs ups pattern PlainList(pattern T) = (null => Nil) | ([T as hd, PlainList(T) as tl] => hd :: tl) -//│ elaborated pattern body: null => (member:Stack⁰.)Nil‹member:Nil› ∨ [T as hd‹hd²›, PlainList(T) as tl‹tl²›] => (member:Stack⁰.)Cons‹member:Cons›(hd³, tl³) +//│ elaborated pattern body: null => Nil‹open› ∨ [T³ as hd‹hd²›, member:PlainList¹(T³) as tl‹tl²›] => ::‹open›(hd³, tl³) :ucs ups pattern Consistent = ((Int as x) | (Str as x)) => x.toString() -//│ elaborated pattern body: (Int as x‹x⁴› ∨ Str as x‹x⁴›) => x⁵.toString() +//│ elaborated pattern body: (member:Int⁰ as x‹x⁴› ∨ member:Str⁰ as x‹x⁴›) => x⁵.toString() :e pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╔══[COMPILATION ERROR] This pattern cannot be bound. -//│ ║ l.63: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.64: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.63: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.64: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╙── ^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.63: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() -//│ ╙── ^ :e pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() -//│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.75: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() -//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] This selection of member 'toString' has no resolved target +//│ ║ l.73: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() +//│ ╙── ^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] This selection of member 'toString' has no resolved target +//│ ║ l.73: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() +//│ ╙── ^^^^^^^^^^ :ucs ups pattern Negated = ~(Int | Str) -//│ elaborated pattern body: ¬(Int ∨ Str) +//│ elaborated pattern body: ¬(member:Int⁰ ∨ member:Str⁰) // FIXME: The precedence of `=>` is higher than `~`? :pt @@ -105,33 +106,33 @@ pattern BadNegated = ~(Int as x) => x :e pattern BadNegated = (~(Int as x)) => x //│ ╔══[COMPILATION ERROR] This pattern cannot be bound. -//│ ║ l.106: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.107: pattern BadNegated = (~(Int as x)) => x //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.106: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.107: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.106: pattern BadNegated = (~(Int as x)) => x +//│ elaborated pattern body: ¬(member:Int⁰ as x‹›) => x‹open› +//│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: x +//│ ║ l.107: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬(Int as x‹›) => :ucs ups :e pattern BadNegated = (~Pair(Int, x)) => x //│ ╔══[COMPILATION ERROR] This variable cannot be accessed. -//│ ║ l.120: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.121: pattern BadNegated = (~Pair(Int, x)) => x //│ ║ ^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.120: pattern BadNegated = (~Pair(Int, x)) => x -//│ ╙── ^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.120: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.121: pattern BadNegated = (~Pair(Int, x)) => x +//│ ╙── ^^^^^^^^^^^^ +//│ elaborated pattern body: ¬member:Pair⁰(member:Int⁰, x‹?›) => x‹open› +//│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: x +//│ ║ l.121: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬Pair(Int, x‹?›) => :ucs ups pattern DoubleNegated = ~(~Int) -//│ elaborated pattern body: ¬¬Int +//│ elaborated pattern body: ¬¬member:Int⁰ :ucs ups pattern Nothing = ~_ @@ -166,18 +167,18 @@ class SecretPair(a: Int, val b: Int) :ucs ups pattern KeepSecret(b) = SecretPair(_, b) -//│ elaborated pattern body: SecretPair(_, b‹b²›) +//│ elaborated pattern body: member:SecretPair⁰(_, b‹b²›) :e pattern RebuildSecret = SecretPair(_, b => b + 1) //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.172: pattern RebuildSecret = SecretPair(_, b => b + 1) +//│ ║ l.173: pattern RebuildSecret = SecretPair(_, b => b + 1) //│ ║ ^ //│ ╟── because the corresponding parameter `a` is not publicly accessible -//│ ║ l.165: class SecretPair(a: Int, val b: Int) +//│ ║ l.166: class SecretPair(a: Int, val b: Int) //│ ║ ^ //│ ╟── because rebuilding the matched `SecretPair` value requires reading every constructor argument -//│ ║ l.172: pattern RebuildSecret = SecretPair(_, b => b + 1) +//│ ║ l.173: pattern RebuildSecret = SecretPair(_, b => b + 1) //│ ║ ^^^^^^^^^^ //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible @@ -193,10 +194,10 @@ module HiddenCorner with class CantYouSeeMe(val wow: Int) val k = 0 //│ ╔══[COMPILATION ERROR] This pattern cannot be matched -//│ ║ l.188: pattern Naughty = HiddenCorner.m.m.YouCantSeeMe(n) => n + HiddenCorner.m.m.k +//│ ║ l.189: pattern Naughty = HiddenCorner.m.m.YouCantSeeMe(n) => n + HiddenCorner.m.m.k //│ ║ ^ //│ ╟── because the corresponding parameter `wow` is not publicly accessible -//│ ║ l.192: class YouCantSeeMe(wow: Int) +//│ ║ l.193: class YouCantSeeMe(wow: Int) //│ ║ ^^^ //│ ╟── Suggestion: use a wildcard pattern `_` in this position //│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible diff --git a/hkmc2/shared/src/test/mlscript/wasm/Basics.mls b/hkmc2/shared/src/test/mlscript/wasm/Basics.mls index 89b20fcc17..ed5b634513 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Basics.mls @@ -1,3 +1,4 @@ +:. :global :wasm @@ -33,7 +34,7 @@ x fun foo() = 42 foo //│ ╔══[COMPILATION ERROR] Returning function instances is not supported -//│ ║ l.34: foo +//│ ║ l.35: foo //│ ╙── ^^^ :ge @@ -41,7 +42,7 @@ class Foo with val x = 1 let f = Foo //│ ╔══[COMPILATION ERROR] Plain class references are not supported in Wasm; instantiate the class instead. -//│ ║ l.42: let f = Foo +//│ ║ l.43: let f = Foo //│ ╙── ^^^ @@ -437,7 +438,7 @@ fun bar() = 42 fun foo() = bar foo()() //│ ╔══[COMPILATION ERROR] Returning function instances is not supported -//│ ║ l.437: fun foo() = bar +//│ ║ l.438: fun foo() = bar //│ ╙── ^^^ @@ -449,8 +450,8 @@ fun takeTop(m: Int32) = m takeTop(1) //│ ═══[COMPILATION ERROR] Cannot use a value of type 'Int32' at an unrelated type 'Unknown' //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Int32' -//│ ║ l.449: takeTop(1) +//│ ║ l.450: takeTop(1) //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Wasm function `takeTop` expects result to have type `(ref null any)`, but got `i32` -//│ ║ l.448: fun takeTop(m: Int32) = m +//│ ║ l.449: fun takeTop(m: Int32) = m //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/wasm/DeadConstructorElim.mls b/hkmc2/shared/src/test/mlscript/wasm/DeadConstructorElim.mls index 508a3f7f7e..4ac5ffbb26 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/DeadConstructorElim.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/DeadConstructorElim.mls @@ -1,3 +1,4 @@ +:. :noInline :global diff --git a/hkmc2/shared/src/test/mlscript/wasm/DeadParamElim.mls b/hkmc2/shared/src/test/mlscript/wasm/DeadParamElim.mls index f338c5e93e..1513f84d04 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/DeadParamElim.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/DeadParamElim.mls @@ -1,3 +1,4 @@ +:. :noInline :global From 69158b9fb346ecce4d3891fd24a2575c596d4894 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 1 Oct 2026 21:06:28 +0800 Subject: [PATCH 164/228] Make migration documentation a current status and worklist --- doc/new-resolution-suite-migration.md | 197 ++++++-------------------- 1 file changed, 45 insertions(+), 152 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index d1753dd6e9..586bac1ccb 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -41,57 +41,6 @@ imply that its dependencies have been migrated. These totals include the new-resolution `NamedFieldLibrary` regression fixture. They count language directives in source files, not test-runner test cases. -`CachedHash` and `parsing/TreeHelpers` already use new resolution and are included -in the totals; the earlier table undercounted them. - -### Recent compilation ports - -The following 39 fixtures migrated after the previous documentation revision: - -| Suite | Migrated fixtures | -| --- | --- | -| Main | `Benchmark`, `Char`, `Iter`, `MutMap`, `ObjectBuffer`, `QuoteExample`, `Stack`, `TreeTracer`, `XML`, `ups/EvaluationContext` | -| Applications | `parsing/Rules`, `parsing/Test`, `parsing/Tree` | -| Nofib | `NofibPrelude`, `ansi`, `atom`, `awards`, `cichelli`, `circsim`, `constraints`, `cryptarithm2`, `cse`, `eliza`, `fish`, `integer`, `knights`, `lambda`, `lcss`, `life`, `mate`, `minimax`, `para`, `power`, `pretty`, `primetest`, `scc`, `secretary`, `treejoin` | -| WASM | `wasm/Wasm` | - -These ports add exposed callback, collection, and nominal interfaces. The `Iter` -and `cse` investigations also fixed lexical-scope transport for omitted type -arguments; `newres/InferenceHoleScopes` records the regression coverage. -`Char.AnyChar` now uses `s.length` directly: named-pattern definitions propagate -shapes from class tests into alias bindings, guards, and transformation parameters. -The WASM utility fixture declares its JavaScript/WebAssembly boundary explicitly -with `dyn`; its migration does not mean that all host exports have static interfaces. - -### Worksheet retry, 2026-10-01 - -All 115 then-legacy worksheets in the suites above were tried with `:.`, after -rebuilding their compilation fixtures. Forty-one ports were retained: 6 basics, -6 codegen, 17 UCS, 9 UPS, and 3 WASM worksheets. Thirty need only the configuration -change and regenerated goldens. The other eleven need these small changes: - -| Worksheets | Source or expectation changes | -| --- | --- | -| `basics/Classes`, `ucs/syntax/SimpleUCS` | Structural interfaces for the `id` and `get` callbacks used by otherwise unconstrained parameters. | -| `ucs/normalization/Deduplication` | `Str` on the helper that selects `length`. | -| `ups/examples/BasicSeqStackParse` | Array interfaces for the parser stack and token input, and `Str` for the text input. Existing parser results and known limitations are preserved. | -| `codegen/ImportedOps`, `ucs/examples/ListFold` | Explicitly open the binary `~` operator to override the builtin. | -| `ucs/examples/EitherOrBoth` | Explicitly open `~` and correct the binary callback signature to `(A, B) -> C`; runtime checks cover all three fold branches. | -| `ups/examples/DoubleTripleList` | Use `testList!head` and `testList!tail` for the reflectively installed getters in the explicit logging check. Reads through `Cons`'s `val` interface are pure. | -| `basics/Inheritance`, `basics/CompanionModules_Functions`, `basics/Overloading` | Remove resolved `:todo`/`:fixme` expectations. The separate non-inlined overload failures remain covered. | - -Newly migrated pattern worksheets include `ucs/general/BooleanPatterns`, -`ucs/patterns/{BooleansOps,ConjunctionPattern,RecordPattern,String}`, -`ups/RecursiveTransformations`, and `ups/examples/Record`. The WASM ports are -`Basics`, `DeadConstructorElim`, and `DeadParamElim`. - -`basics/CompanionModules_Classes` passed the runner's failure policy but was not -retained: its contextual call returns a function instead of `42` because automatic -`using` insertion remains unsupported. Reviewing successful values as well as -failure markers is necessary when accepting a migration. - -Blocked worksheets keep their original source and goldens. No new failure -suppressions were added to obtain these ports. ## Migration procedure @@ -111,6 +60,15 @@ suppressions were added to obtain these ports. operations may need compilation-error expectations in addition to runtime-error expectations. Do not remove an expectation without resolving the intended behavior, or rewrite a callable-constructor test to avoid the feature it tests. + Review successful values and logs as well as failure markers: unsupported + contextual calls can return function values without failing the test runner. +- Open operators explicitly when overriding builtins, as in `codegen/ImportedOps` + and `ucs/examples/ListFold`. +- Use dynamic selection for reflective host access whose effects are not described + by the static interface. For example, `ups/examples/DoubleTripleList` uses + `testList!head` to observe an installed JavaScript getter; reads through the + declared `val` interface are pure and may be discarded when unused. + `newres/GetterReadEffects.mls` covers getters, dynamic reads, and field interfaces. - Keep blocked files on their existing configuration with their source and goldens intact. Do not add per-file `:todo`, `:fixme`, `:ignore`, or resolution exceptions to claim a port. Use focused regressions for unresolved compiler bugs. @@ -119,125 +77,61 @@ suppressions were added to obtain these ports. ### Mixed-mode imports and fixture interfaces -Legacy consumers can read completed new-resolution symbols in imported signatures. -The prelude, `Option`, `apps/parsing/Token`, and `apps/parsing/Keywords` use new -resolution; all option consumers share `Option`'s constructor identities. Imported -nodes must not be re-resolved, and erased types must not be queried before erasure. -Other parser implementation modules retain legacy resolution and need individual -migration checks even though their worksheets use new resolution. - -The remaining compilation fixtures are listed below. The 2026-10-01 worksheet -retry did not re-probe these fixtures in new resolution; the interface and compiler -issues are investigation leads from earlier compilation attempts, not a claim that -every listed file still needs a compiler change. +Some migrated worksheets import legacy compilation fixtures. Check each fixture's +exposed interfaces and its consumers together. The table lists all remaining +legacy fixtures and the areas to check when retrying them; these leads do not +establish that each fixture requires a compiler change. | Legacy fixtures | Outstanding checks | | --- | --- | | `Block`, `Shape` | Nominal members, callback arity, and the public interface for `showArm`. | -| `LazyArray`, `FingerTreeList`, `LazyFingerTree` | Collection interfaces and shape propagation. An earlier `FingerTreeList` probe exceeded the 25-second limit while combining tuple spreads; the candidate-storage fix is on `LPTK/new-resolution-no-hashing`. Retry against the current resolver before attributing a new timeout to that cause. | +| `LazyArray`, `FingerTreeList`, `LazyFingerTree` | Collection interfaces, shape propagation, and termination when combining tuple spreads. | | `Runtime`, `Rendering`, `Predef` | Runtime/host interfaces and their consumers; `Predef.use` also depends on contextual arguments. | | `CSP`, `QuoteExample1` | Quasiquote type selections and wildcard-reference lowering. | | `apps/Accounting`, `apps/CSV` | Public interfaces and their worksheet consumers. | -| `parsing/Extension`, `ParseRule`, `Parser`, `ParseRuleVisualizer`, `parsing-web-demo/main` | Remaining exposed interfaces and mixed-mode parser dependencies. `Rules`, `Test`, `Tree`, and `TreeHelpers` are already migrated. | -| `parsing/Lexer` | Calls with trailing contextual parameters leave function values where tokens are expected. The binary `~` is explicitly imported. | -| `nofib/lastpiece`, `nofib/sorting` | Remaining Nofib interface and flow issues; all other Nofib fixtures use new resolution. | +| `parsing/Extension`, `ParseRule`, `Parser`, `ParseRuleVisualizer`, `parsing-web-demo/main` | Exposed interfaces and mixed-mode parser dependencies. | +| `parsing/Lexer` | Calls with trailing contextual parameters leave function values where tokens are expected. | +| `nofib/lastpiece`, `nofib/sorting` | Exposed interfaces and value flow. | To reproduce a blocker, temporarily add the language directive to the named compilation fixture and run `ctest ` or `catest ` as appropriate. The blocked fixtures retain their existing resolution mode. -The 2026-09-30 `Lexer` investigation fixed an assertion encountered before its -listed blockers: ancestor constraints applied a nominal value's caller path to -its parent annotation without first leaving the subclass's instance scope. -The trigger is passing `Lexer.string`'s `[Int, Token.Literal]` result to the -helper expecting `[Int, Token.Token]`. -Parent type arguments now enter that scope before substitution, and ancestor -constraints and inherited member lookup share the corresponding exit. Only -parameters used by the parent are captured, including when recursively observing -a legacy imported type. `newres/InheritedTypeArguments.mls` covers qualified -subclass annotations, tuple transport, multiple parent steps, callback inputs, -and caller separation. A local module's captured outer binder still has a -separate scope failure recorded there with `:fixme`. - -The earlier 2026-09-30 probe of 61 legacy compilation fixtures found no additional -ports from the ancestor-constraint correction alone. Subsequent interface -annotations and the omitted-type-argument scope fix enabled the compilation ports -listed above. Do not use the old probe as the current migration inventory. - -`newres/LexerMigration.mls` covers the explicit operator import and records missing -contextual argument insertion. Completing that migration requires contextual -instance lookup and argument insertion in the new resolver; passing every instance -explicitly would bypass that missing feature rather than complete it. - ### Shape propagation and capture precision - **Generators:** `newres/GeneratorResults.mls` records a call whose result is treated as the generator body's return value, leaving `.next` unresolved. Model the iterator result produced by lowering; use `codegen/Generators` as the worksheet acceptance case. -- **Mutation and control flow:** mutable arrays propagate initializer and write - shapes through their element parameter. Position and length tracking is out of - scope. [Type-flow design improvements](new-resolution-future-work.md) are deferred: - receiver reconstruction, inferred missing member types through nominal views, - omitted-argument caller separation, and a more precise regularity check. These - do not block the current implementation batch. The reproduced alias/projection - termination failures now have fixes and regressions; the remaining - [whole-graph convergence audit](new-resolution-future-work.md#whole-graph-convergence-audit) - and local allocation/replay bounds are documented in the - [type-flow reference](new-resolution-type-value-flow.md#canonical-references-and-termination-obligations). - For broader mutation migration, decide how reassignment changes storage's - inferred interface, including across worksheet blocks. `newres/MutationFlow.mls` - records a reassigned array checked against its initializer's tuple length; - accumulating both shapes would still reject valid later indexing. - `codegen/SetStmt` needs argument flow through its update callback. Member-variable - definitions also need work. Handler inference needs separate flows for the - receiver, values passed to resumptions, and abortive results - (`newres/HandlerResults.mls` and `codegen/ScopedBlocksAndHandlers`). Review these - designs before implementation. +- **Mutation and control flow:** `newres/MutationFlow.mls` records a reassigned + array checked against its initializer's tuple length; accumulating both shapes + would still reject valid later indexing. `codegen/SetStmt` needs argument flow + through its update callback. Member-variable definitions also need work. + Handler inference needs separate flows for the receiver, values passed to + resumptions, and abortive results (`newres/HandlerResults.mls` and + `codegen/ScopedBlocksAndHandlers`). - **Captured activations:** preserve activation identity through captured functions, constructor aliases, reconstruction of the same class, and partial construction. Existing regressions include `CtxSens`, `ValCtxSens`, `Projections`, `RecursiveEnvironment`, `SpreadCalls`, and `InterfaceExposure` - under `newres`. Do not turn unknown shapes into dynamic values or truncate - capture paths to make these cases pass. + under `newres`. A local module capturing an outer binder has a scope failure + recorded with `:fixme` in `newres/InheritedTypeArguments.mls`. Do not turn unknown + shapes into dynamic values or truncate capture paths to make these cases pass. - **Exposed nominal results:** `newres/InterfaceExposure.mls` records a returned private class whose callable methods are not checked through its nominal result annotation. Follow declared member interfaces back to their implementations without exposing initializer shapes hidden by annotations. - **Cross-block flow:** remaining cases include parameter flow in `basics/MiscArrayTests`, unfinished closures in `codegen/FirstClassFunctionTransform`, - and the unresolved receiver in `codegen/ObjectMethodDebinding`. Coordinate - cross-block method changes with the separate implementation work before porting. - -### Reflective getter instrumentation - -`ups/examples/DoubleTripleList` uses `Object.defineProperty` to replace `Cons.head` -and `Cons.tail` with getters. New resolution identifies their direct reads as -reads of the original `val` members, which are pure and may be discarded when -unused. Reflectively replacing those data properties does not change the contract -of their declared interface. This is not a getter-preservation bug. - -The migrated worksheet uses `testList!head` and `testList!tail` in its explicit -logging check to observe the JavaScript getters dynamically. Both log lines and -all existing matcher results and logs are preserved. `newres/GetterReadEffects.mls` -covers direct and abstract getters, both dynamic-selection forms, and the nominal, -structural, and abstract field interfaces whose unused reads are eliminated. -Ordinary MLscript getter reads remain effectful; an effectful getter cannot be -assumed to satisfy a pure `val` interface merely because it returns the right type. -The abstract-val case also exhibits the same elimination under legacy resolution. + and the unresolved receiver in `codegen/ObjectMethodDebinding`. ### Patterns and generated references -Named-pattern definitions now analyze an unknown input through the same shape -matcher used for direct matches. Constructor tests refine `as` bindings; guards -retain their binding symbols, and transformations receive those shapes through -their generated parameters. `newres/PatternDefinitionBindings` covers this flow, -and `Char.AnyChar` no longer needs its redundant `Str` annotation. - -This does not supply every compound pattern's output interface. Conjunctions test -both operands against the input and can produce a pair; chains feed one output -into the next match. Concatenations and rebuilt constructor outputs remain -conservative. Remaining worksheet blockers include: +Constructor tests in named-pattern definitions refine `as` bindings, including +uses in guards and transformations (`newres/PatternDefinitionBindings`). Compound +pattern outputs remain incomplete: conjunctions can produce pairs, chains feed +outputs into subsequent matches, and concatenations and rebuilt constructors need +appropriate result interfaces. Remaining worksheet blockers include: - `ups/examples/Computation`: a numeric range does not yet refine its bound minute value enough for `toString` and `padStart`. @@ -260,18 +154,18 @@ recursive UPS matchers. Preserve both runtime results and matcher structure. ### Calls, declarations, and diagnostics -- Implement automatic contextual argument insertion for `Lexer` calls to - `Token.integer`, `symbol`, and related APIs with trailing `using` lists. - Operators that override builtins must be imported explicitly, as in `Lexer`. +- Implement contextual instance lookup and automatic argument insertion for calls + with trailing `using` lists (`newres/LexerMigration.mls`). This blocks + `parsing/Lexer` and `basics/CompanionModules_Classes`: contextual calls leave + function values where completed results are expected. Passing instances + explicitly would bypass the feature these tests need to exercise. - Complete constructor-value member lookup (`codegen/ParamClasses`, - `basics/DynamicInstantiation`) and module/call checks. `basics/GenericClasses` - now migrates with its type-argument-count diagnostics intact. Decide nominal + `basics/DynamicInstantiation`) and module/call checks. Decide nominal module-forwarding compatibility for `basics/CyclicModuleForwarders` before changing its expected outcomes. -- Extend host interfaces where needed. `Map.set`, `Reflect.set`, and the array - methods needed by `Iter` now have declarations; statically describing arbitrary - WebAssembly exports remains separate work. `Array.reduce` requires an initial - value because declared methods cannot be overloaded by arity; the form without one would need +- Extend host interfaces where needed, including static descriptions of arbitrary + WebAssembly exports. `Array.reduce` requires an initial value because declared + methods cannot be overloaded by arity; the form without one would need an accumulator type that also includes the element type. `Array.concat` currently returns `Array[Any]`; improving precision needs a declared element constraint on its rest arguments. `Array.splice` must separate @@ -285,10 +179,9 @@ recursive UPS matchers. Preserve both runtime results and matcher structure. Retry legacy worksheets and compilation fixtures against the current compiler and prelude before diagnosing a blocker. Keep deferred fixtures on legacy resolution. -The WASM fixture `wasm/Wasm.mls` is migrated. The remaining legacy WASM worksheet, -`wasm/Binaryen`, still fails when given the common language configuration; it -exercises JavaScript host tooling alongside backend directives and needs its -configuration and backend diagnostics reviewed separately. +The remaining legacy WASM worksheet, `wasm/Binaryen`, fails when given the common +language configuration. It exercises JavaScript host tooling alongside backend +directives and needs its configuration and backend diagnostics reviewed separately. ## Validation and completion gates From c451b70a2d62ccab0a818c5e0d0d926a5e42b712 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 1 Oct 2026 21:08:21 +0800 Subject: [PATCH 165/228] Forward-port annotated FingerTreeList file; make minor test updates --- .../test/mlscript-compile/FingerTreeList.mls | 53 ++++++++++--------- .../src/test/mlscript/basics/Classes.mls | 2 +- .../src/test/mlscript/codegen/ImportedOps.mls | 8 +-- .../src/test/mlscript/newres/MutationFlow.mls | 4 +- .../test/mlscript/std/FingerTreeListTest.mls | 6 +-- 5 files changed, 39 insertions(+), 34 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls index 4143407ce6..2c03b39f0e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls +++ b/hkmc2/shared/src/test/mlscript-compile/FingerTreeList.mls @@ -1,3 +1,7 @@ +// #lang(0.3.x) +// ^ Can't be used yet due to spreads; +// needs the perf. fixes at `LPTK/new-resolution-no-hashing` + import "./Option.mls" import "./Iter.mls" @@ -37,11 +41,11 @@ class FingerTree[T] extends IterableBase with View(head, tail) then set current = tail Result.Next of head - fun toString() = "[" + this joined(", ") + "]" - fun length = FingerTreeList.length(this) - fun at(i) = get(i)(this) - fun concat(other) = FingerTreeList.concat(this, other) - fun slice(beg, end) = + fun toString(): Str = "[" + this joined(", ") + "]" + fun length: Int = FingerTreeList.length(this) + fun at(i: Int): T = get(i)(this) + fun concat(other: FingerTree[T] | Array[T]): FingerTree[T] = FingerTreeList.concat(this, other) + fun slice(beg: Int, end: Int): FingerTree[T] = let len = this.length set beg = normIdxSlice(beg, len) set end = normIdxSlice(end, len) @@ -54,14 +58,14 @@ class FingerTree[T] extends IterableBase with FingerTreeList.dropLeftRight(beg, len - end)(this) object Empty extends FingerTree[Nothing] with - fun toString() = "[" + this joined(", ") + "]" + fun toString(): Str = "[" + this joined(", ") + "]" class Single[T](val e: T) extends FingerTree[T] with - fun toString() = "[" + this joined(", ") + "]" + fun toString(): Str = "[" + this joined(", ") + "]" // prefix and suffix are at most length 4 class Deep[T](val size: Int, val prefix: Array[T], val middle: FingerTree[Node[T]], val suffix: Array[T]) extends FingerTree[T] with - fun toString() = "[" + [...this].join(", ") + "]" + fun toString(): Str = "[" + [...this].join(", ") + "]" // amortized O(log n) fun concatMiddle(ft1, middle, ft2) = if [ft1, middle, ft2] is @@ -132,12 +136,12 @@ object SpliceMarker module FingerTreeList with... -fun toFingerTree(xs) = if xs is +fun toFingerTree[T](xs: FingerTree[T] | Array[T]): FingerTree[T] = if xs is FingerTree then xs else mk(...xs) // assume that the input is iterable if not a finger tree // amortized O(1) -fun (+:) cons(x, xs) = if toFingerTree(xs) is +fun (+:) cons[T](x: T, xs: FingerTree[T] | Array[T]): FingerTree[T] = if toFingerTree(xs) is Empty then Single(x) Single(y) then Deep(getNodeSize(y) + getNodeSize(x), [x], Empty, [y]) Deep(s, ax, dt, ay) then if (ax.length >= 4) @@ -147,7 +151,7 @@ fun (+:) cons(x, xs) = if toFingerTree(xs) is else Deep(s + getNodeSize(x), [x, ...ax], dt, ay) // amortized O(1) -fun (:+) snoc(xs, x) = if toFingerTree(xs) is +fun (:+) snoc[T](xs: FingerTree[T] | Array[T], x: T): FingerTree[T] = if toFingerTree(xs) is Empty then Single(x) Single(y) then Deep(getNodeSize(y) + getNodeSize(x), [y], Empty, [x]) Deep(s, ax, dt, ay) then if (ay.length >= 4) @@ -156,23 +160,23 @@ fun (:+) snoc(xs, x) = if toFingerTree(xs) is ay.0, ay.1, ay.2)), [ay.3, x]) else Deep(s + getNodeSize(x), ax, dt, [...ay, x]) -fun mk(...args) = args.reduce( +fun mk[T](...args: Array[T]): FingerTree[T] = args.reduce( (acc, x, ...) => snoc(acc, x), Empty) -fun isEmpty = case +fun isEmpty[T]: FingerTree[T] -> Bool = case Empty then true _ then false // O(1) -fun length = case +fun length[T]: (FingerTree[T] | Array[T]) -> Int = case Array as a then a.length Empty then 0 Single(x) then getNodeSize(x) Deep(s, _, _, _) then s // amortized O(1) -fun popFront(ft) = if toFingerTree(ft) is +fun popFront[T](ft: FingerTree[T] | Array[T]): Nil | View[T] = if toFingerTree(ft) is Empty then Nil Single(x) then View(x, Empty) Deep(s, [x], dt, ay) then if (popFront(dt)) is @@ -185,7 +189,7 @@ fun popFront(ft) = if toFingerTree(ft) is View(x, Deep(s - getNodeSize(x), ax, dt, ay)) // amortized O(1) -fun popBack(ft) = if toFingerTree(ft) is +fun popBack[T](ft: FingerTree[T] | Array[T]): Nil | View[T] = if toFingerTree(ft) is Empty then Nil Single(x) then View(x, Empty) Deep(s, ax, dt, [x]) then if popBack(dt) is @@ -198,10 +202,11 @@ fun popBack(ft) = if toFingerTree(ft) is View(y, Deep(s - getNodeSize(y), ax, dt, ay)) // amortized O(log n) -fun (++) concat(ft1, ft2) = concatMiddle(toFingerTree(ft1), [], toFingerTree(ft2)) +fun (++) concat[T](ft1: FingerTree[T] | Array[T], ft2: FingerTree[T] | Array[T]): FingerTree[T] = + concatMiddle(toFingerTree(ft1), [], toFingerTree(ft2)) // amortized O(log n) -fun ith(idx) = case +fun ith[T](idx: Int): FingerTree[T] -> Option[T] = case Empty then None Single(x) then ithNode(idx, x) Deep(s, ax, dt, ay) and @@ -211,27 +216,27 @@ fun ith(idx) = case else ithAffixElement(idx - getAffixSize(ax) - length(dt), ay) else None -fun get(idx) = case +fun get[T](idx: Int): (FingerTree[T] | Array[T]) -> T = case Array as a then a.at(idx) FingerTree as lft then if ith(normIdx(idx, length(lft)))(lft) is None then throw "get: index out of bounds" Some(x) then x -fun toArray(ft) = [...ft] +fun toArray[T](ft: FingerTree[T]): Array[T] = [...ft] /// amortized O(beg + end) -fun dropLeftRight(beg, end)(und) = if beg < 0 || end < 0 || beg + end > length(und) +fun dropLeftRight[T](beg: Int, end: Int)(und: FingerTree[T] | Array[T]): FingerTree[T] = if beg < 0 || end < 0 || beg + end > length(und) then throw "dropLeftRight: index out of bounds" else repeatPopFront(beg)(repeatPopBack(end)(toFingerTree(und))) -fun isFingerTree(xs) = if xs is +fun isFingerTree(xs: Any): Bool = if xs is FingerTree then true else false // The following functions are not meant for users; it's meant referred to by Runtime.mls // TODO: use `FingerTreeList.internals.concat` when this is implemented -fun __markerConcat(...arr) = +fun __markerConcat(...arr: Array[Any]): FingerTree[Any] = let acc = FingerTreeList.mk() let idx = 0 while idx < arr.length do @@ -244,4 +249,4 @@ fun __markerConcat(...arr) = set idx = idx + 1 acc -val __split = SpliceMarker +val __split: SpliceMarker = SpliceMarker diff --git a/hkmc2/shared/src/test/mlscript/basics/Classes.mls b/hkmc2/shared/src/test/mlscript/basics/Classes.mls index e4b67f7bc6..b3a4caad0b 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Classes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Classes.mls @@ -22,7 +22,7 @@ class Foo { fun f = 1 } -class Foo(pred: {id: Any -> Any}) { +class Foo(pred: {id: Function}) { val r = pred.id(42) fun callId(x) = pred.id(x) } diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls index 262f9f72b2..2e4885893b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls @@ -3,13 +3,13 @@ module M with - fun (~) concat(a, b) = a + b + fun (~~) concat(a, b) = a + b -open M { concat, ~ } +open M :sjs fun foo() = - "a" ~ "b" ~ "c" + "a" ~~ "b" ~~ "c" foo() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let foo; foo = function foo() { return "abc" }; return "abc" @@ -22,7 +22,7 @@ let name = "_" let display(balance) = balance //│ display = fun display -"b" ~ display("-") +"b" ~~ display("-") //│ = "b-" diff --git a/hkmc2/shared/src/test/mlscript/newres/MutationFlow.mls b/hkmc2/shared/src/test/mlscript/newres/MutationFlow.mls index 1d6bc76f8a..36d087154d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/MutationFlow.mls +++ b/hkmc2/shared/src/test/mlscript/newres/MutationFlow.mls @@ -1,8 +1,8 @@ :. -// FIXME: Assignment leaves the variable's initializer shape in place, so the +// Assignment leaves the variable's initializer shape in place, so the // later read is checked against a one-element tuple instead of the stored array. -:fixme +:e :expect 1 let values = [0] set values = values.concat([1]) diff --git a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls index f4292dcb8b..c960aec027 100644 --- a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls @@ -101,14 +101,14 @@ if xs is //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let element0$; //│ if (runtime.Tuple.isArrayLike(xs2) && xs2.length >= 1) { -//│ let xs3, inlinedVal27, tmp16; +//│ let xs3, inlinedVal38, tmp16; //│ element0$ = runtime.Tuple.get(xs2, 0); //│ xs3 = xs2; //│ tmp16 = xs3.length - 0; -//│ inlinedVal27 = runtime.safeCall(xs3.slice(1, tmp16)); +//│ inlinedVal38 = runtime.safeCall(xs3.slice(1, tmp16)); //│ return globalThis.Object.freeze([ //│ element0$, -//│ inlinedVal27 +//│ inlinedVal38 //│ ]) //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); From 32420899b395a1aeaef66661384c3add5cdab034 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 1 Oct 2026 21:29:07 +0800 Subject: [PATCH 166/228] Identify concrete remaining fixture migration failures --- doc/new-resolution-suite-migration.md | 40 +++++++++++++++++++-------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 586bac1ccb..2b3c4bb345 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -77,21 +77,37 @@ They count language directives in source files, not test-runner test cases. ### Mixed-mode imports and fixture interfaces -Some migrated worksheets import legacy compilation fixtures. Check each fixture's -exposed interfaces and its consumers together. The table lists all remaining -legacy fixtures and the areas to check when retrying them; these leads do not -establish that each fixture requires a compiler change. +Some migrated worksheets import legacy compilation fixtures. An import across +resolution modes is supported and is not itself a migration blocker. Check each +fixture's exposed interfaces and its consumers together. Routine annotations and +source corrections should be attempted before attributing a failure to the +resolver. The remaining fixtures need the following work: -| Legacy fixtures | Outstanding checks | +| Legacy fixtures | Concrete work and known failures | | --- | --- | -| `Block`, `Shape` | Nominal members, callback arity, and the public interface for `showArm`. | -| `LazyArray`, `FingerTreeList`, `LazyFingerTree` | Collection interfaces, shape propagation, and termination when combining tuple spreads. | -| `Runtime`, `Rendering`, `Predef` | Runtime/host interfaces and their consumers; `Predef.use` also depends on contextual arguments. | +| `Block` | Annotate `showArm(a: Arm)`. Adapt callbacks such as `args.map(showArg)` to accept unused JS callback arguments, e.g. `(arg, ...) => showArg(arg)`. Declare `Str.replaceAll` in the host interface. `showLiteral` also selects `toString` through a union containing `null` and `undefined`; its branches need usable receiver interfaces. | +| `Shape` | Correct reads of `sym.args`: neither `ClassSymbol` nor `Symbol` defines that member. The available constructor metadata is `ConcreteClassSymbol.paramsOpt`. Check the intended name lookup and parameter-count operations, and adapt `map`/`every` callbacks to accept unused arguments. These are fixture corrections, not a demonstrated resolver design gap. | +| `LazyArray`, `LazyFingerTree` | Give indexable inputs and elements of concatenation arguments interfaces for `length`, `at`, and `slice`. Check nullable iterator/cache variables and preserve the `[index, collection]` shape of stack entries. `LazyArray` currently rejects `uitr.next()` because `uitr` includes `null`, and rejects `vals.bits` even inside `if vals is Splice`. These need flow/refinement investigation in addition to annotations. | +| `FingerTreeList` | Enabling new resolution exceeds the compilation test's time limit. Diagnose inference growth around recursive trees and tuple spreads before calling this an annotation-only port. | +| `Runtime`, `Rendering`, `Predef` | Audit callable and host interfaces; `Predef.use` also depends on contextual argument insertion. No separate design blocker is established for their missing annotations. | | `CSP`, `QuoteExample1` | Quasiquote type selections and wildcard-reference lowering. | -| `apps/Accounting`, `apps/CSV` | Public interfaces and their worksheet consumers. | -| `parsing/Extension`, `ParseRule`, `Parser`, `ParseRuleVisualizer`, `parsing-web-demo/main` | Exposed interfaces and mixed-mode parser dependencies. | -| `parsing/Lexer` | Calls with trailing contextual parameters leave function values where tokens are expected. | -| `nofib/lastpiece`, `nofib/sorting` | Exposed interfaces and value flow. | +| `apps/Accounting` | Annotate `process`'s callback as `Report -> Any`, give array callbacks their `Line` input interface and unused argument tails, expose the numeric receiver of `toFixed`, and explicitly import binary `~`. Validate the resulting report with the accounting worksheet. | +| `apps/CSV` | Refine the `Array[Str] | null` result of `RegExp.exec` before selecting captures, and the possibly undefined result of `Array.at` before calling `push`. Add the missing `Str.replace` host declaration. The existing `!== null` condition does not provide the required receiver interface. | +| `parsing/Lexer` | Calls to token constructors with trailing `using` parameters need automatic contextual argument insertion; `newres/LexerMigration.mls` records the missing behavior. | +| `parsing/ParseRule` | Correct `andThen`'s helper signature: it transforms a rule's result into a pair with the following rule's result, rather than preserving `B`. Compilation also triggers a repeated lexical-scope-exit assertion, including with a generic helper and an explicit pair result. This is a compiler bug to isolate, not an interface-design decision. | +| `parsing/Extension` | Resolve `display`, `extendChoices`, and `andThen` on rules obtained from `Rules.syntaxKinds`. Migrating its `ParseRule` dependency also encounters the assertion above. | +| `parsing/Parser` | Give rule/options parameters their interfaces. `exprCont` selects `infix.rule` and `infix.process` from an `Option` without unwrapping it. Compilation also rejects uses of the virtual `source` module at `source.line`; investigate those separately from annotations. | +| `parsing/ParseRuleVisualizer` | Declare the railroad-library interface passed as `rr` (`Sequence`, `Diagram`, `Terminal`, `Optional`, `NonTerminal`, and `Choice`) and resolve the mutable `renderedKinds` set's `has`/`union` selections. Its migrated parser dependencies have the failures listed above. | +| `parsing-web-demo/main` | Annotate iterator callback inputs and the DOM/railroad host boundary. Replace `examples.get(...)` with the module-style `MutMap.get` API. Validate in the browser as described in the fixture; parser dependency failures remain separate prerequisites. | +| `nofib/lastpiece`, `nofib/sorting` | Retry exposed-interface annotations and identify any residual flow errors. No specific design blocker is established. | + +Array callbacks must accept the arguments supplied by the JS API: three for +`map`/`filter`/`forEach` and four for `reduce`. An unused rest parameter satisfies +this existing rule (`newres/Arrays.mls`); it does not require relaxing callback +arity checking. Missing host declarations and ordinary parameter annotations do +not require a language-design discussion. Compiler assertions and timeouts need +reproductions and fixes; a design question should name the unresolved semantic +choice, rather than merely name the failing subsystem. To reproduce a blocker, temporarily add the language directive to the named compilation fixture and run `ctest ` or `catest ` as appropriate. From b04e0ad3c0302825b1503715c8794d9291985621 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 1 Oct 2026 22:36:33 +0800 Subject: [PATCH 167/228] Migrate collection and parser fixtures to new resolution --- doc/new-resolution-suite-migration.md | 36 ++++++----- .../scala/hkmc2/semantics/Elaborator.scala | 34 +++++----- .../src/test/mlscript-compile/Block.mls | 21 +++--- .../src/test/mlscript-compile/LazyArray.mls | 50 ++++++++------- .../src/test/mlscript-compile/Shape.mls | 27 ++++---- .../test/mlscript-compile/apps/Accounting.mls | 21 +++--- .../src/test/mlscript-compile/apps/CSV.mls | 15 +++-- .../apps/parsing-web-demo/main.mls | 64 +++++++++++-------- .../apps/parsing/ParseRuleVisualizer.mls | 11 ++-- .../mlscript-compile/apps/parsing/Parser.mls | 54 +++++++++------- .../src/test/mlscript/apps/AccountingTest.mls | 1 + .../shared/src/test/mlscript/apps/CSVTest.mls | 1 + .../test/mlscript/apps/parsing/ParserTest.mls | 10 +++ .../src/test/mlscript/decls/Prelude.mls | 4 ++ .../src/test/mlscript/newres/Arrays.mls | 24 +++---- .../test/mlscript/newres/GenericMethods.mls | 2 +- .../mlscript/newres/LegacyGenericMethods.mls | 12 ++++ .../newres/PatternDefinitionBindings.mls | 2 +- .../src/test/mlscript/newres/ShapeFixture.mls | 59 +++++++++++++++++ .../test/mlscript/newres/SourceLocations.mls | 43 +++++++++++++ .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 21 files changed, 330 insertions(+), 165 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/LegacyGenericMethods.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/ShapeFixture.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/SourceLocations.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 2b3c4bb345..3066bdfb19 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -23,8 +23,8 @@ Counts exclude shared `.mls` configurations, new-resolution regression tests, an | codegen | 93 | 33 | 126 | | ucs | 69 | 1 | 70 | | ups | 58 | 13 | 71 | -| apps | 13 | 3 | 16 | -| Total | 300 | 73 | 373 | +| apps | 15 | 1 | 16 | +| Total | 302 | 71 | 373 | | wasm (separate backend) | 22 | 1 | 23 | All twelve `apps/parsing` worksheets use new resolution, but some implementation @@ -33,11 +33,11 @@ imply that its dependencies have been migrated. | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | -| Main (including quotes, UPS, and regression fixtures) | 38 | 10 | 48 | -| Applications | 12 | 8 | 20 | +| Main (including quotes, UPS, and regression fixtures) | 41 | 7 | 48 | +| Applications | 17 | 3 | 20 | | Nofib | 37 | 2 | 39 | | WASM | 1 | 0 | 1 | -| Total | 88 | 20 | 108 | +| Total | 96 | 12 | 108 | These totals include the new-resolution `NamedFieldLibrary` regression fixture. They count language directives in source files, not test-runner test cases. @@ -85,21 +85,14 @@ resolver. The remaining fixtures need the following work: | Legacy fixtures | Concrete work and known failures | | --- | --- | -| `Block` | Annotate `showArm(a: Arm)`. Adapt callbacks such as `args.map(showArg)` to accept unused JS callback arguments, e.g. `(arg, ...) => showArg(arg)`. Declare `Str.replaceAll` in the host interface. `showLiteral` also selects `toString` through a union containing `null` and `undefined`; its branches need usable receiver interfaces. | -| `Shape` | Correct reads of `sym.args`: neither `ClassSymbol` nor `Symbol` defines that member. The available constructor metadata is `ConcreteClassSymbol.paramsOpt`. Check the intended name lookup and parameter-count operations, and adapt `map`/`every` callbacks to accept unused arguments. These are fixture corrections, not a demonstrated resolver design gap. | -| `LazyArray`, `LazyFingerTree` | Give indexable inputs and elements of concatenation arguments interfaces for `length`, `at`, and `slice`. Check nullable iterator/cache variables and preserve the `[index, collection]` shape of stack entries. `LazyArray` currently rejects `uitr.next()` because `uitr` includes `null`, and rejects `vals.bits` even inside `if vals is Splice`. These need flow/refinement investigation in addition to annotations. | +| `LazyFingerTree` | Indexable annotations and casts expose an assertion when calling the imported generic `FingerTree.at` method: its result crosses the class and method scopes incorrectly. `newres/LegacyGenericMethods.mls` reproduces it with `FingerTreeList.mk(42).at(0)`. | | `FingerTreeList` | Enabling new resolution exceeds the compilation test's time limit. Diagnose inference growth around recursive trees and tuple spreads before calling this an annotation-only port. | | `Runtime`, `Rendering`, `Predef` | Audit callable and host interfaces; `Predef.use` also depends on contextual argument insertion. No separate design blocker is established for their missing annotations. | | `CSP`, `QuoteExample1` | Quasiquote type selections and wildcard-reference lowering. | -| `apps/Accounting` | Annotate `process`'s callback as `Report -> Any`, give array callbacks their `Line` input interface and unused argument tails, expose the numeric receiver of `toFixed`, and explicitly import binary `~`. Validate the resulting report with the accounting worksheet. | -| `apps/CSV` | Refine the `Array[Str] | null` result of `RegExp.exec` before selecting captures, and the possibly undefined result of `Array.at` before calling `push`. Add the missing `Str.replace` host declaration. The existing `!== null` condition does not provide the required receiver interface. | | `parsing/Lexer` | Calls to token constructors with trailing `using` parameters need automatic contextual argument insertion; `newres/LexerMigration.mls` records the missing behavior. | | `parsing/ParseRule` | Correct `andThen`'s helper signature: it transforms a rule's result into a pair with the following rule's result, rather than preserving `B`. Compilation also triggers a repeated lexical-scope-exit assertion, including with a generic helper and an explicit pair result. This is a compiler bug to isolate, not an interface-design decision. | -| `parsing/Extension` | Resolve `display`, `extendChoices`, and `andThen` on rules obtained from `Rules.syntaxKinds`. Migrating its `ParseRule` dependency also encounters the assertion above. | -| `parsing/Parser` | Give rule/options parameters their interfaces. `exprCont` selects `infix.rule` and `infix.process` from an `Option` without unwrapping it. Compilation also rejects uses of the virtual `source` module at `source.line`; investigate those separately from annotations. | -| `parsing/ParseRuleVisualizer` | Declare the railroad-library interface passed as `rr` (`Sequence`, `Diagram`, `Terminal`, `Optional`, `NonTerminal`, and `Choice`) and resolve the mutable `renderedKinds` set's `has`/`union` selections. Its migrated parser dependencies have the failures listed above. | -| `parsing-web-demo/main` | Annotate iterator callback inputs and the DOM/railroad host boundary. Replace `examples.get(...)` with the module-style `MutMap.get` API. Validate in the browser as described in the fixture; parser dependency failures remain separate prerequisites. | -| `nofib/lastpiece`, `nofib/sorting` | Retry exposed-interface annotations and identify any residual flow errors. No specific design blocker is established. | +| `parsing/Extension` | Giving rules their `ParseRule[Tree]` interface exposes a lexical-scope assertion in `ParseRule.andThen`, even while the dependency remains on legacy resolution. Explicit generic arguments and a result annotation do not resolve it. | +| `nofib/lastpiece`, `nofib/sorting` | Both exceed the compilation time limit under new resolution. `sorting` first needs `int_of_char(c: Str)`; adding it reveals the timeout. Diagnose inference growth before retrying these ports. | Array callbacks must accept the arguments supplied by the JS API: three for `map`/`filter`/`forEach` and four for `reduce`. An unused rest parameter satisfies @@ -113,6 +106,19 @@ To reproduce a blocker, temporarily add the language directive to the named compilation fixture and run `ctest ` or `catest ` as appropriate. The blocked fixtures retain their existing resolution mode. +`Block`, `Shape`, `LazyArray`, `Accounting`, `CSV`, `Parser`, +`ParseRuleVisualizer`, and the parser web demo use new resolution. Nullable host +results are cast after their existing checks; mutable traversal arrays use `mut`. +The parser unwraps optional continuation rules and resumes module parsing after +leading separators. The virtual `source` fields expand in both resolution modes +(`newres/SourceLocations`); constructor metadata is covered by `newres/ShapeFixture`. + +The web demo compiles, but serving its checked-in HTML directly still encounters +Node-only imports (`fs`, `process`, `path`, and `url`) through `Predef` and `Term`. +Browser packaging must separate that dependency before direct startup works. +Parser and visualizer worksheets run under Node and do not exercise this browser +packaging constraint. + ### Shape propagation and capture precision - **Generators:** `newres/GeneratorResults.mls` records a call whose result is diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 5a81056d0e..86064cbd82 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1260,25 +1260,29 @@ extends Importer: def elaborateSelection(tree: Sel): Term = val preTrm = subterm(tree.prefix, Receiver) - if newResolution then + // `source` has no runtime object. Recognize its statically named fields + // before constructing a runtime selection, in either resolution mode. + // Compare symbols so a user-defined binding named `source` stays ordinary. + val sym = if newResolution then preTrm.withoutCaptures match + case Term.MemberRef(bms) if bms === ctx.builtins.source.bms => + ctx.builtins.source.module.tree.definedSymbols.get(tree.name.name) + case _ => N + else resolveField(tree.name, preTrm.symbol, tree.name) + if sym.contains(ctx.builtins.source.line) then + val loc = tree.toLoc.getOrElse(???) + val (line, _, _) = loc.origin.fph.getLineColAt(loc.spanStart) + Term.Lit(IntLit(loc.origin.startLineNum + line)) + else if sym.contains(ctx.builtins.source.name) then + Term.Lit(StrLit(ctx.getOuter.map(_.nme).getOrElse(""))) + else if sym.contains(ctx.builtins.source.file) then + val loc = tree.toLoc.getOrElse(???) + Term.Lit(StrLit(loc.origin.fileName.toString)) + else if newResolution then val res = new Term.NewSel(preTrm, tree.name, N)(FlowSymbol.sel(tree.name.name)).withLocOf(tree) - // listenTerm(preTrm, shape => selShape2(shape, tree.name, res)) newSel(res) interpretRef(res, interp) else - val sym = if newResolution then N - else resolveField(tree.name, preTrm.symbol, tree.name) - if sym.contains(ctx.builtins.source.line) then - val loc = tree.toLoc.getOrElse(???) - val (line, _, _) = loc.origin.fph.getLineColAt(loc.spanStart) - Term.Lit(IntLit(loc.origin.startLineNum + line)) - else if sym.contains(ctx.builtins.source.name) then - Term.Lit(StrLit(ctx.getOuter.map(_.nme).getOrElse(""))) - else if sym.contains(ctx.builtins.source.file) then - val loc = tree.toLoc.getOrElse(???) - Term.Lit(StrLit(loc.origin.fileName.toString)) - else - Term.Sel(preTrm, tree.name)(sym, FlowSymbol.sel(tree.name.name), N, S(summon)) + Term.Sel(preTrm, tree.name)(sym, FlowSymbol.sel(tree.name.name), N, S(summon)) tree.desugared match case Tree.Trm(term) => interpretRef(term, interp) diff --git a/hkmc2/shared/src/test/mlscript-compile/Block.mls b/hkmc2/shared/src/test/mlscript-compile/Block.mls index ef323a67d6..128751d896 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Block.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Block.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) import "./Predef.mls" import "./Option.mls" import "./StrOps.mls" @@ -99,8 +100,8 @@ fun showLiteral(l: Literal) = if l is undefined then "undefined" null then "null" - Str then "\"" + l.toString() + "\"" - else l.toString() + Str then "\"" + String(l) + "\"" + else String(l) fun showSymbol(s: Symbol) = s.name.replaceAll("$", "_") @@ -121,7 +122,7 @@ fun showArg(arg: Arg) = showPath(arg.value) fun showArgs(args: Array[Arg]) = - args.map(showArg).join(", ") + args.map((value, ...) => showArg(value)).join(", ") fun showResult(r: Result): Str = if r is @@ -144,11 +145,11 @@ fun showCase(c) = Tup(len) then "[" + Array(len).fill("_").join(", ") + "]" _ then "" -fun showArm(a) = +fun showArm(a: Arm) = showCase(a.cse) + " then" + (if a.body is Return then " " else "\n ") + indent(showBlock(a.body)) fun showParams(p: ParamList) = - "(" + p.map(showSymbol(_)).join(", ") + ")" + "(" + p.map((value, ...) => showSymbol(value)).join(", ") + ")" fun showParamsOpt(p) = if p is @@ -156,7 +157,7 @@ fun showParamsOpt(p) = None then "" fun showParamList(ps: Array[ParamList]) = - ps.map(showParams).join("") + ps.map((value, ...) => showParams(value)).join("") fun showDefn(d: Defn): Str = if d is @@ -165,11 +166,11 @@ fun showDefn(d: Defn): Str = (if body is (Return | End) then " " else "\n ") + indent(showBlock(body)) ClsLikeDefn(sym, methods, _) then "class " + showSymbol(sym) + showParamsOpt(sym.paramsOpt) - + indent((if methods is [] then "" else " with\n") + methods.map(showDefn).join("\n")) + + indent((if methods is [] then "" else " with\n") + methods.map((value, ...) => showDefn(value)).join("\n")) // TODO: used to represent assignments in constructors, which still need some adjustments ValDefn(owner, sym, rhs) then "val " + showSymbol(sym) + " = " + showPath(rhs) - _ then "" + _ then "" fun showBlock(b) = if b is @@ -182,12 +183,12 @@ fun showBlock(b) = showResult(res) Match(scrut, arms, dflt, rest) then "if " + showPath(scrut) + " is" - + indent("\n" + arms.map(showArm).join("\n")) + + indent("\n" + arms.map((value, ...) => showArm(value)).join("\n")) + if dflt is Some(db) then indent("\nelse" + indent((if db is Return then " " else "\n") + showBlock(db))) else "" + showRestBlock(rest) Scoped(symbols, rest) then // initialize symbols - "let {" + symbols.map(showSymbol).join(", ") + "}" + showRestBlock(rest) + "let {" + symbols.map((value, ...) => showSymbol(value)).join(", ") + "}" + showRestBlock(rest) End() then "()" _ then "" + b diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls index eb4ae6cee2..a619c4be91 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) import "./Iter.mls" open Iter @@ -9,10 +10,10 @@ fun normIdx(i, len) = else i fun enforceArray = case - (LazyArr | Array | String | Str | TypedArray) as a then a - ow then throw Error("Expected an Array, got: " + ow.toString()) + (LazyArr | Array | String | Str | TypedArray) as a then (a as Indexable) + ow then throw Error("Expected an Array, got: " + String(ow)) -fun concatHelper(a, l, ...args) = +fun concatHelper(a: Array[Any], l: Int, ...args: Array[Indexable]) = let idx = 0 init_len = a.length @@ -34,7 +35,7 @@ fun normIdxSlice(i, len) = else 0 else i -fun sliceHelper(beg, fin, arr) = +fun sliceHelper(beg: Int, fin: Int, arr: Indexable) = let len = arr.length set beg = normIdxSlice(beg, len) set fin = normIdxSlice(fin, len) @@ -47,18 +48,18 @@ fun sliceHelper(beg, fin, arr) = if arr is View(itr, start, end) then View of itr, start + beg, end + (len - fin) - Splice(bits, l) as s then View of s.materialize().slice(beg, fin), 0, 0 + Splice(bits, l) as s then View of (s as Splice).materialize().slice(beg, fin), 0, 0 else View of enforceArray(arr), beg, (arr.length - fin) // Marker base class for View and Splice class LazyArr extends IterableBase with fun toString() = "[" + this joined(", ") + "]" - fun concat(...args) = concatHelper(mut [SpliceMarker, this], this.length, ...args) + fun concat(...args: Array[Indexable]) = concatHelper(mut [SpliceMarker, this], this.length, ...args) // Requires that the underlying iterable is constant-time indexable // Note: Ideally `underlying` should be typed as `IterableBase`, but `Array` currently does not extend it. -class View(val underlying, val start, val end) extends LazyArr with +class View(val underlying: Indexable, val start: Int, val end: Int) extends LazyArr with fun iterator() = let idx = start @@ -70,7 +71,7 @@ class View(val underlying, val start, val end) extends LazyArr with Result.Next of next else Result.Done - fun materialize() = underlying.slice(start, underlying.length - end) + fun materialize() = underlying.slice(start, underlying.length - end) as Indexable fun at(i) = if i < -length || i >= length do @@ -83,7 +84,7 @@ class View(val underlying, val start, val end) extends LazyArr with fun toString() = "[" + this joined(", ") + "]" object SpliceMarker -class Splice(val bits, val len) extends LazyArr with +class Splice(val bits: Array[Any], val len: Int) extends LazyArr with let materialized = null fun iterator() = @@ -96,7 +97,7 @@ class Splice(val bits, val len) extends LazyArr with SpliceMarker then if uitr === null do set uitr = bits.[idx + 1].(Symbol.iterator)() - let n = uitr.next() + let n = (uitr as Iterator[Any]).next() if n.done then set uitr = null set idx += 2 @@ -113,15 +114,15 @@ class Splice(val bits, val len) extends LazyArr with fun reify = let counter = 0 let arr = new mut Array(len) - let stack = mut [] + val stack: Array[[Int, Indexable]] = mut [] stack.push([0, bits]) while stack.length > 0 do - let e = stack.pop() - let idx = e.[0] - let vals = e.[1] + let e = stack.pop() as [Int, Indexable] + let idx = e.0 + let vals = e.1 - if vals is Splice do - set vals = vals.bits + if vals is Splice as splice do + set vals = (splice as Splice).bits while (idx < vals.length and (vals.at(idx) is SpliceMarker) is false) do @@ -131,17 +132,17 @@ class Splice(val bits, val len) extends LazyArr with if (idx < vals.length and vals.at(idx) is SpliceMarker) do stack.push([idx + 2, vals]) - stack.push([0, vals.at(idx + 1)]) + stack.push([0, vals.at(idx + 1) as Indexable]) arr - fun materialize() = + fun materialize(): Array[Any] = if materialized === null do set materialized = reify - materialized + materialized as Array[Any] fun at(i) = materialize().at(i) fun slice(beg, fin) = sliceHelper(beg, fin, this) - fun concat(...args) = + fun concat(...args: Array[Indexable]) = if materialized === null then concatHelper(mut [SpliceMarker, this], len, ...args) else concatHelper(mut [SpliceMarker, materialized], len, ...args) @@ -152,17 +153,20 @@ class Splice(val bits, val len) extends LazyArr with module LazyArray with... +// Arrays, strings, and lazy views share this indexable interface. +type Indexable = {length: Int, at: Int -> Any, slice: Function} + fun mk(...args) = View of args, 0, 0 -fun concat(...args) = concatHelper(mut [], 0, ...args) -fun dropLeftRight(beg, fin)(xs) = +fun concat(...args: Array[Indexable]) = concatHelper(mut [], 0, ...args) +fun dropLeftRight(beg: Int, fin: Int)(xs: Indexable) = if beg < 0 || fin < 0 do throw RangeError("LazyArray.dropLeftRight: indices must be non-negative") if beg > xs.length || fin > xs.length do throw RangeError("LazyArray.dropLeftRight: indices out of bounds") sliceHelper(beg, xs.length - fin, xs) -fun equals(xs, ys) = +fun equals(xs: Indexable, ys: Indexable) = xs.length === ys.length && let idx = 0 while (idx < xs.length) do diff --git a/hkmc2/shared/src/test/mlscript-compile/Shape.mls b/hkmc2/shared/src/test/mlscript-compile/Shape.mls index d8a3dcca30..57936459df 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Shape.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Shape.mls @@ -1,7 +1,8 @@ +#lang(0.3.x) import "./Block.mls" import "./Option.mls" -open Block { Literal, ClassSymbol, showSymbol, isPrimitiveType, isPrimitiveTypeOf } +open Block { Literal, ClassSymbol, ConcreteClassSymbol, showSymbol, isPrimitiveType, isPrimitiveTypeOf } open Option type Shape = Shape.Shape @@ -19,16 +20,14 @@ fun show(s: Shape) = if s is Dyn then "Dyn" Lit(lit) then "Lit(" + Block.showLiteral(lit) + ")" - Arr(shapes) then "Arr(" + shapes.map(show).join(", ") + ")" - Class(sym, params) then "Class(" + showSymbol(sym) + ", [" + params.map(show).join(", ") + "])" + Arr(shapes) then "Arr(" + shapes.map((s, ...) => show(s)).join(", ") + ")" + Class(sym, params) then "Class(" + showSymbol(sym) + ", [" + params.map((s, ...) => show(s)).join(", ") + "])" fun sel(s1: Shape, s2: Shape): Array[Shape] = if [s1, s2] is - [Class(sym, params), Lit(n)] and n is Str - and sym.args is Some(args) - and args.find(_ == n) - == () then [] - is i then [params.(i)] + [Class(ConcreteClassSymbol(_, _, Some(args), _), params), Lit(n)] and n is Str as n then + let i = args.findIndex((arg, ...) => arg.name == n) + if i >= 0 then [params.(i)] else [] [Dyn, Lit(n)] and n is Str then [Dyn()] [Arr(shapes), Lit(n)] and n is Int @@ -44,18 +43,18 @@ fun sel(s1: Shape, s2: Shape): Array[Shape] = fun static(s: Shape) = if s is Dyn then false - Lit(l) then not (l is Str and isPrimitiveType(l)) // redundant bracket? - Class(_, params) then params.every(static) - Arr(shapes) then shapes.every(static) + Lit(l) then not (l is Str and isPrimitiveType(Block.Symbol(l))) // redundant bracket? + Class(_, params) then params.every((s, ...) => static(s)) + Arr(shapes) then shapes.every((s, ...) => static(s)) open Block { Case } fun silh(p: Case): Shape = if p is Block.Lit(l) then Lit(l) Block.Cls(sym, path) then - val size = if sym.args is Some(i) then i else 0 - Class(sym, Array(size).fill(Dyn)) - Block.Tup(n) then Arr(Array(n).fill(Dyn)) + val size = if sym is ConcreteClassSymbol(_, _, Some(params), _) then params.length else 0 + Class(sym, Array(size).fill(Dyn())) + Block.Tup(n) then Arr(Array(n).fill(Dyn())) // TODO: use Option instead, since all of them return at most one shape fun filter(s: Shape, p: Case): Array[Shape] = diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls index 0d3035c3aa..3355fbcd58 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls @@ -1,17 +1,16 @@ +#lang(0.3.x) import "fs" import "../StrOps.mls" open StrOps +open StrOps { concat2, ~ } import "../Predef.mls" open Predef -declare class Num -declare class Bool -declare val String data class Project(val num: Str) @@ -20,7 +19,7 @@ data class Project(val num: Str) data class Accounting with ... -fun display(amt): Str = (amt / 1000).toFixed(1) +fun display(amt: Num): Str = ((amt / 1000) as Num).toFixed(1) val warnings = mut [] @@ -55,27 +54,27 @@ data class Report(fileName) with fun init() = wln of "" - wln of "|" ~ "Year" ~ "|" ~ lines.map(x => x.name).join("|") ~ "|" - wln of "|" ~ "---" ~ "|" ~ lines.map(x => "--:").join("|") ~ "|" + wln of "|" ~ "Year" ~ "|" ~ lines.map((x: Line, ...) => x.name).join("|") ~ "|" + wln of "|" ~ "---" ~ "|" ~ lines.map((x, ...) => "--:").join("|") ~ "|" fun snapShot(label) = - wln of "|" ~ String(label) ~ "|" ~ lines.map(x => display(x.balance)).join("|") ~ "|" + wln of "|" ~ String(label) ~ "|" ~ lines.map((x: Line, ...) => display(x.balance)).join("|") ~ "|" fun wrapUp() = wln of "" - warnings.forEach of x => (wln(x), wln("")) + warnings.forEach of (x, ...) => (wln(x), wln("")) wln of "### Remaining Available Funds" wln of "" wln of "|" ~ "Summary" ~ "| |" wln of "|" ~ "---" ~ "|--:|" wln of "|" ~ "Matchable" ~ "|" ~ display( - lines.filter(x => x.isMatchable).map(x => x.balance).reduce((a, b) => a +. b, 0) + lines.filter((x: Line, ...) => x.isMatchable).map((x: Line, ...) => x.balance).reduce((a, b, ...) => a +. b, 0) ) ~ "|" wln of "|" ~ "Non-matchable" ~ "|" ~ display( - lines.filter(x => not x.isMatchable).map(x => x.balance).reduce((a, b) => a +. b, 0) + lines.filter((x: Line, ...) => not x.isMatchable).map((x: Line, ...) => x.balance).reduce((a, b, ...) => a +. b, 0) ) ~ "|" -fun process(filename, k) = +fun process(filename: Str, k: Report -> Any) = let report = Report(filename) report.init() k(report) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls index 00a1f50b37..53045ce40d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) // Adapted from https://www.bennadel.com/blog/1504-ask-ben-parsing-csv-strings-with-javascript-exec-regular-expression-command.htm // (JS code commented at bottom) @@ -23,7 +24,7 @@ val objPattern = new mut RegExp of "gi" // This will parse a delimited string into an array of arrays. -fun toArrays(strData) = +fun toArrays(strData: Str) = // Create an array to hold our data. Give the array a default empty first row. let arrData = mut [mut []] @@ -33,8 +34,10 @@ fun toArrays(strData) = let arrMatches = objPattern.exec(strData) arrMatches !== null do + // The loop condition guarantees a successful regular-expression match. + val matches = arrMatches as Array[Str] // Get the delimiter that was found. - let strMatchedDelimiter = arrMatches.1 + let strMatchedDelimiter = matches.1 // Check to see if the given delimiter has a length (is not the start of string) // and if it matches field delimiter. @@ -46,17 +49,17 @@ fun toArrays(strData) = // Now that we have our delimiter out of the way, // let's check to see which kind of value we captured (quoted or unquoted). let strMatchedValue = - if arrMatches.2 != undefined + if matches.2 != undefined then // We found a quoted value. When we capture this value, unescape any double quotes. - arrMatches.2.replace of + matches.2.replace of new mut RegExp("\"\"", "g") "\"" else // We found a non-quoted value. - arrMatches.3 + matches.3 - arrData.at(arrData.length - 1).push(strMatchedValue) + (arrData.at(arrData.length - 1) as Array[Str]).push(strMatchedValue) arrData diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls index c3fa60c204..53ec832ce1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) // Note: This file does not have a corresponding test file. It is recommended to // test it through the following steps. // @@ -17,6 +18,7 @@ import "../../StrOps.mls" import "../../Iter.mls" import "../../XML.mls" import "../../Option.mls" +import "../../MutMap.mls" import "../../Runtime.mls" import "../../Predef.mls" import "../parsing/TreeHelpers.mls" @@ -33,13 +35,17 @@ open Examples module Main with ... -let query = document.querySelector.bind(document) -let editor = query("#editor") -let selector = query("select#example") -let parseButton = query("button#parse") -let outputPanel = query("#output") +type Example = {name: Str, source: Str} -examples Iter.each of case [key, example] then +val query: Str -> dyn = document.querySelector.bind(document) +val editor = query("#editor") +val selector = query("select#example") +val parseButton = query("button#parse") +val outputPanel = query("#output") + +examples Iter.each of (entry: [Str, Example]) => + let key = entry.0 + let example = entry.1 let option = document.createElement of "option" set option.value = key set option.textContent = example.name @@ -49,7 +55,7 @@ examples Iter.each of case [key, example] then set editor.value = example.source // Make pressing Tab add two spaces at the caret's position. -editor.addEventListener of "keydown", event => +editor.addEventListener of "keydown", (event: dyn) => if event.key is "Tab" do // Prevent the default tab behavior (focusing the next element). event.preventDefault() @@ -59,12 +65,12 @@ editor.addEventListener of "keydown", event => set editor.selectionEnd = start + 2 set editor.selectionStart = editor.selectionEnd -selector.addEventListener of "change", event => - if examples.get(selector.value) is - Some(example) do set editor.value = example.source +selector.addEventListener of "change", (event: dyn) => + if (examples |> MutMap.get(selector.value)) is + Some(example) do set editor.value = (example as Example).source None do throw new Error of "Example \"" + selector.value + "\" not found" -parseButton.addEventListener of "click", event => +parseButton.addEventListener of "click", (event: dyn) => let tokens = Lexer.lex(editor.value, noWhitespace: true) set outputPanel.innerHTML = "" Runtime.try_catch of @@ -81,19 +87,21 @@ parseButton.addEventListener of "click", event => errorDisplay.setError of error outputPanel.appendChild of errorDisplay -let indentRegex = new RegExp("""^(\s*)""") +val indentRegex = new RegExp("""^(\s*)""") + +type IndentedNode = {text: Str, children: Array[IndentedNode]} -fun parseIndentedText(text) = - let root = (text: "", children: []) - let stack = [(node: root, indent: -1)] +fun parseIndentedText(text: Str) = + let root = (text: "", children: mut []) + let stack = mut [(node: root, indent: -1)] text.split("\n") - Iter.filtering(line => line.trim().length > 0) - Iter.each of line => - let indent = line.match(indentRegex).[1].length + Iter.filtering((line: Str) => line.trim().length > 0) + Iter.each of (line: Str) => + let indent = (indentRegex.exec(line) as Array[Str]).[1].length let text = line.substring(indent) while indent <= stack.[stack.length - 1].indent do stack.pop() - let newNode = (text: text, children: []) + let newNode = (text: text, children: mut []) stack.[stack.length - 1].node.children.push of newNode stack.push of node: newNode, indent: indent root.children @@ -102,7 +110,7 @@ fun parseIndentedText(text) = // * The content in custom elements is placed inside the ShadowDOM, which is // * scoped. Therefore, styles placed directly in HTML won't be effective on // * elements inside custom elements. Placing CSS here is more convenient. -let errorDisplayStyle = """ +val errorDisplayStyle = """ .error-container { background-color: #fdd; padding: 0.375rem 0.75rem 0.5rem; @@ -132,9 +140,9 @@ class CollapsibleTree extends HTMLElement with let treeElement = createDetailsTree(treeData) this.appendChild(treeElement) - fun createDetailsTree(nodes) = + fun createDetailsTree(nodes: Array[IndentedNode]) = let fragment = document.createDocumentFragment() - nodes Iter.each of node => + nodes Iter.each of (node: IndentedNode) => let details = document.createElement("details") details.setAttribute("open", "") let summary = document.createElement("summary") @@ -155,24 +163,24 @@ customElements.define of "collapsible-tree", CollapsibleTree class ErrorDisplay extends HTMLElement with this.attachShadow(mode: "open") - let _error = None + mut val _error: Option[Error] = None fun connectedCallback() = this.render() - fun setError(value) = + fun setError(value: Error) = set _error = Some(value) this.render() fun render() = if _error is Some(error) do - let stackLines = error.stack.split("\n") - if stackLines.[0].startsWith(error.name) do + let stackLines = if error.stack is Str as stack then stack.split("\n") else mut [] + if stackLines.length > 0 and stackLines.[0].startsWith(error.name) do stackLines.shift() set this.shadowRoot.innerHTML = elem("div", "class": "error-container") of elem("h3", "class": "error-message") of error.name + ": " + error.message elem("ul", "class": "stack-trace") of stackLines - Iter.mapping of line => elem("li") of line.trim() + Iter.mapping of (line: Str) => elem("li") of line.trim() Iter.joined("") elem("style") of errorDisplayStyle @@ -184,7 +192,7 @@ fun makeFigures(entries) = entries Iter.joined("") fun displayRules() = - open ParseRuleVisualizer + open ParseRuleVisualizer {render, reset} open Rules reset() set query("#syntax-diagrams>main").innerHTML = StrOps.concat of diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls index e5293b9a8b..f645754632 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) import "../../Predef.mls" import "../../Stack.mls" import "../../Iter.mls" @@ -24,13 +25,13 @@ val tracer = new TreeTracer.TreeTracer() let defaultKinds = tuple of "type", "term", "typevar", "ident" -let renderedKinds = new Set of defaultKinds +mut val renderedKinds: Set[Str] = new Set of defaultKinds fun reset() = set renderedKinds = new Set of defaultKinds // * `rr` is the railroad library. -fun render[T](rr, title: Str, rule: ParseRule.ParseRule[T]) = +fun render[T](rr: dyn, title: Str, rule: ParseRule.ParseRule[T]) = let helperRules = mut [] let referencedKinds = new Set() let renderCache = new Map() @@ -40,7 +41,7 @@ fun render[T](rr, title: Str, rule: ParseRule.ParseRule[T]) = None then lhs fun diagram(choicesOpt) = rr.Diagram of if choicesOpt is Some(choices) then choices else [] - fun renderChoice(parentRule, choice) = if choice is + fun renderChoice(parentRule, choice: Choice[Any]) = if choice is Choice.End then tracer.print of "found Choice.End" None @@ -85,10 +86,10 @@ fun render[T](rr, title: Str, rule: ParseRule.ParseRule[T]) = while referencedKinds.size > 0 do let currentKinds = referencedKinds set renderedKinds = renderedKinds.union of currentKinds - referencedKinds = new Set() + set referencedKinds = new Set() diagrams.push of ...currentKinds Iter.mapping of kind => - let theRule = syntaxKinds |> MutMap.get(kind) |> Option.unsafe.get + let theRule = (syntaxKinds |> MutMap.get(kind) |> Option.unsafe.get) as ParseRule.ParseRule[T] [kind, diagram of renderRule(theRule)] Iter.toArray() diagrams diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls index 4a0f353db5..13efadae75 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) import "../../Predef.mls" import "../../Option.mls" import "../../Stack.mls" @@ -26,6 +27,11 @@ module Parser with ... val tracer: TreeTracer.TreeTracer = new TreeTracer.TreeTracer +type ExprOptions = { + kind: Str, rule: ParseRule.ParseRule[Tree], + allowOperators: Bool, allowLiterals: Bool +} + let termOptions = kind: "term", rule: termRule allowOperators: true, allowLiterals: true @@ -33,7 +39,7 @@ let typeOptions = kind: "type", rule: typeRule allowOperators: false, allowLiterals: true, -fun parse(tokens) = +fun parse(tokens: Stack[Token.Token]) = let counter = 0 fun consume = @@ -62,9 +68,9 @@ fun parse(tokens) = token :: _ then Tree.error("expect a type variable but found " + token) Nil then Tree.error("expect a type variable but found the end of input") // * other rule-based kinds - syntaxKinds |> MutMap.get(kind) is Some(rule) then + syntaxKinds |> MutMap.get(kind) is Some(ParseRule.ParseRule as rule) then let tree = parseRule(prec, rule) - if rule.refChoice is Some(Ref(kind', process, None, None, rest)) and kind == kind' do + if (rule as ParseRule.ParseRule[Tree]).refChoice is Some(Ref(kind', process, None, None, rest)) and kind == kind' do let shouldParse = true while shouldParse do let tree' = parseRule(prec, rest) @@ -76,21 +82,21 @@ fun parse(tokens) = tree else throw Error("Unknown syntax kind: \"" + kind + "\"") - fun parseRule(prec: Int, rule) = tracer.trace of + fun parseRule(prec: Int, rule: ParseRule.ParseRule[Tree]): Tree = tracer.trace of "parsing rule \"" + rule.name + "\" with precedence " + prec result => "parsed rule \"" + rule.name + "\": " + result Tree.summary() () => if tokens is... Token.Identifier(name, _) :: _ and do tracer.print of "found an identifier \"" + name + "\"", source.line Keywords.all |> MutMap.get(name) is Some(keyword) and - do tracer.print of keyword.toString(), source.line + do tracer.print of (keyword as Keywords.Keyword).toString(), source.line do tracer.print of "keyword choices: ", rule.keywordChoices Iter.mapping of case [k, v] then "`" + k + "`" Iter.joined(", ") rule.keywordChoices |> MutMap.get(name) is - Some(rest) then + Some(ParseRule.ParseRule as rest) then tracer.print of "found a rule starting with `" + name + "`", source.line - tracer.print of "the rest of the rule: " + rest.display, source.line + tracer.print of "the rest of the rule: " + (rest as ParseRule.ParseRule[Tree]).display, source.line consume parseRule(0, rest) do tracer.print of "\"" + name + "\" is not a keyword", source.line @@ -131,17 +137,17 @@ fun parse(tokens) = tracer.print of "no end choice but found the end of input", source.line Tree.error("unexpected end of input") - fun expr(prec: Int, options): Tree = tracer.trace of + fun expr(prec: Int, options: ExprOptions): Tree = tracer.trace of options.kind + " <<< " + prec + " " + TokenHelpers.preview(tokens) result => options.kind + " >>> " + result Tree.summary() () => if tokens is ... Token.Identifier(name, symbolic) :: _ and Keywords.all |> MutMap.get(name) is // * the keyword case Some(keyword) and options.rule.keywordChoices |> MutMap.get(name) is - Some(rule) and - keyword.leftPrecOrMin > prec then + Some(ParseRule.ParseRule as rule) and + (keyword as Keywords.Keyword).leftPrecOrMin > prec then consume - parseRule(keyword.rightPrecOrMax, rule) exprCont(prec, options) + parseRule((keyword as Keywords.Keyword).rightPrecOrMax, rule) exprCont(prec, options) else tracer.print of "the left precedence of \"" + name + "\" is less", source.line Tree.empty @@ -163,26 +169,30 @@ fun parse(tokens) = token :: _ then Tree.error("unrecognized token: " + token) Nil then Tree.error("unexpected end of input") - fun exprCont(acc: Tree, prec: Int, options) = if tokens is - let infix = options.rule.refChoice Option.flatMap of case + fun infixRule(options: ExprOptions): Option[{process: (Tree, Tree) -> Tree, rule: ParseRule.ParseRule[Tree]}] = + // A category may have no continuation rule (for example, an atomic kind). + options.rule.refChoice Option.flatMap of case Ref(kind, process, None, None, rest) and kind == options.kind then - { process: process, rule: rest } + Some of { process: process, rule: rest } else throw Error("Kind " + options.kind + " does not have infix rules") + + fun exprCont(acc: Tree, prec: Int, options: ExprOptions) = if tokens is + let infix = infixRule(options) do tracer.print of ">>> " + options.kind + "Cont " + prec + " " + acc Tree.summary() + " <<<", source.line // * the case of infix keyword rules do tracer.print of "check keyword " + TokenHelpers.preview(tokens), source.line Token.Identifier(name, _) :: _ and Keywords.all |> MutMap.get(name) is Some(keyword) and do tracer.print of "found a keyword: " + name, source.line - infix.rule.keywordChoices |> MutMap.get(name) is Some(rule) and + infix is Some(infix) and infix.rule.keywordChoices |> MutMap.get(name) is Some(ParseRule.ParseRule as rule) and do tracer.print of "keyword `" + name + "` is found in infix rules", source.line - keyword.leftPrecOrMin > prec and rule.refChoice is + (keyword as Keywords.Keyword).leftPrecOrMin > prec and (rule as ParseRule.ParseRule[Tree]).refChoice is Some(Ref(kind, process, outerPrec, innerPrec, rest)) and do tracer.print of "try to parse kind \"" + kind + "\" at " + TokenHelpers.preview(tokens), source.line let outerPrec' = outerPrec Option.getOrElse(Keywords.maxOperatorPrec) let innerPrec' = innerPrec Option.getOrElse(outerPrec') outerPrec' > prec then consume - let rhs = parseKind(kind, keyword.rightPrecOrMin) + let rhs = parseKind(kind, (keyword as Keywords.Keyword).rightPrecOrMin) let restRes = parseRule(innerPrec', rest) infix.process(acc, process(rhs, restRes)) exprCont(prec, options) None then acc @@ -201,7 +211,7 @@ fun parse(tokens) = acc // * the case of application do tracer.print of "not a keyword", source.line - token :: _ and infix.rule.refChoice is + token :: _ and infix is Some(infix) and infix.rule.refChoice is Some(Ref(kind, process, outerPrec, innerPrec, rest)) and do tracer.print of "found reference to " + kind + " with outerPrec = " + outerPrec, source.line let outerPrec' = outerPrec Option.getOrElse(Keywords.maxOperatorPrec) @@ -223,7 +233,7 @@ fun parse(tokens) = None then tracer.print of "cannot consume " + token, source.line acc - Nil then acc + _ then acc fun handleDirective(tree: Tree, acc: Stack[Tree]) = if tree is // Call the corresponding handler for the directive. @@ -245,11 +255,11 @@ fun parse(tokens) = do tracer.print of ">>>>>> mod <<<<<<", source.line Token.Identifier(";;", _) :: _ then consume - mod + mod(acc) Token.Identifier(name, _) :: _ and Keywords.all |> MutMap.get(name) is Some(keyword) and // First, try if the keyword is the prefix of the term. - termRule.keywordChoices |> MutMap.get(name) is Some(rule) then + termRule.keywordChoices |> MutMap.get(name) is Some(ParseRule.ParseRule as rule) then let tree = expr(0, termOptions) if tree is Tree.LetIn(bindings, Tree.Empty) then // If the body is empty, then this is a let-definition. @@ -257,7 +267,7 @@ fun parse(tokens) = else modCont of tree :: acc // Then try to parse according to the rules of the definition. - declRule.keywordChoices |> MutMap.get(name) is Some(rule) then + declRule.keywordChoices |> MutMap.get(name) is Some(ParseRule.ParseRule as rule) then consume parseRule(0, rule) handleDirective(acc) _ :: _ then modCont of expr(0, termOptions) :: acc diff --git a/hkmc2/shared/src/test/mlscript/apps/AccountingTest.mls b/hkmc2/shared/src/test/mlscript/apps/AccountingTest.mls index b099eb5570..1e739e7b78 100644 --- a/hkmc2/shared/src/test/mlscript/apps/AccountingTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/AccountingTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/apps/Accounting.mls" diff --git a/hkmc2/shared/src/test/mlscript/apps/CSVTest.mls b/hkmc2/shared/src/test/mlscript/apps/CSVTest.mls index 6d3fac655c..5b8b9f6fca 100644 --- a/hkmc2/shared/src/test/mlscript/apps/CSVTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/CSVTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/apps/CSV.mls" diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/ParserTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/ParserTest.mls index 720c9a34f7..932ab8e79a 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/ParserTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/ParserTest.mls @@ -458,3 +458,13 @@ mkStr of "A", x is Str //│ = "Afalse" + +// Leading separators must resume module parsing, not return the parser function. +example of ";; ;; 42" +//│ > «42» + +// Exercise continuation lookup for application, operators, and type expressions. +example of "f x + y" +example of "let x: int = 42" +//│ > «f x + y» +//│ > «let x : int = 42» diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index f8538003f8..9e18b1c946 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -74,6 +74,8 @@ declare class WeakMap[K, V] declare module WeakMap declare class Error with val message: Str + val name: Str + val stack: Str | undefined declare fun Error(...args): Error declare class TypeError extends Error declare fun TypeError(...args): TypeError @@ -135,6 +137,8 @@ declare class Str with fun slice(...bounds): Str fun substring(...bounds): Str fun split(...separatorAndLimit): Array[Str] + fun replace(search: Str | RegExp, replacement: Str | Function): Str + fun replaceAll(search: Str | RegExp, replacement: Str | Function): Str fun repeat(count: Int): Str fun toUpperCase(): Str fun toLowerCase(): Str diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index b90e7e8c6c..2eaf406f16 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.174: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.178: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -141,15 +141,15 @@ new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.154: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.158: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.155: fun join(...separators): Str +//│ ║ l.159: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.156: fun reverse(): Array[T] +//│ ║ l.160: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.157: fun sort(...compare): Array[T] +//│ ║ l.161: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.158: ... (more lines omitted) ... +//│ ║ l.162: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2] @@ -160,15 +160,15 @@ new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.154: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.158: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.155: fun join(...separators): Str +//│ ║ l.159: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.156: fun reverse(): Array[T] +//│ ║ l.160: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.157: fun sort(...compare): Array[T] +//│ ║ l.161: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.158: ... (more lines omitted) ... +//│ ║ l.162: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.188: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.192: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 1994ba7da3..81aa64df99 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.174: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.178: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/newres/LegacyGenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/LegacyGenericMethods.mls new file mode 100644 index 0000000000..eeefae93f3 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/LegacyGenericMethods.mls @@ -0,0 +1,12 @@ +:. + +import "../../mlscript-compile/FingerTreeList.mls" + +// Calling a generic method imported from a legacy fixture must preserve the +// class's type argument when interpreting the method's annotated result. +// FIXME: The method result currently exits the method scope while its mark +// still denotes the enclosing class scope. This blocks LazyFingerTree. +:fixme +:expect 42 +FingerTreeList.mk(42).at(0) +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: class:FingerTree⁰ vs term:FingerTree⁰/at⁰ for InstanceShape(T⁰) diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls index 864b37c64d..4abd9e68c0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls @@ -99,7 +99,7 @@ pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Int' does not contain member 'toUpperCase' //│ ║ in ../decls/Prelude.mls -//│ ║ l.123: val length: Int +//│ ║ l.125: val length: Int //│ ╙── ^^^ // Conjunction produces a pair, not the string tested by its left operand. diff --git a/hkmc2/shared/src/test/mlscript/newres/ShapeFixture.mls b/hkmc2/shared/src/test/mlscript/newres/ShapeFixture.mls new file mode 100644 index 0000000000..2248ac1031 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ShapeFixture.mls @@ -0,0 +1,59 @@ +:. +:js + +import "../../mlscript-compile/Block.mls" +import "../../mlscript-compile/Shape.mls" +import "../../mlscript-compile/Option.mls" + +class Pair(val left: Int, val right: Str) +val pair = Block.ConcreteClassSymbol("Pair", Pair, Option.Some([ + Block.Symbol("left"), Block.Symbol("right")]), []) +//│ pair = ConcreteClassSymbol( +//│ "Pair", +//│ fun Pair { class: class Pair }, +//│ Some([Symbol("left"), Symbol("right")]), +//│ [] +//│ ) + +:expect "Class(Pair, [Dyn, Dyn])" +Shape.show(Shape.silh(Block.Cls(pair, Block.ValueLit(null)))) +//│ = "Class(Pair, [Dyn, Dyn])" + +:expect "Arr(Dyn, Dyn, Dyn)" +Shape.show(Shape.silh(Block.Tup(3))) +//│ = "Arr(Dyn, Dyn, Dyn)" + +:expect ["Lit(42)"] +val pairShape = Shape.Class(pair, [Shape.Lit(42), Shape.Lit("hello")]) +Shape.sel(pairShape, Shape.Lit("left")).map((s, ...) => Shape.show(s)) +//│ = ["Lit(42)"] +//│ pairShape = Class( +//│ ConcreteClassSymbol( +//│ "Pair", +//│ fun Pair { class: class Pair }, +//│ Some([Symbol("left"), Symbol("right")]), +//│ [] +//│ ), +//│ [Lit(42), Lit("hello")] +//│ ) + +:expect ["Lit(\"hello\")"] +Shape.sel(pairShape, Shape.Lit("right")).map((s, ...) => Shape.show(s)) +//│ = ["Lit(\"hello\")"] + +:expect [] +Shape.sel(pairShape, Shape.Lit("missing")) +//│ = [] + +:expect [true, false, false] +[Shape.static(Shape.Lit(42)), Shape.static(Shape.Lit("Int")), + Shape.static(Shape.silh(Block.Tup(1)))] +//│ = [true, false, false] + +:expect "_name" +Block.showSymbol(Block.Symbol("$name")) +//│ = "_name" + +:expect "42 then 1" +Block.showArm(Block.Arm(Block.Lit(42), Block.Return(Block.ValueLit(1)))) +//│ = "42 then 1" diff --git a/hkmc2/shared/src/test/mlscript/newres/SourceLocations.mls b/hkmc2/shared/src/test/mlscript/newres/SourceLocations.mls new file mode 100644 index 0000000000..635a0174d4 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/SourceLocations.mls @@ -0,0 +1,43 @@ +:. + +// Source metadata is expanded at its lexical use, including inside functions. +:expect true +source.line > 0 +//│ = true + +:expect "" +fun location() = source.name +location() +//│ = "" + +:expect "SourceMarker" +module SourceMarker with + fun nested() = source.name +SourceMarker.nested() +//│ = "SourceMarker" + +:expect true +source.file.endsWith("SourceLocations.mls") +//│ = true + +// A captured reference still denotes the builtin module. +:expect true +fun line() = source.line +line() > 0 +//│ = true + +// A user binding with the same name keeps ordinary member access. +:expect [42, "user", "custom"] +fun shadow() = + let source = (line: 42, name: "user", file: "custom") + [source.line, source.name, source.file] +shadow() +//│ = [42, "user", "custom"] + +:expect 1 +fun consecutiveLines() = + val first = source.line + val second = source.line + second - first +consecutiveLines() +//│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 386b19eb6e..c214c279ff 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.343: fun +//│ ║ l.347: fun //│ ║ ^^^ -//│ ║ l.344: const(c: Int): Int32 +//│ ║ l.348: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 58a652296380e2851f2c9b9e40f6c0a6b9e1da4b Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 1 Oct 2026 22:51:40 +0800 Subject: [PATCH 168/228] Minor --- hkmc2/shared/src/test/mlscript-compile/Block.mls | 1 + hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls | 5 +---- hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls | 2 +- .../src/test/mlscript-compile/apps/parsing-web-demo/main.mls | 1 + .../mlscript-compile/apps/parsing/ParseRuleVisualizer.mls | 5 +++-- .../shared/src/test/mlscript-compile/apps/parsing/Parser.mls | 1 + 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Block.mls b/hkmc2/shared/src/test/mlscript-compile/Block.mls index 128751d896..2671d1faaf 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Block.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Block.mls @@ -1,4 +1,5 @@ #lang(0.3.x) + import "./Predef.mls" import "./Option.mls" import "./StrOps.mls" diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls index 3355fbcd58..5048831b10 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls @@ -10,9 +10,6 @@ import "../Predef.mls" open Predef - - - data class Project(val num: Str) @@ -74,7 +71,7 @@ data class Report(fileName) with lines.filter((x: Line, ...) => not x.isMatchable).map((x: Line, ...) => x.balance).reduce((a, b, ...) => a +. b, 0) ) ~ "|" -fun process(filename: Str, k: Report -> Any) = +fun process(filename: Str, k: Report -> ()) = let report = Report(filename) report.init() k(report) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls index 53045ce40d..762a0410c3 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls @@ -59,7 +59,7 @@ fun toArrays(strData: Str) = // We found a non-quoted value. matches.3 - (arrData.at(arrData.length - 1) as Array[Str]).push(strMatchedValue) + (arrData.at(arrData.length - 1) as Array).push(strMatchedValue) arrData diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls index 53ec832ce1..32a7693598 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls @@ -1,4 +1,5 @@ #lang(0.3.x) + // Note: This file does not have a corresponding test file. It is recommended to // test it through the following steps. // diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls index f645754632..7e61966b03 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRuleVisualizer.mls @@ -1,4 +1,5 @@ #lang(0.3.x) + import "../../Predef.mls" import "../../Stack.mls" import "../../Iter.mls" @@ -41,7 +42,7 @@ fun render[T](rr: dyn, title: Str, rule: ParseRule.ParseRule[T]) = None then lhs fun diagram(choicesOpt) = rr.Diagram of if choicesOpt is Some(choices) then choices else [] - fun renderChoice(parentRule, choice: Choice[Any]) = if choice is + fun renderChoice(parentRule, choice: Choice) = if choice is Choice.End then tracer.print of "found Choice.End" None @@ -89,7 +90,7 @@ fun render[T](rr: dyn, title: Str, rule: ParseRule.ParseRule[T]) = set referencedKinds = new Set() diagrams.push of ...currentKinds Iter.mapping of kind => - let theRule = (syntaxKinds |> MutMap.get(kind) |> Option.unsafe.get) as ParseRule.ParseRule[T] + let theRule = syntaxKinds |> MutMap.get(kind) |> Option.unsafe.get [kind, diagram of renderRule(theRule)] Iter.toArray() diagrams diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls index 13efadae75..2b2ae3496c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls @@ -1,4 +1,5 @@ #lang(0.3.x) + import "../../Predef.mls" import "../../Option.mls" import "../../Stack.mls" From b088388a5e6750193f374c13fb2ba9f87fb633c8 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 1 Oct 2026 23:43:58 +0800 Subject: [PATCH 169/228] Fix migration shape propagation and migrate Predef --- doc/new-resolution-suite-migration.md | 33 +++-- .../scala/hkmc2/TypeInstantiationTest.scala | 11 ++ .../main/scala/hkmc2/codegen/ErasedType.scala | 1 + .../main/scala/hkmc2/invalml/InvalML.scala | 1 + .../scala/hkmc2/semantics/Elaborator.scala | 35 +++--- .../scala/hkmc2/semantics/NewResolver.scala | 115 ++++++++++++++---- .../hkmc2/semantics/NewResolverState.scala | 35 ++++-- .../scala/hkmc2/semantics/PatternShape.scala | 3 +- .../main/scala/hkmc2/semantics/Resolver.scala | 6 + .../src/main/scala/hkmc2/semantics/Term.scala | 1 + .../scala/hkmc2/semantics/TypeShape.scala | 6 +- .../hkmc2/semantics/flow/FlowAnalysis.scala | 1 + .../src/test/mlscript-compile/Block.mls | 12 +- .../src/test/mlscript-compile/LazyArray.mls | 10 +- .../mlscript-compile/LegacyGenericLibrary.mls | 5 + .../src/test/mlscript-compile/MutMap.mls | 9 +- .../src/test/mlscript-compile/Predef.mjs | 21 ++-- .../src/test/mlscript-compile/Predef.mls | 43 ++++--- .../src/test/mlscript-compile/Shape.mls | 13 +- .../test/mlscript-compile/apps/Accounting.mls | 12 +- .../src/test/mlscript-compile/apps/CSV.mls | 4 +- .../apps/parsing-web-demo/Examples.mls | 2 +- .../apps/parsing-web-demo/main.mls | 18 +-- .../apps/parsing/Keywords.mls | 2 +- .../apps/parsing/ParseRule.mls | 2 +- .../mlscript-compile/apps/parsing/Parser.mls | 26 ++-- .../mlscript-compile/apps/parsing/Rules.mls | 2 +- .../mlscript/basics/MultiParamListClasses.mls | 22 ++-- .../src/test/mlscript/basics/MutVal.mls | 8 +- .../test/mlscript/codegen/BlockPrinter.mls | 8 +- .../test/mlscript/codegen/CaseShorthand.mls | 12 +- .../src/test/mlscript/codegen/PartialApps.mls | 18 +-- .../src/test/mlscript/codegen/SetStmt.mls | 13 ++ .../mlscript/ctx/NestedUsingInstances.mls | 8 +- hkmc2/shared/src/test/mlscript/ctx/Summon.mls | 6 +- .../src/test/mlscript/ctx/TypeResolution.mls | 32 +++-- .../src/test/mlscript/flows/BasicFlows.mls | 2 +- .../mlscript/flows/LeadingDotAccesses.mls | 2 +- .../src/test/mlscript/flows/SelExpansion.mls | 18 +-- .../src/test/mlscript/newres/Basics.mls | 2 +- .../test/mlscript/newres/BuiltinFunction.mls | 2 +- .../src/test/mlscript/newres/Functions.mls | 10 +- .../mlscript/newres/LegacyGenericMethods.mls | 15 ++- .../test/mlscript/newres/MapOptionFlow.mls | 31 +++++ .../test/mlscript/newres/MemberVariables.mls | 39 ++++++ .../test/mlscript/newres/NestedModules.mls | 2 +- .../mlscript/newres/PatternRefinement.mls | 49 ++++++++ .../test/mlscript/newres/PredefInterfaces.mls | 25 ++++ .../mlscript/newres/RecursiveEquality.mls | 21 ++++ .../newres/TypeInterfaceDiagnostics.mls | 69 ++++++----- 50 files changed, 591 insertions(+), 252 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript-compile/LegacyGenericLibrary.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/MapOptionFlow.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/MemberVariables.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/PatternRefinement.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/RecursiveEquality.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 3066bdfb19..f0d4196bf0 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -33,13 +33,14 @@ imply that its dependencies have been migrated. | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | -| Main (including quotes, UPS, and regression fixtures) | 41 | 7 | 48 | +| Main (including quotes, UPS, and regression fixtures) | 42 | 7 | 49 | | Applications | 17 | 3 | 20 | | Nofib | 37 | 2 | 39 | | WASM | 1 | 0 | 1 | -| Total | 96 | 12 | 108 | +| Total | 97 | 12 | 109 | -These totals include the new-resolution `NamedFieldLibrary` regression fixture. +These totals include the new-resolution `NamedFieldLibrary` regression fixture +and the deliberately legacy `LegacyGenericLibrary` mixed-mode regression fixture. They count language directives in source files, not test-runner test cases. ## Migration procedure @@ -85,17 +86,18 @@ resolver. The remaining fixtures need the following work: | Legacy fixtures | Concrete work and known failures | | --- | --- | -| `LazyFingerTree` | Indexable annotations and casts expose an assertion when calling the imported generic `FingerTree.at` method: its result crosses the class and method scopes incorrectly. `newres/LegacyGenericMethods.mls` reproduces it with `FingerTreeList.mk(42).at(0)`. | +| `LazyFingerTree` | Supply indexable interfaces and check its consumers. Imported generic method results are covered by the passing `newres/LegacyGenericMethods.mls`, including `FingerTreeList.mk(42).at(0)`. | | `FingerTreeList` | Enabling new resolution exceeds the compilation test's time limit. Diagnose inference growth around recursive trees and tuple spreads before calling this an annotation-only port. | -| `Runtime`, `Rendering`, `Predef` | Audit callable and host interfaces; `Predef.use` also depends on contextual argument insertion. No separate design blocker is established for their missing annotations. | +| `Runtime`, `Rendering` | Audit callable and host interfaces. No separate design blocker is established for their missing annotations. | | `CSP`, `QuoteExample1` | Quasiquote type selections and wildcard-reference lowering. | | `parsing/Lexer` | Calls to token constructors with trailing `using` parameters need automatic contextual argument insertion; `newres/LexerMigration.mls` records the missing behavior. | -| `parsing/ParseRule` | Correct `andThen`'s helper signature: it transforms a rule's result into a pair with the following rule's result, rather than preserving `B`. Compilation also triggers a repeated lexical-scope-exit assertion, including with a generic helper and an explicit pair result. This is a compiler bug to isolate, not an interface-design decision. | -| `parsing/Extension` | Giving rules their `ParseRule[Tree]` interface exposes a lexical-scope assertion in `ParseRule.andThen`, even while the dependency remains on legacy resolution. Explicit generic arguments and a result annotation do not resolve it. | +| `parsing/ParseRule` | Correct `andThen`'s helper signature: it transforms a rule's result into a pair with the following rule's result, rather than preserving `B`. Recheck migration with lexical type captures preserved in legacy signatures. | +| `parsing/Extension` | Give rules their `ParseRule[Tree]` interfaces and validate calls to `ParseRule.andThen` together with that dependency's migration. | | `nofib/lastpiece`, `nofib/sorting` | Both exceed the compilation time limit under new resolution. `sorting` first needs `int_of_char(c: Str)`; adding it reveals the timeout. Diagnose inference growth before retrying these ports. | Array callbacks must accept the arguments supplied by the JS API: three for -`map`/`filter`/`forEach` and four for `reduce`. An unused rest parameter satisfies +`map`/`filter`/`forEach` and four for `reduce`. Use `pass1(f)` or `pass2(f)` when +adapting a function that uses fewer arguments. An unused rest parameter also satisfies this existing rule (`newres/Arrays.mls`); it does not require relaxing callback arity checking. Missing host declarations and ordinary parameter annotations do not require a language-design discussion. Compiler assertions and timeouts need @@ -106,9 +108,12 @@ To reproduce a blocker, temporarily add the language directive to the named compilation fixture and run `ctest ` or `catest ` as appropriate. The blocked fixtures retain their existing resolution mode. -`Block`, `Shape`, `LazyArray`, `Accounting`, `CSV`, `Parser`, +`Predef`, `Block`, `Shape`, `LazyArray`, `Accounting`, `CSV`, `Parser`, `ParseRuleVisualizer`, and the parser web demo use new resolution. Nullable host -results are cast after their existing checks; mutable traversal arrays use `mut`. +results use pattern matching where possible; mutable traversal arrays use `mut`. +Parser map interfaces carry their element types through `Option`, so ordinary +`Some(rule)` bindings retain the rule interface. Member `let` bindings contribute +their initializer and assignment shapes, including in the web demo. The parser unwraps optional continuation rules and resumes module parsing after leading separators. The virtual `source` fields expand in both resolution modes (`newres/SourceLocations`); constructor metadata is covered by `newres/ShapeFixture`. @@ -127,8 +132,8 @@ packaging constraint. the worksheet acceptance case. - **Mutation and control flow:** `newres/MutationFlow.mls` records a reassigned array checked against its initializer's tuple length; accumulating both shapes - would still reject valid later indexing. `codegen/SetStmt` needs argument flow - through its update callback. Member-variable definitions also need work. + would still reject valid later indexing. `newres/MemberVariables.mls` covers + shape accumulation through local and member-variable assignments. Handler inference needs separate flows for the receiver, values passed to resumptions, and abortive results (`newres/HandlerResults.mls` and `codegen/ScopedBlocksAndHandlers`). @@ -146,6 +151,10 @@ packaging constraint. - **Cross-block flow:** remaining cases include parameter flow in `basics/MiscArrayTests`, unfinished closures in `codegen/FirstClassFunctionTransform`, and the unresolved receiver in `codegen/ObjectMethodDebinding`. +- **Recursive inference:** `newres/RecursiveEquality.mls` preserves the unannotated + deep-equality function and its expected results. Resolution currently overflows + the stack on its recursive array callback. `Predef.equals` uses the public + `Any × Any -> Bool` interface; that annotation does not fix unannotated inference. ### Patterns and generated references diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala index a8b6e8cb83..7befe21689 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeInstantiationTest.scala @@ -125,3 +125,14 @@ class TypeInstantiationTest extends AnyFunSuite: assert(local.currentShapes(using source).size == 1) assert(local.currentShapes(using other.inGraph(source)).size == 1) assert(consumer.allocatedTypeInstanceCount == 1) + + test("imported callbacks return to the consumer without growing graph-view chains"): + val exporter = new Elaborator.State().newResolverState + val middle = new Elaborator.State().newResolverState + val consumer = new Elaborator.State().newResolverState + val imported = consumer.inGraph(middle.inGraph(exporter)) + // A worksheet can observe successful values without detecting an extra + // import-view layer per callback. Check the graph's boundedness directly. + (1 to 1000).foreach: _ => + assert(imported.inGraph(consumer) eq consumer) + assert(imported.inGraph(imported) eq imported) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala index 69e95d1332..76b2d3dfcc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ErasedType.scala @@ -241,6 +241,7 @@ object ErasedType: * nominal hierarchy published by Erasure. */ def eraseSign(sign: Term)(using Erasure): Opt[ErasedValueType] = sign match + case Capture(base, _) => eraseSign(base) case CompType(lhs, rhs, true) => // * A union is kept as a transient `Union` surface form; `canonicalize` collapses it to the members' LUB. for diff --git a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala index 1b81a6a048..48733bf839 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/invalml/InvalML.scala @@ -128,6 +128,7 @@ class InvalTyper(using codegen.Erasure)(using elState: Elaborator.State, tl: TL) def mono(ty: Term, pol: Bool): Type = monoOrErr(typeAndSubstType(ty, pol), ty) ty match + case Capture(base, _) => typeAndSubstType(base, pol) case Ref(sym: LocalSymbol) => log(s"Type lookup: ${sym.nme} ${sym.uid} ${map.keySet}") map.get(sym.uid) match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 86064cbd82..61951e9b72 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -166,16 +166,19 @@ object Elaborator: /** Explicit bindings in any enclosing scope take precedence over every wildcard open. */ def get(name: Str)(using config: Config): Opt[Ctx.Elem] = - getExplicit(name).orElse: + get(name, config.language.useNewResolution) + + def get(name: Str, captureScopes: Bool)(using config: Config): Opt[Ctx.Elem] = + getExplicit(name, captureScopes).orElse: if config.language.useNewResolution then val sources = visibleWildcardOpens.distinct if sources.isEmpty then N else S(Ctx.WildcardElem(name, sources)) else N - private def getExplicit(name: Str)(using config: Config): Opt[Ctx.Elem] = + private def getExplicit(name: Str, captureScopes: Bool)(using config: Config): Opt[Ctx.Elem] = env.get(name).orElse: - val inherited = parent.flatMap(_.getExplicit(name)) - if config.language.useNewResolution then inherited.map(capture) + val inherited = parent.flatMap(_.getExplicit(name, captureScopes)) + if captureScopes then inherited.map(capture) else inherited private def capture(elem: Ctx.Elem): Ctx.Elem = @@ -1209,6 +1212,7 @@ extends Importer: if newResolution then listenTerm(lhs)(_ => ()) assignArrayElement(lhs, rhs) + assignVariable(lhs, rhs) Term.Assgn(lhs, rhs) def ifLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit, loc: Opt[Loc]): Term.IfLike = @@ -1393,7 +1397,7 @@ extends Importer: raise: ErrorReport(msg"Cannot use 'this' outside of an object scope" -> tree.toLoc :: Nil) error - case id @ Ident(name) => ident(id).map(interpretRef(_, interp)).getOrElse: + case id @ Ident(name) => ident(id, interp).map(interpretRef(_, interp)).getOrElse: raise(ErrorReport(msg"Name not found: $name" -> id.toLoc :: Nil)) error case TyApp(lhs, targs) => @@ -1726,7 +1730,7 @@ extends Importer: case PrefixApp(kw @ Keywrd(Keyword.`yield` | Keyword.`yield*`), body) => if ctx.inGenerator then val synthIdent = new Tree.Ident(kw.kw.name).withLocOf(kw) - app(ident(synthIdent).get, Term.Tup(PlainFld(subterm(body)) :: Nil)(DummyTup))(DummyApp, N, FlowSymbol("yield")) + app(ident(synthIdent, Trm).get, Term.Tup(PlainFld(subterm(body)) :: Nil)(DummyTup))(DummyApp, N, FlowSymbol("yield")) else raise: ErrorReport(msg"Yield expressions are not allowed in this context." -> tree.toLoc :: Nil) @@ -2767,14 +2771,17 @@ extends Importer: msg"Expected a parameter list (a tuple of parameters), but found ${t.describe}" -> t.toLoc :: Nil (ParamList(ParamListFlags.empty, Nil, N)(t.toLoc), ctx) - def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = ctx.get(id.name) match - case candidate @ (S(_: Ctx.WildcardElem) | N) => - // Primitive operators are implicit bindings outside the Ctx environments; - // like explicit bindings, they take precedence over wildcard sources. - state.builtinOpsMap.get(id.name) match - case S(bi) => S(bi.ref(id)) - case N => candidate.map(_.ref(id)) - case S(elem) => S(elem.ref(id)) + // Imported legacy signatures are also consumed by the new resolver. Keep + // their lexical type captures even though legacy term lookup ignores scopes. + def ident(id: Ident, interp: Interpretation)(using Ctx): Ctxl[Opt[Term]] = + ctx.get(id.name, newResolution || interp == Tpe) match + case candidate @ (S(_: Ctx.WildcardElem) | N) => + // Primitive operators are implicit bindings outside the Ctx environments; + // like explicit bindings, they take precedence over wildcard sources. + state.builtinOpsMap.get(id.name) match + case S(bi) => S(bi.ref(id)) + case N => candidate.map(_.ref(id)) + case S(elem) => S(elem.ref(id)) def pattern(t: Tree): Ctxl[Pattern] = pattern(t, Map.empty) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 141f3b2b8c..53d7252470 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -145,11 +145,13 @@ class NewResolver: else result.publish(TypeShape.Record(record, fields.reverse.distinctBy(_.sym.nme).reverse.map(field => field -> typeResolution(field.rhs)))) // Signed numeric literals use operator applications in the syntax tree. - // Like unsigned singleton types, they have no member interface here. + // Their primitive interface is not recovered from these applications yet. case App(Ref(op: BuiltinSymbol), Tup(Fld(_, Lit(_: Tree.IntLit | _: Tree.DecLit), _) :: Nil)) if op.nme === "-" || op.nme === "+" => result.publish(TypeShape.Abstract) - case _: Tup | _: Lit | Missing | Error() => + case literal: Lit => + listenTerm(literal)(shape => result.publish(TypeShape.Inferred(shape))) + case _: Tup | Missing | Error() => result.publish(TypeShape.Abstract) case _ => listen(term, discardMarks = true): case shape: SymShape => shape.sym.onComplete: () => @@ -1855,7 +1857,7 @@ class NewResolver: def reject(sh: TermShape)(using NewResolverState): Unit = rstate.markError(res) resolError(res, sh.diagnostic(msg"${sh.describe.capitalize} cannot be used as a constructor pattern.")) - def classPattern(cls: ClassLikeDef)(using NewResolverState): Unit = if select(cls.sym) then + def classPattern(cls: ClassDef | ModuleOrObjectDef)(using NewResolverState): Unit = if select(cls.sym) then val assoc = res.arguments match case N => Nil case S(args) => cls.paramsOpt match @@ -1888,8 +1890,18 @@ class NewResolver: resolError(res, msg"Pattern argument requires an accessible constructor field." -> p.toLoc :: Nil) Nil if !rstate.hasError(res) then - val psh = CtorPatternShape(cls, assoc, res, FlowSymbol.pat()(using rstate.owner)) - if res.currentShapes.add(psh) then res.notifyShapeListeners(psh) + val site = FlowSymbol.pat()(using rstate.owner) + def publish(marks: Ls[Marks])(using NewResolverState): Unit = + val psh = CtorPatternShape(cls, assoc, res, site, marks) + if res.currentShapes.add(psh) then res.notifyShapeListeners(psh) + cls match + // An ambiguous lookup can publish several constructors. Pair each + // declaration only with its own references; the lookup diagnoses + // competing candidates independently of this shape subscription. + case _: ClassDef => listenClass(lhs)(ref => + if ref.definition eq cls then publish(ref.marks) + , _ => ()) + case _: ModuleOrObjectDef => publish(Nil) def valuePattern(sh: TermShape)(using NewResolverState): Unit = shapeParts(sh).value match case Marked(ds: DefnShape, _) => ds.defn match case cls: ClassDef => classPattern(cls) @@ -1914,8 +1926,8 @@ class NewResolver: case S(sym: PatternSymbol) => select(sym) case S(sym: (ClassSymbol | ModuleOrObjectSymbol)) => sym.defn match - case S(cls) => classPattern(cls) - case N => softAssert(false, "Completed pattern member has no definition") + case S(cls: (ClassDef | ModuleOrObjectDef)) => classPattern(cls) + case _ => softAssert(false, "Completed pattern member has no definition") case N => fromBMS(bms, FlowSymbol.pat()(using rstate.owner), sh.markss, valuePattern, lhs, _ => (), false) case sh: TermShape => valuePattern(sh) @@ -2003,7 +2015,19 @@ class NewResolver: case Pattern.Annotated(pat, _) => matchShapePat(shape, pat)(matched) case ctor: Pattern.Constructor => def listenConstructor(psh: PatternShape)(using NewResolverState): Unit = psh match - case CtorPatternShape(cls, fs, _, resSym) => + case CtorPatternShape(cls, fs, _, resSym, constructorMarks) => + // Nominal views already live outside the instance. Cancel the + // constructor's own exit, retaining its enclosing lexical path. + val context = cls match + case _: ClassDef => MarkedShape.enter(unitResultShape, ResolutionBoundary(cls.sym), N).exit(constructorMarks) match + case Marked(_, marks) => marks :: Nil + case NoShape => lastWords("A wildcard constructor entry cannot reject its own exit") + case _: ModuleOrObjectDef => Nil + def atPattern(tpe: DeclaredType)(receive: Listener)(using NewResolverState): Unit = + listenTypeViews(tpe): candidate => + transportShape(candidate, context) match + case value: TermShape => receive(value) + case NoShape => () def check(sh: TermShape, unknown: Opt[TermShape])(using NewResolverState): Unit = if sh.isInstanceOfClass(cls) then fs.foreach: (bms, pat) => @@ -2038,18 +2062,34 @@ class NewResolver: softAssert(false, "Matched constructor is missing its field") member(sh.getMember(bms.nme), TypeSubstitution.empty) matched(if fs.forall((_, pat) => preservesInput(pat)) then sh else unknownOutput) + // A structural annotation describes possible class instances too. + // A successful class test supplies the tested class's declared + // interface even when the input did not have a nominal interface. + // Interpret the constructor reference with its lexical captures: + // inside C, testing against C must not place the refined value + // outside C's scope before a subsequent field read leaves it. + def testedClass(using NewResolverState): DeclaredType = + rstate.patternTypes.getOrElseUpdate((new Identity(ctor), cls.sym), { + val resolution = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) + resolution.publish(TypeShape.Nominal(cls)) + declaredType(resolution, Map.empty) + }) def narrow(sh: TermShape)(using NewResolverState): Unit = sh match + case Marked(_: RecordTypeShape, _) => + atPattern(testedClass)(check(_, N)) case Marked(unknown: UnknownValueShape, _) => // A class test can succeed for an external input. Its fields then // expose their declarations, not shapes observed in local calls. // Unknown type arguments retain the original diagnostic witness. - val base = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) - base.publish(TypeShape.Nominal(cls)) + val base = testedClass.resolution val arg = new TypeResolution(unknown.source, messages => resolError(unknown.source, messages)) - arg.publish(TypeShape.Inferred(sh)) + val contextual = transportShape(sh, inverseMarks(context)) + contextual match + case value: TermShape => arg.publish(TypeShape.Inferred(value)) + case NoShape => () val applied = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) applied.publish(TypeShape.Applied(base, cls.tparams.map(_ => arg))) - listenTypeViews(declaredType(applied, Map.empty))(check(_, S(sh))) + atPattern(declaredType(applied, Map.empty))(check(_, S(sh))) case Marked(opaque: OpaqueTypeShape, marks) => UnknownValueShape(opaque.source)(opaque.provenance).exit(marks) match case value: TermShape => narrow(value) @@ -2061,12 +2101,7 @@ class NewResolver: // unrelated constructor calls elsewhere in the unit. // Reuse the abstract instantiation when recursive flow reaches // this test again, so its omitted arguments have stable identities. - val tested = rstate.patternTypes.getOrElseUpdate((new Identity(ctor), cls.sym), { - val resolution = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) - resolution.publish(TypeShape.Nominal(cls)) - declaredType(resolution, Map.empty) - }) - listenTypeViews(tested): candidate => + atPattern(testedClass): candidate => if candidate.isInstanceOfClass(nominal.defn) then check(candidate, N) case _ => check(sh, N) shape match @@ -2579,13 +2614,28 @@ class NewResolver: resolError(nw, shape.diagnostic(msg"${shape.describe.capitalize} cannot be instantiated with keyword 'new'.")) ) + /** Assignments return to the binding's lexical scope before contributing + * candidates. Reads must see every assigned shape, including assignments + * inside nested functions, without recording the RHS's symbol as their target. + */ + private[semantics] def assignVariable(lhs: Term, rhs: Term)(using NewResolverState): Unit = + def assign(target: Term, shape: TermShape)(using NewResolverState): Unit = target match + case Capture(base, thru) => + MarkedShape.exit(shape, ResolutionBoundary(thru), N) match + case value: TermShape => assign(base, value) + case NoShape => () + case MemberRef(sym: TermSymbol) if sym.k is LetBind => + publishActivated(rstate.memberVariable(sym), shape) + case SimpleRef(sym: LocalSymbol) => publishActivated(sym, shape) + case Ref(sym: LocalSymbol) => publishActivated(sym, shape) + case _ => () + listenTerm(rhs)(assign(lhs, _)) + def defineVar(sym: LocalSymbol | TermSymbol, rhs: Term)(loc: Opt[Loc])(using NewResolverState): DefineVar = if newResolution then sym match case sym: TermSymbol => - // symShape(sym, rhs) - // ??? - // sym.defn.get - println(s"TODO: defineVar for TermSymbol ${sym.showDbg}") + listenTerm(rhs): sh => + publishActivated(rstate.memberVariable(sym), sh) case sym: LocalSymbol => listen(rhs): sh => publishActivated(sym, sh) @@ -2704,6 +2754,7 @@ class NewResolver: // Adding an exit here would create a mismatch because we do not track module captures explicitly. val exited = sym match case _: ModuleOrObjectSymbol => sh + case sym: TermSymbol if sym.k is LetBind => sh case _ => MarkedShape.exit(sh, ResolutionBoundary(sym), S(resSym)) (exited match case value: TermShape => transportShape(value, markss) @@ -2765,6 +2816,8 @@ class NewResolver: // ??? // TODO error? wrappedListener(defnShapes.getOrElseUpdate(sym, DefnShape(d, extsh))) ) + case N if sym.asTrm.exists(_.k is LetBind) => + listenMemberVariable(sym.asTrm.get)(wrappedListener) case N => // sym.defnListeners += (d => listener(defnShapes.getOrElseUpdate(sym, DefnShape(d)))) softAssert(false, s"Symbol definition of ${sym} is not set upon completion of ${bms}") @@ -2873,7 +2926,7 @@ class NewResolver: start: shape => if aggregate.currentShapes.add(shape) then aggregate.notifyShapeListeners(shape) - def listen(trm: Term, discardMarks: Bool = false)(receive: ShapeListener[Shape])(using NewResolverState): Unit = + private def activationListener(receive: ShapeListener[Shape])(using NewResolverState): ShapeListener[ShapeEvent] = // A request and event are compatible when their activation maps agree on // shared keys; an empty source request accepts all activations. Unwrap the // event and supply that activation to the receiving operation, without @@ -2881,11 +2934,20 @@ class NewResolver: val requestedInstances = rstate.instances def compatible(instances: TypeSubstitution): Bool = requestedInstances.forall((parameter, instance) => instances.get(parameter).forall(_ eq instance)) - val listener: ShapeListener[ShapeEvent] = event => event match + event => event match case ActivatedShapeEvent(value, instances) => if compatible(instances) then receive(value)(using rstate.withInstances(requestedInstances.withOverrides(instances))) case value: TermShape => receive(instantiateShape(value, rstate.instances)) case symbol: SymShape => receive(symbol) + + private def listenMemberVariable(sym: TermSymbol)(receive: Listener)(using NewResolverState): Unit = + rstate.memberVariable(sym).subscribeToShapes(activationListener: + case value: TermShape => receive(value) + case _: SymShape => softAssert(false, "Member variables contain only term shapes") + ) + + def listen(trm: Term, discardMarks: Bool = false)(receive: ShapeListener[Shape])(using NewResolverState): Unit = + val listener = activationListener(receive) log(s"listen: trm = ${trm.showDbg}") trm.addShapeListener(listener) trm match @@ -3092,6 +3154,11 @@ class NewResolver: symbol.defn match case S(defn) => completed(defn) case N => symbol.defnListeners += completed + case ref @ MemberRef(sym: TermSymbol) if sym.k is LetBind => + // A let-binding has no evaluation boundary of its own. Surrounding + // function/class captures still enter and exit through Capture normally. + recordValueTarget(ref, sym) + rstate.memberVariable(sym).subscribeToShapes(listener) case ref @ MemberRef(sym: TermSymbol) => listenDefn(sym, sh => MarkedShape.exit(sh, ResolutionBoundary(sym), S(ref.resSym)) match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 0c0687b05f..38cb147bfc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -86,15 +86,20 @@ final class NewResolverState private ( private[hkmc2] def inGraph(graph: NewResolverState): NewResolverState = rebase(graph.contextBase.getOrElse(graph), root).withInstances(instances) private def rebase(graph: NewResolverState, destination: NewResolverState): NewResolverState = - val origin = source.fold(graph)(_.rebase(graph, destination)) - if origin.root eq root then origin - else if root eq destination then - root.views.getOrElseUpdate(origin, new NewResolverState(owner, S(root), S(origin), N, TypeSubstitution.empty)) - else root.views.get(origin).getOrElse: - // A previously unused path can require a view of an intermediate exporter. - // Memoize that read-only view in the consumer, never in the exporter. - destination.inheritedViews.getOrElseUpdate((root, origin), - new NewResolverState(owner, S(root), S(origin), N, TypeSubstitution.empty)) + // A callback registered in this consuming unit already names its graph. + // Sending it through the current import view would create X(Y(X(...))) + // when an imported callback invokes a consumer callback and returns. + if graph.root eq root then graph + else + val origin = source.fold(graph)(_.rebase(graph, destination)) + if origin.root eq root then origin + else if root eq destination then + root.views.getOrElseUpdate(origin, new NewResolverState(owner, S(root), S(origin), N, TypeSubstitution.empty)) + else root.views.get(origin).getOrElse: + // A previously unused path can require a view of an intermediate exporter. + // Memoize that read-only view in the consumer, never in the exporter. + destination.inheritedViews.getOrElseUpdate((root, origin), + new NewResolverState(owner, S(root), S(origin), N, TypeSubstitution.empty)) private val copies = mutable.Map.empty[Publisher.Data[?], Publisher.Data[?]] private val pending = mutable.Map.empty[Identity[Publisher[?]], Publisher.Data[?]] @@ -212,6 +217,18 @@ final class NewResolverState private ( new Cache(inherited.map(_.introShapes), identity) val symShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks]), CoreSymShape] = new Cache(inherited.map(_.symShapes), identity) + // Member `let`s have no TermDefinition body. Their initializers and subsequent + // assignments feed this host, just as assignments feed a local variable. + // Keep the host in resolver state so imported observations use the existing + // consumer-local publisher machinery rather than mutating the source symbol. + private val memberVariables: Cache[TermSymbol, ShapeHost] = + new Cache(inherited.map(_.memberVariables), identity) + def memberVariable(symbol: TermSymbol): ShapeHost = + root.memberVariables.getOrElseUpdate(symbol, + symbol.getState.newResolverState.memberVariables.get(symbol).getOrElse( + new Host[ShapeEvent]: + def showDbg(using DebugPrinter): Str = s"assignments to ${symbol.nme}" + )) val declaredSymShapes: Cache[(BlockMemberSymbol, FlowSymbol, Ls[Marks], Map[VarSymbol, DeclaredType], Bool), DeclaredSymShape] = new Cache(inherited.map(_.declaredSymShapes), identity) val selfShapes: Cache[InnerSymbol, BaseShape] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala index aec784956f..ac9654ccf5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/PatternShape.scala @@ -28,10 +28,11 @@ sealed abstract class PatternShape: // TODO: not a case class...? case class CtorPatternShape( - cls: ClassLikeDef, + cls: ClassDef | ModuleOrObjectDef, fs: Ls[BlockMemberSymbol -> Pattern], src: Pattern.Constructor, resSym: FlowSymbol, + marks: Ls[Marks], ) extends PatternShape: def showDbg(using DebugPrinter): Str = src.showDbg diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index 8d91b65b15..b18658e602 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -1062,6 +1062,11 @@ class Resolver(tl: TraceLogger) // * Traverse through the sub-terms. t match + // Captures retain lexical scopes for consumers using new resolution; + // they do not change the type interpreted by legacy resolution. + case Term.Capture(base, _) => + traverseSign(base, expect, inAppPrefix) + break() case Term.Ref(_) => case Term.Lit(_) => case Term.Tup(_) => t.subTerms.foreach(traverse(_, expect = NonModule(N))) @@ -1153,6 +1158,7 @@ class Resolver(tl: TraceLogger) Type.Error t match + case Term.Capture(base, _) => resolveSign(base, expect) // If the term is a type application, e.g., T[A, ...], resolve the // type constructor and arguments respectively. case Term.TyApp(con, args) => resolveSign(con, expect = expect) match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 2ce084bf97..f1653809df 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -508,6 +508,7 @@ enum Term extends Statement, AutoLocated, ShapePublisher: * elaboration. */ lazy val symbol: Opt[Symbol] = this match + case Capture(base, _) => base.symbol case res: Resolved => S(res.sym) case Ref(sym) => S(sym) case sel: Sel => sel.sym diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 0790bf3490..c675d35a6b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -39,9 +39,9 @@ enum TypeShape: // An omitted argument owns one source inference node. It is not a quantified // binder and is never instantiated at a call site; marks distinguish its flows. case Hole(host: Publisher.Data[TermShape]) - // Synthesized generic arguments can retain inferred value shapes, for example - // the element union of the Array supertype of a tuple. Written annotations - // never introduce this case by inspecting their implementation. + // Synthesized generic arguments retain inferred value shapes, for example + // the element union of the Array supertype of a tuple. Singleton annotations + // also retain their literal shape; no implementation is inspected. case Inferred(value: TermShape) case Captured(base: TypeResolution, thru: AnyDefinitionSymbol) case Unit diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala index 190c29b243..e24d6fdd38 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -264,6 +264,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): def typeType(t: Term): Type = trace[Type](s"Typing consumer: ${t.showDbg}", post = res => s": ${res.showDbg}"): t match + case Capture(base, _) => typeType(base) case Ref(sym: VarSymbol) => Type.Ref(sym, Nil) // unparameterized type variable case Ref(cls: ClassSymbol) => Type.Ref(cls, Nil) case Ref(ts: BlockMemberSymbol) => diff --git a/hkmc2/shared/src/test/mlscript-compile/Block.mls b/hkmc2/shared/src/test/mlscript-compile/Block.mls index 2671d1faaf..a088650eee 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Block.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Block.mls @@ -123,7 +123,7 @@ fun showArg(arg: Arg) = showPath(arg.value) fun showArgs(args: Array[Arg]) = - args.map((value, ...) => showArg(value)).join(", ") + args.map(pass1(showArg)).join(", ") fun showResult(r: Result): Str = if r is @@ -150,7 +150,7 @@ fun showArm(a: Arm) = showCase(a.cse) + " then" + (if a.body is Return then " " else "\n ") + indent(showBlock(a.body)) fun showParams(p: ParamList) = - "(" + p.map((value, ...) => showSymbol(value)).join(", ") + ")" + "(" + p.map(pass1(showSymbol)).join(", ") + ")" fun showParamsOpt(p) = if p is @@ -158,7 +158,7 @@ fun showParamsOpt(p) = None then "" fun showParamList(ps: Array[ParamList]) = - ps.map((value, ...) => showParams(value)).join("") + ps.map(pass1(showParams)).join("") fun showDefn(d: Defn): Str = if d is @@ -167,7 +167,7 @@ fun showDefn(d: Defn): Str = (if body is (Return | End) then " " else "\n ") + indent(showBlock(body)) ClsLikeDefn(sym, methods, _) then "class " + showSymbol(sym) + showParamsOpt(sym.paramsOpt) - + indent((if methods is [] then "" else " with\n") + methods.map((value, ...) => showDefn(value)).join("\n")) + + indent((if methods is [] then "" else " with\n") + methods.map(pass1(showDefn)).join("\n")) // TODO: used to represent assignments in constructors, which still need some adjustments ValDefn(owner, sym, rhs) then "val " + showSymbol(sym) + " = " + showPath(rhs) @@ -184,12 +184,12 @@ fun showBlock(b) = showResult(res) Match(scrut, arms, dflt, rest) then "if " + showPath(scrut) + " is" - + indent("\n" + arms.map((value, ...) => showArm(value)).join("\n")) + + indent("\n" + arms.map(pass1(showArm)).join("\n")) + if dflt is Some(db) then indent("\nelse" + indent((if db is Return then " " else "\n") + showBlock(db))) else "" + showRestBlock(rest) Scoped(symbols, rest) then // initialize symbols - "let {" + symbols.map((value, ...) => showSymbol(value)).join(", ") + "}" + showRestBlock(rest) + "let {" + symbols.map(pass1(showSymbol)).join(", ") + "}" + showRestBlock(rest) End() then "()" _ then "" + b diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls index a619c4be91..4f9e4eb5f9 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls @@ -10,7 +10,7 @@ fun normIdx(i, len) = else i fun enforceArray = case - (LazyArr | Array | String | Str | TypedArray) as a then (a as Indexable) + (LazyArr | Array | String | Str | TypedArray) as a then a as Indexable ow then throw Error("Expected an Array, got: " + String(ow)) fun concatHelper(a: Array[Any], l: Int, ...args: Array[Indexable]) = @@ -48,7 +48,7 @@ fun sliceHelper(beg: Int, fin: Int, arr: Indexable) = if arr is View(itr, start, end) then View of itr, start + beg, end + (len - fin) - Splice(bits, l) as s then View of (s as Splice).materialize().slice(beg, fin), 0, 0 + Splice(bits, l) as s then View of s.materialize().slice(beg, fin), 0, 0 else View of enforceArray(arr), beg, (arr.length - fin) // Marker base class for View and Splice @@ -71,7 +71,7 @@ class View(val underlying: Indexable, val start: Int, val end: Int) extends Lazy Result.Next of next else Result.Done - fun materialize() = underlying.slice(start, underlying.length - end) as Indexable + fun materialize(): Indexable = underlying.slice(start, underlying.length - end) fun at(i) = if i < -length || i >= length do @@ -122,7 +122,7 @@ class Splice(val bits: Array[Any], val len: Int) extends LazyArr with let vals = e.1 if vals is Splice as splice do - set vals = (splice as Splice).bits + set vals = splice.bits while (idx < vals.length and (vals.at(idx) is SpliceMarker) is false) do @@ -138,7 +138,7 @@ class Splice(val bits: Array[Any], val len: Int) extends LazyArr with fun materialize(): Array[Any] = if materialized === null do set materialized = reify - materialized as Array[Any] + materialized fun at(i) = materialize().at(i) fun slice(beg, fin) = sliceHelper(beg, fin, this) diff --git a/hkmc2/shared/src/test/mlscript-compile/LegacyGenericLibrary.mls b/hkmc2/shared/src/test/mlscript-compile/LegacyGenericLibrary.mls new file mode 100644 index 0000000000..0d3cce85ec --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/LegacyGenericLibrary.mls @@ -0,0 +1,5 @@ +// Kept in legacy mode to exercise signatures imported by new resolution. +module LegacyGenericLibrary with + class Cell[A](val value: A) with + fun get: A = value + fun getAt(index: Int): A = value diff --git a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls index a5c869c088..4c9977fee7 100644 --- a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls +++ b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls @@ -14,12 +14,13 @@ class MutMap[A, B](val underlying: Map[A, B]) with set this.(Symbol.iterator) = () => underlying.(Symbol.iterator)() -fun get(key)(m: MutMap) = +fun get[A, B](key: A)(m: MutMap[A, B]): Option[B] = + // Map.get also admits undefined for absent keys; has excludes that case. if m.underlying.has(key) - then Some of m.underlying.get(key) + then Some of (m.underlying.get(key) as B) else None -fun insert(key, value)(m: MutMap) = +fun insert[A, B](key: A, value: B)(m: MutMap[A, B]) = m.underlying.set(key, value) () @@ -48,6 +49,6 @@ fun toMap(entries: Array) = set i += 1 m -fun empty = new MutMap(new Map()) +fun empty[A, B]: MutMap[A, B] = new MutMap(new Map()) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index bf7746f614..2abe09dede 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -24,10 +24,8 @@ lambda2 = (undefined, function (Predef2, acc, x) { return acc + x }); lambda$1 = (undefined, function (Predef2, b) { - return (a, i) => { - let tmp; - tmp = runtime.safeCall(b.at(i)); - return Predef2.equals(a, tmp) + return (a, i, ..._) => { + return lambda1(Predef2, b, a, i, ..._) } }); lambda$ = (undefined, function (Predef2, a, b) { @@ -35,7 +33,7 @@ lambda$ = (undefined, function (Predef2, a, b) { return lambda(Predef2, a, b, field) } }); -lambda1 = (undefined, function (Predef2, b, a, i) { +lambda1 = (undefined, function (Predef2, b, a, i, ..._) { let tmp; tmp = runtime.safeCall(b.at(i)); return Predef2.equals(a, tmp) @@ -189,7 +187,7 @@ lambda = (undefined, function (Predef2, a, b, field) { } } static equals(a, b) { - let scrut, scrut1, scrut2, ac, scrut3, md, scrut4, scrut5, scrut6, scrut7, scrut8, scrut9, lambda$here, lambda$here1; + let scrut, scrut1, scrut2, ac, scrut3, md, scrut4, scrut5, scrut6, scrut7, scrut8, scrut9, lambda$here; scrut = a === b; if (scrut === true) { return true @@ -198,8 +196,7 @@ lambda = (undefined, function (Predef2, a, b, field) { if (b instanceof globalThis.Array) { scrut1 = a.length === b.length; if (scrut1 === true) { - lambda$here = lambda$1(Predef, b); - return runtime.safeCall(a.every(lambda$here)) + return runtime.safeCall(a.every(lambda$1(Predef, b))) } } } @@ -211,16 +208,16 @@ lambda = (undefined, function (Predef2, a, b, field) { if (scrut8 === true) { scrut7 = b !== null; if (scrut7 === true) { - ac = a.constructor; + ac = a["constructor"]; scrut3 = ac !== undefined; if (scrut3 === true) { - scrut6 = ac === b.constructor; + scrut6 = ac === b["constructor"]; if (scrut6 === true) { md = ac[Predef.Symbols.definitionMetadata]; scrut4 = md !== undefined; if (scrut4 === true) { - lambda$here1 = lambda$(Predef, a, b); - scrut5 = runtime.safeCall(md[2].every(lambda$here1)); + lambda$here = lambda$(Predef, a, b); + scrut5 = runtime.safeCall(md[2].every(lambda$here)); if (scrut5 === true) { return true } diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index b02ae78268..c4f109f7dd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) import "./RuntimeJS.mjs" @@ -23,31 +24,33 @@ fun id(x) = x fun maybe = hide(true) -fun (@) apply(f, ...args) = f(...args) +// Arbitrary variadic forwarding has no fixed callback domain. Keep this +// reflective calling convention explicit; the fixed-arity helpers use arrows. +fun (@) apply(f: dyn, ...args) = f(...args) -fun (|>) pipeInto(x, f) = f(x) -fun (<|) pipeFrom(f, x) = f(x) +fun (|>) pipeInto[A, B](x: A, f: A -> B): B = f(x) +fun (<|) pipeFrom[A, B](f: A -> B, x: A): B = f(x) // Same as above but with high precedence -fun (.>) pipeIntoHi(x, f) = f(x) -fun (<.) pipeFromHi(f, x) = f(x) +fun (.>) pipeIntoHi[A, B](x: A, f: A -> B): B = f(x) +fun (<.) pipeFromHi[A, B](f: A -> B, x: A): B = f(x) -fun (!>) tap(x, f) = f(x); x -fun () tap[A, B](x: A, f: A -> B): A = f(x); x +fun ( B, x: A): A = f(x); x fun (|!) alsoDo(x, eff) = x -fun (>>) andThen(f, g)(x) = g(f(x)) -fun (<<) compose(f, g)(x) = f(g(x)) +fun (>>) andThen[A, B, C](f: A -> B, g: B -> C)(x: A): C = g(f(x)) +fun (<<) compose[A, B, C](f: B -> C, g: A -> B)(x: A): C = f(g(x)) // x \foo(a, b, c) == foo(x, a, b, c) -fun (\) passTo(receiver, f)(...args) = f(receiver, ...args) +fun (\) passTo(receiver, f: dyn)(...args) = f(receiver, ...args) // Same as above but with low precedence -fun (|.) passToLo(receiver, f)(...args) = f(receiver, ...args) +fun (|.) passToLo(receiver, f: dyn)(...args) = f(receiver, ...args) // x |>. foo(a, b, c) == foo.call(x, a, b, c) -fun (|>.) call(receiver, f)(...args) = f.call(receiver, ...args) +fun (|>.) call(receiver, f: dyn)(...args) = f.call(receiver, ...args) object Symbols with @@ -70,17 +73,17 @@ type (>:>) Sup[A, B] = Sub[B, A] // * - arrays of values that are pairwise recursively equal; // * - instances of the same parameterized MLscript class when all fields are public and recursively equal. // TODO: compare lazy arrays and other sequences and collections -fun (==) equals(a, b) = if +fun (==) equals(a: Any, b: Any): Bool = if a === b then true - a is Array and b is Array and a.length === b.length then a.every((a, i) => a == b.at(i)) + a is Array as a and b is Array as b and a.length === b.length then a.every((a, i, ...) => a == b.at(i)) else a !== undefined and a !== null and b !== undefined and b !== null and - let ac = a.constructor - ac !== undefined and ac === b.constructor and - let md = ac.(Symbols.definitionMetadata) + let ac = a!constructor + ac !== undefined and ac === b!constructor and + let md = ac!(Symbols.definitionMetadata) md !== undefined and - md.2.every(field => field !== null and a.(field) == b.(field)) + md.2.every(field => field !== null and a!(field) == b!(field)) -fun (!=) nequals(a, b) = not a == b +fun (!=) nequals(a: Any, b: Any): Bool = not a == b fun pass1 = Rendering.pass1 @@ -113,7 +116,7 @@ fun mkSet(...xs) = new Set(xs) val foldl = fold // fun foldr(f)(...rest, init) = // TODO allow this syntax -fun foldr(f)(first, ...rest) = +fun foldr[A](f: (A, A) -> A)(first: A, ...rest: Array[A]): A = let len = rest.length if len === 0 then first else... let diff --git a/hkmc2/shared/src/test/mlscript-compile/Shape.mls b/hkmc2/shared/src/test/mlscript-compile/Shape.mls index 57936459df..791c75a702 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Shape.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Shape.mls @@ -1,6 +1,9 @@ #lang(0.3.x) import "./Block.mls" import "./Option.mls" +import "./Predef.mls" + +open Predef { pass1 } open Block { Literal, ClassSymbol, ConcreteClassSymbol, showSymbol, isPrimitiveType, isPrimitiveTypeOf } open Option @@ -20,13 +23,13 @@ fun show(s: Shape) = if s is Dyn then "Dyn" Lit(lit) then "Lit(" + Block.showLiteral(lit) + ")" - Arr(shapes) then "Arr(" + shapes.map((s, ...) => show(s)).join(", ") + ")" - Class(sym, params) then "Class(" + showSymbol(sym) + ", [" + params.map((s, ...) => show(s)).join(", ") + "])" + Arr(shapes) then "Arr(" + shapes.map(pass1(show)).join(", ") + ")" + Class(sym, params) then "Class(" + showSymbol(sym) + ", [" + params.map(pass1(show)).join(", ") + "])" fun sel(s1: Shape, s2: Shape): Array[Shape] = if [s1, s2] is [Class(ConcreteClassSymbol(_, _, Some(args), _), params), Lit(n)] and n is Str as n then - let i = args.findIndex((arg, ...) => arg.name == n) + let i = args.findIndex(pass1((arg: Block.Symbol) => arg.name == n)) if i >= 0 then [params.(i)] else [] [Dyn, Lit(n)] and n is Str then [Dyn()] @@ -44,8 +47,8 @@ fun static(s: Shape) = if s is Dyn then false Lit(l) then not (l is Str and isPrimitiveType(Block.Symbol(l))) // redundant bracket? - Class(_, params) then params.every((s, ...) => static(s)) - Arr(shapes) then shapes.every((s, ...) => static(s)) + Class(_, params) then params.every(pass1(static)) + Arr(shapes) then shapes.every(pass1(static)) open Block { Case } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls index 5048831b10..9267e1706f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls @@ -51,24 +51,24 @@ data class Report(fileName) with fun init() = wln of "" - wln of "|" ~ "Year" ~ "|" ~ lines.map((x: Line, ...) => x.name).join("|") ~ "|" - wln of "|" ~ "---" ~ "|" ~ lines.map((x, ...) => "--:").join("|") ~ "|" + wln of "|" ~ "Year" ~ "|" ~ lines.map(pass1((x: Line) => x.name)).join("|") ~ "|" + wln of "|" ~ "---" ~ "|" ~ lines.map(pass1(x => "--:")).join("|") ~ "|" fun snapShot(label) = - wln of "|" ~ String(label) ~ "|" ~ lines.map((x: Line, ...) => display(x.balance)).join("|") ~ "|" + wln of "|" ~ String(label) ~ "|" ~ lines.map(pass1((x: Line) => display(x.balance))).join("|") ~ "|" fun wrapUp() = wln of "" - warnings.forEach of (x, ...) => (wln(x), wln("")) + warnings.forEach of pass1(x => (wln(x), wln(""))) wln of "### Remaining Available Funds" wln of "" wln of "|" ~ "Summary" ~ "| |" wln of "|" ~ "---" ~ "|--:|" wln of "|" ~ "Matchable" ~ "|" ~ display( - lines.filter((x: Line, ...) => x.isMatchable).map((x: Line, ...) => x.balance).reduce((a, b, ...) => a +. b, 0) + lines.filter(pass1((x: Line) => x.isMatchable)).map(pass1((x: Line) => x.balance)).reduce(pass2((a, b) => a +. b), 0) ) ~ "|" wln of "|" ~ "Non-matchable" ~ "|" ~ display( - lines.filter((x: Line, ...) => not x.isMatchable).map((x: Line, ...) => x.balance).reduce((a, b, ...) => a +. b, 0) + lines.filter(pass1((x: Line) => not x.isMatchable)).map(pass1((x: Line) => x.balance)).reduce(pass2((a, b) => a +. b), 0) ) ~ "|" fun process(filename: Str, k: Report -> ()) = diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls index 762a0410c3..d64efb20fb 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls @@ -32,10 +32,8 @@ fun toArrays(strData: Str) = // Keep looping over the regular expression matches until we can no longer find a match. while let arrMatches = objPattern.exec(strData) - arrMatches !== null do + arrMatches is Array as matches do - // The loop condition guarantees a successful regular-expression match. - val matches = arrMatches as Array[Str] // Get the delimiter that was found. let strMatchedDelimiter = matches.1 diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls index 707b4f20cd..01089ae43e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls @@ -6,7 +6,7 @@ import "../../Predef.mls" open Predef module Examples with... -val examples = MutMap.empty +val examples: MutMap.MutMap[Str, {name: Str, source: Str}] = MutMap.empty examples |> MutMap.insert of "hanoi" diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls index 32a7693598..5092cd9150 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls @@ -38,11 +38,11 @@ module Main with ... type Example = {name: Str, source: Str} -val query: Str -> dyn = document.querySelector.bind(document) -val editor = query("#editor") -val selector = query("select#example") -val parseButton = query("button#parse") -val outputPanel = query("#output") +let query = document.querySelector.bind(document) +let editor = query("#editor") +let selector = query("select#example") +let parseButton = query("button#parse") +let outputPanel = query("#output") examples Iter.each of (entry: [Str, Example]) => let key = entry.0 @@ -68,7 +68,7 @@ editor.addEventListener of "keydown", (event: dyn) => selector.addEventListener of "change", (event: dyn) => if (examples |> MutMap.get(selector.value)) is - Some(example) do set editor.value = (example as Example).source + Some(example) do set editor.value = example.source None do throw new Error of "Example \"" + selector.value + "\" not found" parseButton.addEventListener of "click", (event: dyn) => @@ -88,7 +88,7 @@ parseButton.addEventListener of "click", (event: dyn) => errorDisplay.setError of error outputPanel.appendChild of errorDisplay -val indentRegex = new RegExp("""^(\s*)""") +let indentRegex = new RegExp("""^(\s*)""") type IndentedNode = {text: Str, children: Array[IndentedNode]} @@ -111,7 +111,7 @@ fun parseIndentedText(text: Str) = // * The content in custom elements is placed inside the ShadowDOM, which is // * scoped. Therefore, styles placed directly in HTML won't be effective on // * elements inside custom elements. Placing CSS here is more convenient. -val errorDisplayStyle = """ +let errorDisplayStyle = """ .error-container { background-color: #fdd; padding: 0.375rem 0.75rem 0.5rem; @@ -164,7 +164,7 @@ customElements.define of "collapsible-tree", CollapsibleTree class ErrorDisplay extends HTMLElement with this.attachShadow(mode: "open") - mut val _error: Option[Error] = None + let _error = None fun connectedCallback() = this.render() diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls index d2bc4acfd2..3cf102bfcf 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls @@ -34,7 +34,7 @@ fun makePrecMap(startPrec: Int, ...ops: Array[Str]) = m |> MutMap.insert of op, i + startPrec m -val all = MutMap.empty +val all: MutMap.MutMap[Str, Keyword] = MutMap.empty fun keyword(name: Str, leftPrec: Some[Int] | None, rightPrec: Some[Int] | None) = let result = Keyword(name, leftPrec, rightPrec) all |> MutMap.insert(name, result) diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls index 9570e97034..81001ea40b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls @@ -216,7 +216,7 @@ data class ParseRule[A](name: Str, mut val choices: Stack[Choice[A]]) with MutMap.toMap() // Collect all keyword choices in this rule. - fun keywordChoices = _keywordChoices.get() + fun keywordChoices: MutMap.MutMap[Str, ParseRule[A]] = _keywordChoices.get() let _refChoice = lazy of () => choices Iter.fromStack() diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls index 2b2ae3496c..6c591c487a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Parser.mls @@ -69,9 +69,9 @@ fun parse(tokens: Stack[Token.Token]) = token :: _ then Tree.error("expect a type variable but found " + token) Nil then Tree.error("expect a type variable but found the end of input") // * other rule-based kinds - syntaxKinds |> MutMap.get(kind) is Some(ParseRule.ParseRule as rule) then + syntaxKinds |> MutMap.get(kind) is Some(rule) then let tree = parseRule(prec, rule) - if (rule as ParseRule.ParseRule[Tree]).refChoice is Some(Ref(kind', process, None, None, rest)) and kind == kind' do + if rule.refChoice is Some(Ref(kind', process, None, None, rest)) and kind == kind' do let shouldParse = true while shouldParse do let tree' = parseRule(prec, rest) @@ -90,14 +90,14 @@ fun parse(tokens: Stack[Token.Token]) = Token.Identifier(name, _) :: _ and do tracer.print of "found an identifier \"" + name + "\"", source.line Keywords.all |> MutMap.get(name) is Some(keyword) and - do tracer.print of (keyword as Keywords.Keyword).toString(), source.line + do tracer.print of keyword.toString(), source.line do tracer.print of "keyword choices: ", rule.keywordChoices Iter.mapping of case [k, v] then "`" + k + "`" Iter.joined(", ") rule.keywordChoices |> MutMap.get(name) is - Some(ParseRule.ParseRule as rest) then + Some(rest) then tracer.print of "found a rule starting with `" + name + "`", source.line - tracer.print of "the rest of the rule: " + (rest as ParseRule.ParseRule[Tree]).display, source.line + tracer.print of "the rest of the rule: " + rest.display, source.line consume parseRule(0, rest) do tracer.print of "\"" + name + "\" is not a keyword", source.line @@ -145,10 +145,10 @@ fun parse(tokens: Stack[Token.Token]) = Token.Identifier(name, symbolic) :: _ and Keywords.all |> MutMap.get(name) is // * the keyword case Some(keyword) and options.rule.keywordChoices |> MutMap.get(name) is - Some(ParseRule.ParseRule as rule) and - (keyword as Keywords.Keyword).leftPrecOrMin > prec then + Some(rule) and + keyword.leftPrecOrMin > prec then consume - parseRule((keyword as Keywords.Keyword).rightPrecOrMax, rule) exprCont(prec, options) + parseRule(keyword.rightPrecOrMax, rule) exprCont(prec, options) else tracer.print of "the left precedence of \"" + name + "\" is less", source.line Tree.empty @@ -184,16 +184,16 @@ fun parse(tokens: Stack[Token.Token]) = do tracer.print of "check keyword " + TokenHelpers.preview(tokens), source.line Token.Identifier(name, _) :: _ and Keywords.all |> MutMap.get(name) is Some(keyword) and do tracer.print of "found a keyword: " + name, source.line - infix is Some(infix) and infix.rule.keywordChoices |> MutMap.get(name) is Some(ParseRule.ParseRule as rule) and + infix is Some(infix) and infix.rule.keywordChoices |> MutMap.get(name) is Some(rule) and do tracer.print of "keyword `" + name + "` is found in infix rules", source.line - (keyword as Keywords.Keyword).leftPrecOrMin > prec and (rule as ParseRule.ParseRule[Tree]).refChoice is + keyword.leftPrecOrMin > prec and rule.refChoice is Some(Ref(kind, process, outerPrec, innerPrec, rest)) and do tracer.print of "try to parse kind \"" + kind + "\" at " + TokenHelpers.preview(tokens), source.line let outerPrec' = outerPrec Option.getOrElse(Keywords.maxOperatorPrec) let innerPrec' = innerPrec Option.getOrElse(outerPrec') outerPrec' > prec then consume - let rhs = parseKind(kind, (keyword as Keywords.Keyword).rightPrecOrMin) + let rhs = parseKind(kind, keyword.rightPrecOrMin) let restRes = parseRule(innerPrec', rest) infix.process(acc, process(rhs, restRes)) exprCont(prec, options) None then acc @@ -260,7 +260,7 @@ fun parse(tokens: Stack[Token.Token]) = Token.Identifier(name, _) :: _ and Keywords.all |> MutMap.get(name) is Some(keyword) and // First, try if the keyword is the prefix of the term. - termRule.keywordChoices |> MutMap.get(name) is Some(ParseRule.ParseRule as rule) then + termRule.keywordChoices |> MutMap.get(name) is Some(rule) then let tree = expr(0, termOptions) if tree is Tree.LetIn(bindings, Tree.Empty) then // If the body is empty, then this is a let-definition. @@ -268,7 +268,7 @@ fun parse(tokens: Stack[Token.Token]) = else modCont of tree :: acc // Then try to parse according to the rules of the definition. - declRule.keywordChoices |> MutMap.get(name) is Some(ParseRule.ParseRule as rule) then + declRule.keywordChoices |> MutMap.get(name) is Some(rule) then consume parseRule(0, rule) handleDirective(acc) _ :: _ then modCont of expr(0, termOptions) :: acc diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls index 06476e02f7..86ac2b4aa6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Rules.mls @@ -22,7 +22,7 @@ type Opt[A] = Some[A] | None module Rules with ... -val syntaxKinds = MutMap.empty +val syntaxKinds: MutMap.MutMap[Str, ParseRule.ParseRule] = MutMap.empty val extendedKinds = new Set() diff --git a/hkmc2/shared/src/test/mlscript/basics/MultiParamListClasses.mls b/hkmc2/shared/src/test/mlscript/basics/MultiParamListClasses.mls index fa8985b063..2d2384972e 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MultiParamListClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MultiParamListClasses.mls @@ -85,16 +85,24 @@ Foo(1, 2) :sjs let f = Foo // should compile to `(x, y) => Foo(x, y)(use[Int])` //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f, f1; -//│ f1 = function f(x, y) { -//│ let tmp2; +//│ let f2, f3; +//│ f3 = function f(x1, y1) { +//│ let x2, y2, z2, tmp2, lambda4, f4; +//│ x2 = x1; +//│ y2 = y1; +//│ z2 = instance$Ident$_Int$_; //│ tmp2 = new Foo03(); -//│ tmp2.x = x; -//│ tmp2.y = y; -//│ tmp2.z = instance$Ident$_Int$_; +//│ lambda4 = (undefined, function (res) { +//│ res.x = x2; +//│ res.y = y2; +//│ res.z = z2; +//│ return runtime.Unit +//│ }); +//│ f4 = lambda4; +//│ runtime.safeCall(f4(tmp2)); //│ return tmp2 //│ }; -//│ f = f1; +//│ f2 = f3; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ f = fun f diff --git a/hkmc2/shared/src/test/mlscript/basics/MutVal.mls b/hkmc2/shared/src/test/mlscript/basics/MutVal.mls index e7e381deea..37b7458ef7 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MutVal.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MutVal.mls @@ -36,9 +36,9 @@ O.gy class Foo(x, val y, mut val z) //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ define Foo⁰ as class Foo² { -//│ val y¹; +//│ val y⁰; //│ val z⁰; -//│ constructor Foo¹(x, y, z) { define y¹ as val y² = y; define z⁰ as val z¹ = z; end } +//│ constructor Foo¹(x, y, z) { define y⁰ as val y¹ = y; define z⁰ as val z¹ = z; end } //│ }; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -49,12 +49,12 @@ class Foo(x, val y, mut val z) with //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ define Foo³ as class Foo⁵ { //│ private val x⁰; -//│ val y³; +//│ val y²; //│ val z²; //│ constructor Foo⁴(x, y, z) { //│ @private //│ define x¹ as val x⁰ = x; -//│ define y³ as val y⁴ = y; +//│ define y² as val y³ = y; //│ define z² as val z³ = z; //│ do Predef⁰.print⁰(Foo⁵.this.x⁰); //│ end diff --git a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls index 39b592aca6..1f7e436ba3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls @@ -163,13 +163,13 @@ module A with fun g(x) = 3 A.x //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define A¹ as class A² -//│ module A⁰ { +//│ define A⁰ as class A¹ +//│ module A² { //│ private val y⁰; //│ val x⁷; //│ constructor { //│ define x⁷ as val x⁸ = 2; -//│ set A⁰.this.y⁰ = 3; +//│ set A².this.y⁰ = 3; //│ define C³ as class C⁵ { //│ private val x⁹; //│ constructor C⁴(x) { @@ -185,7 +185,7 @@ A.x //│ } //│ method g⁰ = fun g¹(x) { return 3 } //│ }; -//│ return A⁰.x⁸ +//│ return A².x⁸ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls index fefa8bca1b..66020f8c1a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls @@ -90,12 +90,12 @@ case 0 then true _ then false //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let lambda5; -//│ lambda5 = (undefined, function (caseScrut) { +//│ let lambda10; +//│ lambda10 = (undefined, function (caseScrut) { //│ if (caseScrut === 0) { return true } //│ return false; //│ }); -//│ return lambda5 +//│ return lambda10 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun @@ -107,8 +107,8 @@ val isDefined = case Some then true None then false //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let isDefined, lambda6; -//│ lambda6 = (undefined, function (caseScrut) { +//│ let isDefined, lambda11; +//│ lambda11 = (undefined, function (caseScrut) { //│ if (caseScrut instanceof Some1.class) { //│ return true //│ } else if (caseScrut instanceof None1.class) { @@ -116,7 +116,7 @@ val isDefined = case //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ }); -//│ isDefined = lambda6; +//│ isDefined = lambda11; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ isDefined = fun diff --git a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls index 1550f01f60..6599b1c2dc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls @@ -227,21 +227,21 @@ _ - _ of 1, 2 :sjs 1 \ (_ - 2) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let lambda8; -//│ lambda8 = (undefined, function (_0) { return _0 - 2 }); -//│ return Predef.passTo(1, lambda8) +//│ let lambda32; +//│ lambda32 = (undefined, function (_0) { return _0 - 2 }); +//│ return Predef.passTo(1, lambda32) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun :sjs 1 \ (_ - 2)() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let lambda9, args; -//│ lambda9 = (undefined, function (_0) { +//│ let lambda33, args; +//│ lambda33 = (undefined, function (_0) { //│ return _0 - 2 //│ }); //│ args = []; -//│ return runtime.safeCall(lambda9(1, ...args)) +//│ return runtime.safeCall(lambda33(1, ...args)) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = -1 @@ -260,7 +260,7 @@ let f = if _ then 1 else 0 //│ ║ l.255: let f = if _ then 1 else 0 //│ ╙── ^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f13; f13 = 1; +//│ let f23; f23 = 1; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ f = 1 @@ -270,8 +270,8 @@ fun f(x) = 0 then 1 _ then 2 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f14; -//│ f14 = function f(x3) { +//│ let f24; +//│ f24 = function f(x3) { //│ let scrut; //│ scrut = Predef.equals(x3, 0); //│ if (scrut === true) { return 1 } diff --git a/hkmc2/shared/src/test/mlscript/codegen/SetStmt.mls b/hkmc2/shared/src/test/mlscript/codegen/SetStmt.mls index 7d39e7d169..9c616ff6b7 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SetStmt.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SetStmt.mls @@ -20,9 +20,22 @@ g set g.0 |>= Math.sign +// Without parentheses, the placeholder abstracts the entire assignment; +// its body would pass a number to the pipe operator as a function. +:e set g.2 |>= Math.pow(_, 5) +//│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Num' at an unrelated type 'Function' +//│ ║ l.26: set g.2 |>= Math.pow(_, 5) +//│ ╙── ^^^^^^^^^^^^^^ //│ = fun g //│ = [1, 1, 2] + +// Parenthesizing the callback scopes the placeholder to Math.pow. +set g.2 |>= (Math.pow(_, 5)) + +:expect [1, 1, 32] +g +//│ = [1, 1, 32] diff --git a/hkmc2/shared/src/test/mlscript/ctx/NestedUsingInstances.mls b/hkmc2/shared/src/test/mlscript/ctx/NestedUsingInstances.mls index 3100172059..0dbfd62560 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/NestedUsingInstances.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/NestedUsingInstances.mls @@ -30,9 +30,11 @@ module Ex with //│ k = Ins //│ sym = member:instance$Ident(Int)⁰ //│ tsym = term:instance$Ident(Int)¹ -//│ sign = S of Resolved{sym=class:Int⁰}: -//│ t = Ref{sym=member:Int¹} of member:Int¹ -//│ sym = class:Int⁰ +//│ sign = S of Capture: +//│ base = Resolved{sym=class:Int⁰}: +//│ t = Ref{sym=member:Int¹} of member:Int¹ +//│ sym = class:Int⁰ +//│ thru = term:instance$Ident(Int)¹ //│ body = S of Lit of IntLit of 1 //│ flags = (result annotation) //│ modulefulness = Modulefulness of N diff --git a/hkmc2/shared/src/test/mlscript/ctx/Summon.mls b/hkmc2/shared/src/test/mlscript/ctx/Summon.mls index c992ee90aa..92b965272a 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/Summon.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/Summon.mls @@ -20,7 +20,7 @@ use[Num] //│ ║ ^^^^^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ in ../../mlscript-compile/Predef.mls -//│ ║ l.133: fun use[T](using instance: T) = instance +//│ ║ l.136: fun use[T](using instance: T) = instance //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Num (type parameter T); Available: Int, Str //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -57,7 +57,7 @@ fun f(using Int) = use[Num] //│ ║ ^^^^^^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ in ../../mlscript-compile/Predef.mls -//│ ║ l.133: fun use[T](using instance: T) = instance +//│ ║ l.136: fun use[T](using instance: T) = instance //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╙── Missing instance: Expected: Num (type parameter T); Available: Int, Str @@ -87,7 +87,7 @@ use([Some[Int]].value) //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: //│ ║ in ../../mlscript-compile/Predef.mls -//│ ║ l.133: fun use[T](using instance: T) = instance +//│ ║ l.136: fun use[T](using instance: T) = instance //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╙── Illegal query for an unspecified type variable T. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls b/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls index dad862642c..642b7d4441 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls @@ -101,9 +101,11 @@ fun f(x: Foo) = x //│ params = Ls of //│ Param: //│ sym = x⁰ -//│ sign = S of Resolved{sym=class:Foo⁰}: -//│ t = Ref{sym=member:Foo¹} of member:Foo¹ -//│ sym = class:Foo⁰ +//│ sign = S of Capture: +//│ base = Resolved{sym=class:Foo⁰}: +//│ t = Ref{sym=member:Foo¹} of member:Foo¹ +//│ sym = class:Foo⁰ +//│ thru = term:f¹ //│ modulefulness = Modulefulness of N //│ body = S of Ref{sym=x⁰,typ=class:Foo⁰} of x⁰ //│ modulefulness = Modulefulness of N @@ -161,13 +163,17 @@ fun f(module m: M): module M = m //│ params = Ls of //│ Param: //│ sym = m² -//│ sign = S of Resolved{sym=module:M⁰,typ=module:M⁰}: -//│ t = Ref{sym=member:M¹} of member:M¹ -//│ sym = module:M⁰ +//│ sign = S of Capture: +//│ base = Resolved{sym=module:M⁰,typ=module:M⁰}: +//│ t = Ref{sym=member:M¹} of member:M¹ +//│ sym = module:M⁰ +//│ thru = term:f³ //│ modulefulness = Modulefulness of S of module:M⁰ -//│ sign = S of Resolved{sym=module:M⁰,typ=module:M⁰}: -//│ t = Ref{sym=member:M¹} of member:M¹ -//│ sym = module:M⁰ +//│ sign = S of Capture: +//│ base = Resolved{sym=module:M⁰,typ=module:M⁰}: +//│ t = Ref{sym=member:M¹} of member:M¹ +//│ sym = module:M⁰ +//│ thru = term:f³ //│ body = S of Ref{sym=m²,typ=module:M⁰} of m² //│ flags = (result annotation) //│ modulefulness = Modulefulness of S of module:M⁰ @@ -187,9 +193,11 @@ fun f(o: O) = o //│ params = Ls of //│ Param: //│ sym = o² -//│ sign = S of Resolved{sym=object:O⁰,typ=object:O⁰}: -//│ t = Ref{sym=member:O¹} of member:O¹ -//│ sym = object:O⁰ +//│ sign = S of Capture: +//│ base = Resolved{sym=object:O⁰,typ=object:O⁰}: +//│ t = Ref{sym=member:O¹} of member:O¹ +//│ sym = object:O⁰ +//│ thru = term:f⁵ //│ modulefulness = Modulefulness of N //│ body = S of Ref{sym=o²,typ=object:O⁰} of o² //│ modulefulness = Modulefulness of N diff --git a/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls b/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls index c1defe9ed9..01455eeeca 100644 --- a/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls +++ b/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls @@ -60,7 +60,7 @@ fun test(x) = x.m // :df fun test(x: Foo) = x.m //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ fun test²::test³(x²: Foo⁰{ ~> Foo⁰ }) ‹flow:test¹› = x².mˀˀˀ{ ~> x².m⁰ } +//│ fun test²::test³(x²: Foo⁰{ ~> Foo⁰ }^test³) ‹flow:test¹› = x².mˀˀˀ{ ~> x².m⁰ } //│ where //│ x² <~ type Foo //│ flow:test¹ <~ ((type Foo) -> flow:m⁰) diff --git a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls index e017b8826e..46093abd8a 100644 --- a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -88,7 +88,7 @@ g(d) //│ ║ l.8: fun a = new A //│ ╙── ^^^^^^^^^^^^^ //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— -//│ fun g⁰::g¹(x⁰: C⁰{ ~> C⁰ }) ‹flow:g⁰› = x⁰, +//│ fun g⁰::g¹(x⁰: C⁰{ ~> C⁰ }^g¹) ‹flow:g⁰› = x⁰, //│ let d⁰, //│ d⁰ = _?_.a{ ~> ‹error› }, //│ f⁰{ ~> f⁰ }(d⁰)‹app⁹›, diff --git a/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls index d50c681131..0614ac66e5 100644 --- a/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls +++ b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls @@ -15,7 +15,7 @@ module Foo with //│ val a⁰::a¹ ‹flow:a⁰› = 123 //│ }, //│ module Foo⁰::Foo² { -//│ fun foo⁰::foo¹(x⁰: Foo⁰{ ~> Foo⁰ }) ‹flow:foo⁰› = x⁰.aˀˀˀ{ ~> x⁰.a⁰ } +//│ fun foo⁰::foo¹(x⁰: Foo⁰{ ~> Foo⁰ }^foo¹) ‹flow:foo⁰› = x⁰.aˀˀˀ{ ~> x⁰.a⁰ } //│ } //│ where //│ x⁰ <~ type Foo @@ -199,10 +199,10 @@ module AA with //│ module AA⁰::AA¹ { //│ module BB⁰::BB¹ { //│ class CC⁰::CC¹ { -//│ val x⁴::x⁵: Int⁰{ ~> Int⁰ } ‹flow:x⁰› = 1 +//│ val x⁴::x⁵: Int⁰{ ~> Int⁰ }^CC¹^x⁵ ‹flow:x⁰› = 1 //│ }, //│ module CC⁰::CC² { -//│ fun getX⁰::getX¹(self⁰: ⟨BB¹.⟩CC⁰) ‹flow:getX⁰› = self⁰.xˀˀˀ{ ~> self⁰.x⁴ } +//│ fun getX⁰::getX¹(self⁰: ⟨BB¹.⟩CC⁰^getX¹) ‹flow:getX⁰› = self⁰.xˀˀˀ{ ~> self⁰.x⁴ } //│ } //│ } //│ } @@ -318,11 +318,11 @@ module CC with fun getX(self: CC) = self.x //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— //│ fun CC³ -//│ class CC⁴::CC⁵(val x⁶: Int⁰{ ~> Int⁰ }) { +//│ class CC⁴::CC⁵(val x⁶: Int⁰{ ~> Int⁰ }^CC⁵) { //│ val x⁷::x⁸ ‹flow:x¹› = x⁶ //│ }, //│ module CC⁴::CC⁶ { -//│ fun getX²::getX³(self¹: CC⁴{ ~> CC⁴ }) ‹flow:getX¹› = self¹.xˀˀˀ{ ~> self¹.x⁷ } +//│ fun getX²::getX³(self¹: CC⁴{ ~> CC⁴ }^getX³) ‹flow:getX¹› = self¹.xˀˀˀ{ ~> self¹.x⁷ } //│ } //│ where //│ x⁶ <~ type Int @@ -351,7 +351,7 @@ module CC with //│ //│ }, //│ module CC⁷::CC⁹ { -//│ fun oops⁰::oops¹(x⁹: CC⁷{ ~> CC⁷ }) ‹flow:oops⁰› = x⁹.CC⁷.oops⁰{ ~> CC⁷.oops⁰(x⁹)‹app¹⁵› } +//│ fun oops⁰::oops¹(x⁹: CC⁷{ ~> CC⁷ }^oops¹) ‹flow:oops⁰› = x⁹.CC⁷.oops⁰{ ~> CC⁷.oops⁰(x⁹)‹app¹⁵› } //│ } //│ where //│ x⁹ <~ type CC @@ -379,7 +379,7 @@ module CC with //│ val okay⁰::okay¹ ‹flow:okay⁰› = 123 //│ }, //│ module CC¹⁰::CC¹² { -//│ fun okay²::okay³(x¹⁰: CC¹⁰{ ~> CC¹⁰ }) ‹flow:okay¹› = x¹⁰.okayˀˀˀ{ ~> x¹⁰.okay⁰ } +//│ fun okay²::okay³(x¹⁰: CC¹⁰{ ~> CC¹⁰ }^okay³) ‹flow:okay¹› = x¹⁰.okayˀˀˀ{ ~> x¹⁰.okay⁰ } //│ } //│ where //│ x¹⁰ <~ type CC @@ -403,11 +403,11 @@ module Example with //│ —————————————————| Flowed |————————————————————————————————————————————————————————————————————————— //│ module Example⁰::Example¹ { //│ fun Test⁰ -//│ class Test¹::Test²(val field⁰: Int⁰{ ~> Int⁰ }) { +//│ class Test¹::Test²(val field⁰: Int⁰{ ~> Int⁰ }^Test²) { //│ val field¹::field² ‹flow:field⁰› = field⁰ //│ }, //│ module Test¹::Test³ { -//│ fun next⁰::next¹(self²: ⟨Example¹.⟩Test¹) ‹flow:next⁰› = ⟨Example¹.⟩Test¹(+(self².field¹, 1)‹app¹⁷›)‹app¹⁸› +//│ fun next⁰::next¹(self²: ⟨Example¹.⟩Test¹^next¹) ‹flow:next⁰› = ⟨Example¹.⟩Test¹(+(self².field¹, 1)‹app¹⁷›)‹app¹⁸› //│ } //│ } //│ where diff --git a/hkmc2/shared/src/test/mlscript/newres/Basics.mls b/hkmc2/shared/src/test/mlscript/newres/Basics.mls index 60c028167c..6db9d87e4f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Basics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Basics.mls @@ -90,7 +90,7 @@ Predef.idd(1) //│ ║ ^^^^^^^^^^ //│ ╟── Module 'Predef' does not contain member 'idd' //│ ║ in ../../mlscript-compile/Predef.mls -//│ ║ l.15: module Predef with ... +//│ ║ l.16: module Predef with ... //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls index 6c0c28c90f..10f47c02ab 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls @@ -213,7 +213,7 @@ MutMap.empty |> MutMap.updateWith("key") of (x => x.value) //│ ║ ^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ║ in ../../mlscript-compile/MutMap.mls -//│ ║ l.29: fun updateWith(key)(op: Function)(m: MutMap) = +//│ ║ l.30: fun updateWith(key)(op: Function)(m: MutMap) = //│ ║ ^^^^^^^^ //│ ╙── The 'Function' type does not specify parameter types. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Functions.mls b/hkmc2/shared/src/test/mlscript/newres/Functions.mls index 4c2be5f681..83d75245a8 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Functions.mls @@ -178,9 +178,17 @@ callid1(Predef) //│ | = MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) //│ | appShape isSaturated? true; head? (DefnShape(term definition 'id'),List(EntryMark(term:callid1,Some(callid1),ExitMark(term:Predef/id,Some(⋅id),NoMarks)))) //│ | lhs false List(((x⁰),List(↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ))) -//│ | appShape: td.body = Some(Ref(x)) +//│ | appShape: td.body = Some(SimpleRef(x)) //│ | listenTerm: trm = x⁰ //│ | listen: trm = x⁰ +//│ | .exit MarkedShape(UnknownValueShape(x⁰), ↘⟨term:Predef¹/id¹⟩%⟨exposed⁰⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | .exit MarkedShape(UnknownValueShape(x⁰), ↘⟨term:Predef¹/id¹⟩%⟨exposed⁰⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) +//│ | | | .exit MarkedShape(UnknownValueShape(x⁰), ↘⟨term:Predef¹/id¹⟩%⟨exposed⁰⟩ϵ) (ϵ) +//│ | | | = MarkedShape(UnknownValueShape(x⁰), ↘⟨term:Predef¹/id¹⟩%⟨exposed⁰⟩ϵ) +//│ | | | .exit MarkedShape (MarkedShape(UnknownValueShape(x⁰), ↘⟨term:Predef¹/id¹⟩%⟨exposed⁰⟩ϵ), term:Predef¹/id¹, ⋅id⁰) +//│ | | | = NoShape +//│ | | = NoShape +//│ | = NoShape //│ | .exit MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↘⟨term:callid1²⟩%⟨callid1¹⟩↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | .exit MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) //│ | | | .exit MarkedShape(IntroShape(1), ↘⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩↗⟨term:callid1²⟩%⟨callid1¹⟩ϵ) (ϵ) diff --git a/hkmc2/shared/src/test/mlscript/newres/LegacyGenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/LegacyGenericMethods.mls index eeefae93f3..31495f0c40 100644 --- a/hkmc2/shared/src/test/mlscript/newres/LegacyGenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/LegacyGenericMethods.mls @@ -4,9 +4,16 @@ import "../../mlscript-compile/FingerTreeList.mls" // Calling a generic method imported from a legacy fixture must preserve the // class's type argument when interpreting the method's annotated result. -// FIXME: The method result currently exits the method scope while its mark -// still denotes the enclosing class scope. This blocks LazyFingerTree. -:fixme :expect 42 FingerTreeList.mk(42).at(0) -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: class:FingerTree⁰ vs term:FingerTree⁰/at⁰ for InstanceShape(T⁰) +//│ = 42 + +import "../../mlscript-compile/LegacyGenericLibrary.mls" + +:expect [7, "other"] +class NumberBox(val number: Int) +class TextBox(val text: Str) +fun readNumber(cell: LegacyGenericLibrary.Cell[NumberBox]) = cell.get.number +fun readText(cell: LegacyGenericLibrary.Cell[TextBox]) = cell.getAt(0).text +[readNumber(LegacyGenericLibrary.Cell(NumberBox(7))), readText(LegacyGenericLibrary.Cell(TextBox("other")))] +//│ = [7, "other"] diff --git a/hkmc2/shared/src/test/mlscript/newres/MapOptionFlow.mls b/hkmc2/shared/src/test/mlscript/newres/MapOptionFlow.mls new file mode 100644 index 0000000000..a62d821be9 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/MapOptionFlow.mls @@ -0,0 +1,31 @@ +:. + +import "../../mlscript-compile/MutMap.mls" +import "../../mlscript-compile/Option.mls" +open Option + +// A fresh map retains its inserted values through the curried get API and +// the higher-order pipe helper. Some itself needs no nested class pattern. +:expect 42 +class Box(val value: Int) +val m = MutMap.empty +m |> MutMap.insert("x", Box(42)) +private fun read() = if m |> MutMap.get("x") is Some(b) then b.value else 0 +read() +//│ = 42 +//│ m = MutMap(Map(1) {"x" => Box(42)}) + +// An explicitly exposed map supplies the same element interface. +:expect [42, 42, 0] +class Box(val value: Int) +val m: MutMap.MutMap[Str, Box] = MutMap.empty +m |> MutMap.insert("x", Box(42)) +val piped = if m |> MutMap.get("x") is Some(b) then b.value else 0 +val direct = if MutMap.get("x")(m) is Some(b) then b.value else 0 +val absent = if m |> MutMap.get("absent") is Some(b) then b.value else 0 +[piped, direct, absent] +//│ = [42, 42, 0] +//│ absent = 0 +//│ direct = 42 +//│ m = MutMap(Map(1) {"x" => Box(42)}) +//│ piped = 42 diff --git a/hkmc2/shared/src/test/mlscript/newres/MemberVariables.mls b/hkmc2/shared/src/test/mlscript/newres/MemberVariables.mls new file mode 100644 index 0000000000..d739513c95 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/MemberVariables.mls @@ -0,0 +1,39 @@ +:. + +// Module let initializers feed reads in nested functions. +:expect [3, 5] +module TextCell with + let text = "abc" + fun setText(value: Str) = set text = value + fun length() = text.length +val first = TextCell.length() +TextCell.setText("hello") +[first, TextCell.length()] +//│ = [3, 5] +//│ first = 3 + +// Reading a let initialized from a parameter refers to the let's storage, +// not the parameter's symbol, and preserves each receiver's type argument. +:expect [7, "other"] +class Cell[A](value: A) with + let stored = value + fun get() = stored +class NumberBox(val number: Int) +class TextBox(val text: Str) +[Cell(NumberBox(7)).get().number, Cell(TextBox("other")).get().text] +//│ = [7, "other"] + +// All assignments contribute, including those inside exposed functions. +:e +module Changing with + let value = "before" + fun replace() = set value = 42 + fun length() = value.length +Changing.length() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.31: fun length() = value.length +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Integer literal does not contain member 'length' +//│ ║ l.30: fun replace() = set value = 42 +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls index 079f263862..005cfd9fbf 100644 --- a/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls +++ b/hkmc2/shared/src/test/mlscript/newres/NestedModules.mls @@ -84,7 +84,7 @@ m.id //│ ║ ^^^^ //│ ╟── target: function 'id' in module 'Predef' //│ ║ in ../../mlscript-compile/Predef.mls -//│ ║ l.18: fun id(x) = x +//│ ║ l.19: fun id(x) = x //│ ║ ^^ //│ ╟── target: function 'id' in module 'Bar' //│ ║ in NestedModules.mls:6 diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternRefinement.mls b/hkmc2/shared/src/test/mlscript/newres/PatternRefinement.mls new file mode 100644 index 0000000000..d28ea96d2b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/PatternRefinement.mls @@ -0,0 +1,49 @@ +:. + +import "../../mlscript-compile/Option.mls" +open Option + +// Some extraction retains the option's declared element interface. +:expect 42 +class Box(val value: Int) +fun read(x: Option[Box]) = if x is Some(b) then b.value else 0 +read(Some(Box(42))) +//│ = 42 + +// A class test refines a structural interface without a redundant cast. +:expect 42 +class Box(val value: Int) +fun read(x: {value: Int}) = if x is Box as b then b.value else 0 +read(Box(42)) +//│ = 42 + +// Null is rejected by the Array test; it must not contribute an unknown +// element type alongside Array[Str]. Exercise both runtime branches. +:expect [1, 0] +fun read(x: Array[Str] | null) = if x is Array as xs then xs.0.length else 0 +[read(["x"]), read(null)] +//│ = [1, 0] + +// Refinement must not grant an absent member to the matched interface. +:e +class Box(val value: Int) +fun read(x: {value: Int}) = if x is Box as b then b.missing else 0 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.30: fun read(x: {value: Int}) = if x is Box as b then b.missing else 0 +//│ ║ ^^^^^^^^^ +//│ ╟── Value of type 'Box' does not contain member 'missing' +//│ ║ l.30: fun read(x: {value: Int}) = if x is Box as b then b.missing else 0 +//│ ╙── ^^^ + +// A class pattern inside that same class retains the lexical captures needed +// by subsequent field reads and assignments through a structural interface. +:expect 3 +class Buffer(val items: Array[Int]) with + fun read(value: {length: Int}) = + let values = value + if values is Buffer as buffer do + set values = buffer.items + if values is Array as values then values.length else 0 + fun length = items.length +Buffer([1, 2, 3]).read(Buffer([4, 5, 6])) +//│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls new file mode 100644 index 0000000000..00889c9316 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls @@ -0,0 +1,25 @@ +:. + +// The migrated public helpers preserve result interfaces across callbacks. +:expect [4, 5, 6, 7] +class Box(val value: Int) +fun box(x: Int): Box = Box(x) +[(4 |> box).value, (box <| 5).value, (6 .> box).value, (box <. 7).value] +//│ = [4, 5, 6, 7] + +:expect [8, 9] +class Box(val value: Int) +fun box(x: Int): Box = Box(x) +fun value(box: Box): Int = box.value +[(box >> value)(8), (value << box)(9)] +//│ = [8, 9] + +:expect [true, false, true, false, false, true] +class Box(val value: Int) +[[[1], [2]] == [[1], [2]], [[1]] == [[2]], Box(3) == Box(3), Box(3) == Box(4), null == 0, null == null] +//│ = [true, false, true, false, false, true] + +:expect ["a", "b", "c"] +fun stringify(n: Int): Str = ["a", "b", "c"].[n] +[0, 1, 2].map(pass1(stringify)) +//│ = ["a", "b", "c"] diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveEquality.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveEquality.mls new file mode 100644 index 0000000000..5af979b9d8 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveEquality.mls @@ -0,0 +1,21 @@ +:. + +// Keep the unannotated deep-equality implementation separate from Predef's +// public Any × Any -> Bool interface. Recursing on array elements must not +// cause unbounded inference or a stack overflow. +// FIXME: Recursive inference through Array.every currently overflows the stack. +:fixme +:expect [true, false, true, false] +fun deepEqual(a, b) = if + a === b then true + a is Array as a and b is Array as b and a.length === b.length then + a.every((item, index, ...) => deepEqual(item, b.at(index))) + else a !== undefined and a !== null and b !== undefined and b !== null and + let ac = a!constructor + ac !== undefined and ac === b!constructor and + let md = ac!(Predef.Symbols.definitionMetadata) + md !== undefined and + md.2.every(field => field !== null and deepEqual(a!(field), b!(field))) +class Box(val value: Int) +[deepEqual([[1], [2]], [[1], [2]]), deepEqual([[1]], [[2]]), deepEqual(Box(3), Box(3)), deepEqual(Box(3), Box(4))] +//│ /!!!\ Uncaught error: java.lang.StackOverflowError diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls index 893361f3ef..0b93af7cd3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls @@ -101,8 +101,7 @@ fun inspect(value: 1) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.100: fun inspect(value: 1) = value.member //│ ║ ^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'member' of a value with unknown shape. -//│ ╟── No member interface is known for this type. +//│ ╟── Integer literal does not contain member 'member' //│ ║ l.100: fun inspect(value: 1) = value.member //│ ╙── ^ @@ -110,11 +109,11 @@ fun inspect(value: 1) = value.member :ge fun inspect(value: ~Int) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.111: fun inspect(value: ~Int) = value.member +//│ ║ l.110: fun inspect(value: ~Int) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. //│ ╟── This negated type does not specify a member interface. -//│ ║ l.111: fun inspect(value: ~Int) = value.member +//│ ║ l.110: fun inspect(value: ~Int) = value.member //│ ╙── ^^^^ // Calling a value retains the same explanation as selecting its members. @@ -122,13 +121,13 @@ fun inspect(value: ~Int) = value.member class Item(val value: Int) fun invoke(xs: Array[in Item]) = xs.0() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.123: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ l.122: fun invoke(xs: Array[in Item]) = xs.0() //│ ║ ^^^^^^ //│ ╟── Value with no known interface cannot be called like a function. -//│ ║ l.123: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ l.122: fun invoke(xs: Array[in Item]) = xs.0() //│ ║ ^^^^^^^ //│ ╟── This type argument specifies only an input bound. -//│ ║ l.123: fun invoke(xs: Array[in Item]) = xs.0() +//│ ║ l.122: fun invoke(xs: Array[in Item]) = xs.0() //│ ║ ^^^^^^^ //│ ╙── Values read through it have no output bound more specific than 'Any'. @@ -141,14 +140,14 @@ type Pair[A] = [Left[A], Right[A]] let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) pair.1.item.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.142: pair.1.item.value +//│ ║ l.141: pair.1.item.value //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This type argument supplies only an input bound for 'R'. -//│ ║ l.141: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +//│ ║ l.140: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) //│ ║ ^^^^ //│ ╟── Type parameter 'R' is declared contravariant here. -//│ ║ l.139: class Right[in R](val item: R) +//│ ║ l.138: class Right[in R](val item: R) //│ ║ ^ //│ ╙── Values read through it have no output bound more specific than 'Any'. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -164,14 +163,14 @@ private fun delay[A](value: A) = () => value let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) delay(pair)().1.item.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.165: delay(pair)().1.item.value +//│ ║ l.164: delay(pair)().1.item.value //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This type argument supplies only an input bound for 'R'. -//│ ║ l.164: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) +//│ ║ l.163: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) //│ ║ ^^^^ //│ ╟── Type parameter 'R' is declared contravariant here. -//│ ║ l.161: class Right[in R](val item: R) +//│ ║ l.160: class Right[in R](val item: R) //│ ║ ^ //│ ╙── Values read through it have no output bound more specific than 'Any'. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -183,10 +182,10 @@ class Base type Alias = Base fun inspect(value: Alias) = (() => value)().missing //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.184: fun inspect(value: Alias) = (() => value)().missing +//│ ║ l.183: fun inspect(value: Alias) = (() => value)().missing //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'missing' -//│ ║ l.184: fun inspect(value: Alias) = (() => value)().missing +//│ ║ l.183: fun inspect(value: Alias) = (() => value)().missing //│ ╙── ^^^^^ // A function result identifies the annotation that hides implementation members. @@ -195,10 +194,10 @@ class Base fun make(): Base = new Base make().missing //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.196: make().missing +//│ ║ l.195: make().missing //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'missing' -//│ ║ l.195: fun make(): Base = new Base +//│ ║ l.194: fun make(): Base = new Base //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -206,10 +205,10 @@ make().missing :ge fun inspect(value: {present: Int}) = value.missing //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.207: fun inspect(value: {present: Int}) = value.missing +//│ ║ l.206: fun inspect(value: {present: Int}) = value.missing //│ ║ ^^^^^^^^^^^^^ //│ ╟── Record type does not contain member 'missing' -//│ ║ l.207: fun inspect(value: {present: Int}) = value.missing +//│ ║ l.206: fun inspect(value: {present: Int}) = value.missing //│ ╙── ^^^^^^^^^^^^ // Calling a nominal value points to the annotation that made it non-callable. @@ -217,40 +216,40 @@ fun inspect(value: {present: Int}) = value.missing class Base fun invoke(value: Base) = value() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.218: fun invoke(value: Base) = value() +//│ ║ l.217: fun invoke(value: Base) = value() //│ ║ ^^^^^^^ //│ ╟── Value of type 'Base' cannot be called like a function. -//│ ║ l.218: fun invoke(value: Base) = value() +//│ ║ l.217: fun invoke(value: Base) = value() //│ ╙── ^^^^ // Arity errors on a declared callback identify its function type. :ge fun invoke(callback: Int -> Int) = callback() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.228: fun invoke(callback: Int -> Int) = callback() +//│ ║ l.227: fun invoke(callback: Int -> Int) = callback() //│ ║ ^^^^^^^^^^ //│ ╟── Function with a declared signature expected 1 argument, but got 0 -//│ ║ l.228: fun invoke(callback: Int -> Int) = callback() +//│ ║ l.227: fun invoke(callback: Int -> Int) = callback() //│ ╙── ^^^^^^^^^^ // Type argument arity errors retain the same callback signature witness. :ge fun invoke(callback: Int -> Int) = callback[Int](0) //│ ╔══[COMPILATION ERROR] Resolution error in type application -//│ ║ l.238: fun invoke(callback: Int -> Int) = callback[Int](0) +//│ ║ l.237: fun invoke(callback: Int -> Int) = callback[Int](0) //│ ║ ^^^^^^^^^^^^^ //│ ╟── Function with a declared signature expected 0 type arguments, but got 1 -//│ ║ l.238: fun invoke(callback: Int -> Int) = callback[Int](0) +//│ ║ l.237: fun invoke(callback: Int -> Int) = callback[Int](0) //│ ╙── ^^^^^^^^^^ // A function interface also explains missing members on the callback itself. :ge fun inspect(callback: Int -> Int) = callback.missing //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.248: fun inspect(callback: Int -> Int) = callback.missing +//│ ║ l.247: fun inspect(callback: Int -> Int) = callback.missing //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Function with a declared signature does not contain member 'missing' -//│ ║ l.248: fun inspect(callback: Int -> Int) = callback.missing +//│ ║ l.247: fun inspect(callback: Int -> Int) = callback.missing //│ ╙── ^^^^^^^^^^ // Checked indexing reports the annotation that does not expose an array. @@ -258,10 +257,10 @@ fun inspect(callback: Int -> Int) = callback.missing class Base fun inspect(value: Base) = value.[0] //│ ╔══[COMPILATION ERROR] Resolution error in array index -//│ ║ l.259: fun inspect(value: Base) = value.[0] +//│ ║ l.258: fun inspect(value: Base) = value.[0] //│ ║ ^^^^^^^^ //│ ╟── Value of type 'Base' does not support checked array indexing. -//│ ║ l.259: fun inspect(value: Base) = value.[0] +//│ ║ l.258: fun inspect(value: Base) = value.[0] //│ ║ ^^^^ //│ ╙── Use '![...]' for a dynamic access. @@ -271,13 +270,13 @@ class Base val instance: Base = new Base new instance //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.272: new instance +//│ ║ l.271: new instance //│ ║ ^^^^^^^^^^^^ //│ ╟── Value with a declared type cannot be instantiated with keyword 'new'. -//│ ║ l.271: val instance: Base = new Base +//│ ║ l.270: val instance: Base = new Base //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.272: new instance +//│ ║ l.271: new instance //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ instance = Base @@ -288,11 +287,11 @@ declare type Hidden type Alias = Hidden fun inspect(value: Alias) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.289: fun inspect(value: Alias) = value.member +//│ ║ l.288: fun inspect(value: Alias) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. -//│ ║ l.289: fun inspect(value: Alias) = value.member +//│ ║ l.288: fun inspect(value: Alias) = value.member //│ ║ ^^^^^ //│ ╟── Type 'Hidden' is declared without a member interface. -//│ ║ l.287: declare type Hidden +//│ ║ l.286: declare type Hidden //│ ╙── ^^^^^^ From a6c3bd9d1cb25a8be5c369af0754d13fb107c36e Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 00:16:13 +0800 Subject: [PATCH 170/228] Preserve scope paths and stabilize recursive pattern refinement --- doc/new-resolution-design.md | 5 + doc/new-resolution-scopes.md | 161 ++++++++++++++++++ doc/new-resolution-suite-migration.md | 13 +- doc/new-resolution-type-value-flow.md | 5 + .../jvm/src/test/scala/hkmc2/MarksTest.scala | 44 +++++ .../test/scala/hkmc2/TypeRelationTest.scala | 3 +- .../main/scala/hkmc2/codegen/Erasure.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 4 +- .../scala/hkmc2/semantics/NewResolver.scala | 96 +++++++---- .../hkmc2/semantics/NewResolverState.scala | 8 + .../main/scala/hkmc2/semantics/Shape.scala | 8 + .../scala/hkmc2/semantics/TypeShape.scala | 20 ++- .../newres/InheritedTypeArguments.mls | 9 +- .../newres/RecursiveBooleanInterfaces.mls | 18 +- .../mlscript/newres/RecursiveEquality.mls | 14 +- .../src/test/mlscript/newres/ScopePaths.mls | 72 ++++++++ 16 files changed, 418 insertions(+), 66 deletions(-) create mode 100644 doc/new-resolution-scopes.md create mode 100644 hkmc2/shared/src/test/mlscript/newres/ScopePaths.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 462dc5a4c2..0ba6bee880 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -53,6 +53,11 @@ resolution boundary. Methods capture their enclosing instance scope as well as the enclosing function scopes. Alias qualification and structural type-field projection introduce no value boundary; modules introduce no invocation boundary. +[Lexical paths in resolution](new-resolution-scopes.md) derives the normal form, +boundary agreement, and depth bound from walks in the lexical scope tree. It also +states the endpoint obligations on producers and the separate requirement for +stable identities in recursive inference graphs. + A normalized mark path contains entries followed by exits, with no repeated lexical boundary in either direction. `Shape.scala` enables assertions for this invariant. Checking each new tail costs linear time in its depth; it must not be diff --git a/doc/new-resolution-scopes.md b/doc/new-resolution-scopes.md new file mode 100644 index 0000000000..0eb4ddb44d --- /dev/null +++ b/doc/new-resolution-scopes.md @@ -0,0 +1,161 @@ +# Lexical paths in resolution + +This note gives the scope calculus used by `Shape.scala`, the invariant needed +by its assertions, and the obligations on operations that construct paths. +The paths describe lexical environments, not the runtime call stack. + +## Scopes and endpoints + +Contract the source's scope tree to the boundaries represented by resolution: +function and method bodies, and class instance bodies. A constructor and its +class name the same boundary. Local blocks, anonymous lambdas, modules, and +objects do not add boundaries in this analysis; their bindings live at the +nearest represented scope. In particular, a type alias performs substitution +and has no value activation. `OuterCtx.resolutionBoundary` makes this decision +at elaboration, before either term or type references are constructed. + +Each inference host has one lexical endpoint. An ordinary parameter's host is +inside its function; a class parameter's host is inside its instance scope. +Instantiating a type parameter changes the host's identity, not this endpoint. +An argument reaches the parameter by traversing the reverse of the callable's +path to the caller. A result traverses that same path forward. Recursion goes +out of one activation and into another along the lexical tree; it does not +add another copy of the function to the lexical tree. + +A core shape describes a value at its origin. Its marks describe a walk from +that origin to the host observing it. A compound shape may contain references +with their own endpoints: tuple fields, declared type arguments, and the head +of an applied constructor are examples. Moving the compound delays movement of +those references until they are observed; it must not reinterpret their hosts +at the compound's destination. + +## Reduction and its proof + +Write `E(b, i)` for entering boundary `b` at static site `i`, and `X(b, i)` for +exiting it. An absent site is a wildcard, used for a lexical capture or a +declared interface that does not identify an activation. Here words are written +in execution order; the linked `Marks` representation stores that order reversed. + +There is one reduction rule: + +``` +E(b, i) X(b, j) = identity if i = j or either site is absent +E(b, i) X(b, j) = no flow otherwise +``` + +The rejected case is an ordinary result of inference, represented by `NoShape`. +It prevents a candidate supplied by one call from returning through an +incompatible call. An entry followed immediately by an exit must name the same +boundary: both steps traverse the edge between the current scope and its parent. +Different boundary names at this point are an incorrectly composed path, not +a reason to discard a possible source program. + +An exit followed by an entry is retained, even for two wildcard sites. On an +incoming activation stack it can remove an identified activation and replace +it with an unidentified one. Cancelling this pair would change subsequent +filtering. It also records the provenance of a closure or instance that left +one activation before entering another. + +For any finite, well-scoped walk: + +1. Each reduction shortens the word by two, so reduction terminates. +2. Removing an entry/exit pair preserves the walk's endpoints. Its stack action + is the identity when compatible; otherwise no activation can traverse it. +3. A nonempty reduced word has only exits followed by entries. Any later exit + after an entry would contain another adjacent entry/exit pair. +4. Its exits ascend a tree and its entries descend a tree. Neither run can + repeat a boundary. Its length is at most the source depth plus destination + depth, independently of runtime recursion depth. +5. Two reducible pairs cannot overlap, since a crossing cannot be both entry + and exit. Reductions of disjoint pairs commute, including rejection. + Thus normalization is independent of grouping, and composition is associative. + +These facts establish the three mark assertions for well-scoped walks: adjacent +entry/exit boundaries agree, and neither entries nor exits repeat a boundary. +They apply to arbitrary lexical depth, call-site labels, recursion, and scope +trees, not only to the programs in the test suite. `MarksTest` also compares +generated walks against a separate activation-stack interpreter, including +every prefix and the depth bound. Its purpose is to check the implementation +of the algebra, beyond what a worksheet's final result can observe. + +This proof has a necessary premise: edges must be composed at the same lexical +endpoint. Finite marks alone do not establish that premise for compiler code. +The assertions remain enabled to catch a violation by a producer. Suppressing +them, treating a boundary mismatch as `NoShape`, or truncating a repeated scope +would hide an incorrectly located host and could silently change resolution. + +## Maintaining the endpoint premise + +The producer rules are compositional: + +| Operation | Endpoint obligation | +| --- | --- | +| Lexical reference | Elaboration adds one `Capture` per represented boundary between the binding and the use. Explicit `this` follows the same rule. | +| Call or constructor application | Enter argument paths in reverse fragment order; leave results in forward order. Curried lists retain the original callable path. Class and constructor symbols share `ResolutionBoundary`. | +| Assignment | Undo the left-hand reference's captures before publishing to its binding host. | +| Inferred member | Observe its definition through the receiver's path; class fields keep their class parameter endpoint. | +| Declared member | Interpret the signature at its declaration, bind supplied class arguments there, then traverse the member/class exits and the path to the use. | +| Type reference | Retain the selected declaration, its receiver bindings, its substitution, and its path. Identity alone is sufficient for erasure, but not for interpreting free binders. | +| Type application | Move supplied arguments back to the template endpoint before substituting; move the interpreted result forward. Omitted arguments stay at their original inference host. | +| Deferred type projection | Compose transport on the `InstanceShape` reference before observation, including references nested in fields or arguments. | +| Import or activation view | Copy or instantiate hosts without moving their lexical endpoints. | + +For declared nominal members, distinguish the receiver's origin from the member's +declaration. A tuple literal can be created inside a function even though its +`Array` methods are declared globally. `splitReceiverPath` partitions the +receiver's leading upward journey: exits from scopes below the declaration's +enclosing scopes belong to the argument bindings' provenance. The remaining +path locates the declaration at the consumer. Tree ancestry makes this a prefix +split; a later exit cannot return to a deeper, unrelated scope. Moving the +member itself through the provenance portion would assign different endpoints +to the same method-parameter host for different receiver candidates. + +`TypeShape.Reference` is the common representation for a syntactic capture and +a qualified type selection. It is an **open** expression: the enclosing +declaration can still substitute its free binders. Receiver bindings override +those ambient bindings, and captured instances override ambient instances. +Interpretation closes the expression with `declaredType` and transports the +result with `transportType`. `TypeShape.Contextual`, in contrast, holds an +already interpreted `DeclaredType` with its own bindings and normalized path. +Confusing the two loses the enclosing substitution or applies it twice. + +For example, in a generic `make[A]`, a local module can declare `Parent` with a +field of type `A`. A nested `read(x: Types.Parent)` must retain the path from +`Types` to `read`. Dropping it reads `A` in `make` but later tries to leave +`read`, violating the endpoint premise. The same rule applies to an alias, +a nested class, a returned module, or a reference through an imported definition. +Type aliases must also be excluded at elaboration: ignoring an alias capture +only in type interpretation would leave the same fictitious boundary on the +term receiver used by a qualified selection. + +## Finite paths and finite inference graphs + +Bounded path length does not by itself prove that inference reaches a fixed +point. Core shapes and synthesized type references need stable identities too. +Publishers deduplicate candidates, and synthesized nodes must be memoized by +their semantic inputs before downstream observation can revisit them. + +Class-pattern refinement is one such operation. For an unknown input tested +against `Array`, it constructs a declared `Array` interface whose element is +unknown. A recursive `every` callback can send that element back to the same +test. Allocating a fresh element-type host on each visit produces infinitely +many different `Array` candidates even though all paths are bounded. + +`patternRefinements` therefore keys the synthesized type by the pattern's syntax +identity, selected class, constructor path, and input shape. The path is part +of the key because the same class can be reached through different captured +activations. For each such tuple there is one refinement graph, installed before +its interface is observed. Revisiting the tuple reuses the graph and publisher +deduplication closes the cycle. Diagnostic provenance does not create new +semantic candidates. The cache lives in `NewResolverState`, so imported graphs +retain the existing consumer-isolation rules. + +This is distinct from the regularity check for recursive declared types and +the widening of recursive tuple spreads. Those operations have their own +finiteness arguments; path truncation is not a substitute for either one. + +`newres/InheritedTypeArguments.mls`, `ScopePaths.mls`, and `NominalMemberPaths.mls` +exercise declaration endpoints and separate activations. `RecursiveEquality.mls` +checks both deep equality and mutually recursive array callbacks without +expected-failure markers. LazyFingerTree and the parser compilation fixtures +exercise the same rules in larger programs. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index f0d4196bf0..b418e5d3c0 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -141,8 +141,8 @@ packaging constraint. functions, constructor aliases, reconstruction of the same class, and partial construction. Existing regressions include `CtxSens`, `ValCtxSens`, `Projections`, `RecursiveEnvironment`, `SpreadCalls`, and `InterfaceExposure` - under `newres`. A local module capturing an outer binder has a scope failure - recorded with `:fixme` in `newres/InheritedTypeArguments.mls`. Do not turn unknown + under `newres`. Local modules capturing outer binders are covered by + `newres/InheritedTypeArguments.mls` and `ScopePaths.mls`. Do not turn unknown shapes into dynamic values or truncate capture paths to make these cases pass. - **Exposed nominal results:** `newres/InterfaceExposure.mls` records a returned private class whose callable methods are not checked through its nominal result @@ -151,10 +151,11 @@ packaging constraint. - **Cross-block flow:** remaining cases include parameter flow in `basics/MiscArrayTests`, unfinished closures in `codegen/FirstClassFunctionTransform`, and the unresolved receiver in `codegen/ObjectMethodDebinding`. -- **Recursive inference:** `newres/RecursiveEquality.mls` preserves the unannotated - deep-equality function and its expected results. Resolution currently overflows - the stack on its recursive array callback. `Predef.equals` uses the public - `Any × Any -> Bool` interface; that annotation does not fix unannotated inference. +- **Recursive inference:** `newres/RecursiveEquality.mls` now runs the unannotated + deep-equality function and mutually recursive array callbacks. Pattern refinement + reuses synthesized type arguments for the same test, constructor path, and input, + allowing publisher deduplication to close the cycle. See the + [scope and graph invariants](new-resolution-scopes.md). ### Patterns and generated references diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 4b3b32935d..b9dee9de47 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -342,6 +342,11 @@ Relation replay adds no candidates, listeners, or parameter instances. ## Scope transport +See [Lexical paths in resolution](new-resolution-scopes.md) for the scope-tree +model, reduction proof, and producer obligations. Syntactic captures and qualified +type selections share `TypeShape.Reference`; both retain the declaration's +endpoint before the enclosing substitution is applied. + `ContextualType` pairs a reference with an ordinary normalized mark path. `transportType` uses the same mark operations as value flow and flattens existing contextual nodes before interning the endpoint; references contain diff --git a/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala index f90230607b..86d751456d 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/MarksTest.scala @@ -85,3 +85,47 @@ class MarksTest extends AnyFunSuite: val uncancelled = h.value.exit(h.path(scope, scope, N, N)) assert(uncancelled != h.value) assert(uncancelled.exit(h.exits(scope, h.sites(2))) == h.value.exit(h.exits(scope, N))) + + test("arbitrary lexical walks preserve stack semantics and have a lexical depth bound"): + val h = new Harness + import h.given + case class Crossing(entering: Boolean, boundary: ResolutionBoundary, site: Opt[FlowSymbol]) + type Stack = List[(ResolutionBoundary, Opt[FlowSymbol])] + // Interpret the unreduced walk independently, on a complete activation + // stack. Diff tests cannot observe this equivalence for every path prefix. + def step(stack: Opt[Stack], crossing: Crossing): Opt[Stack] = stack.flatMap: stack => + if crossing.entering then S((crossing.boundary, crossing.site) :: stack) + else stack match + case (boundary, site) :: rest => + assert(boundary == crossing.boundary) + if site.isEmpty || crossing.site.isEmpty || site == crossing.site then S(rest) else N + case Nil => fail("A lexical walk cannot exit the root") + def crossings(marks: Marks): List[Crossing] = marks match + case NoMarks => Nil + case EntryMark(boundary, site, rest) => crossings(rest) :+ Crossing(true, boundary, site) + case ExitMark(boundary, site, rest) => crossings(rest) :+ Crossing(false, boundary, site) + val random = new scala.util.Random(0L) + for trial <- 0 until 400 do + val start = h.scopes(random.nextInt(h.scopes.size)) + val inputs = h.sites.map(site => start.reverse.map(_ -> site)) + var expected = inputs.map(S(_): Opt[Stack]) + var scope = start + var reduced: TermShape | NoShape = h.value + for _ <- 0 until 80 do + val children = h.scopes.filter(s => s.length == scope.length + 1 && s.startsWith(scope)) + val entering = scope.isEmpty || (children.nonEmpty && random.nextBoolean()) + val boundary = if entering then children(random.nextInt(children.size)).last else scope.last + // Half the walks never filter: repeated recursion must remain bounded + // even when every possible return site is accepted. + val site = if trial % 2 == 0 then N else h.sites(random.nextInt(h.sites.size)) + val crossing = Crossing(entering, boundary, site) + expected = expected.map(step(_, crossing)) + scope = if entering then scope :+ boundary else scope.init + val mark = if entering then EntryMark(boundary, site, NoMarks) else ExitMark(boundary, site, NoMarks) + reduced = reduced.exit(mark) + reduced match + case NoShape => assert(expected.forall(_.isEmpty)) + case Marked(_, marks) => + val normal = crossings(marks) + assert(normal.length <= start.length + scope.length) + assert(inputs.map(input => normal.foldLeft[Opt[Stack]](S(input))(step)) == expected) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 39a984b147..2b71cc3c32 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -126,7 +126,8 @@ class TypeRelationTest extends AnyFunSuite: val alias = h.tpe(TypeShape.Alias(symbol, S(ft.resolution))) val scope = TermSymbol(Fun, N, Tree.Ident("owner")) val owner = ResolutionBoundary(scope) - val captured = h.tpe(TypeShape.Captured(alias.resolution, scope)) + val captured = h.tpe(TypeShape.Reference(alias.resolution, + EntryMark(owner, N, NoMarks) :: Nil, Map.empty, TypeSubstitution.empty)) val (argument, at) = h.parameter("A") val application = h.tpe(TypeShape.Applied(captured.resolution, List(at.resolution))) val (target, tt) = h.parameter("Target") diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala index 9a4e932bb1..56324e3a0e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala @@ -30,7 +30,7 @@ final class Erasure private (using Config, Ctx, State): case _ => ErasedType.Unknown case Alias(_, rhs) :: Nil => rhs.fold(ErasedType.Unknown)(erase(_, next)) case Union(left, right) :: Nil => ErasedType.union(erase(left, next), erase(right, next)) - case Captured(base, _) :: Nil => erase(base, next) + case Reference(base, _, _, _) :: Nil => erase(base, next) case Applied(base, _) :: Nil => erase(base, next) case Function(_, _) :: Nil => ErasedType.Function(S(false)) case Polymorphic(_, _, body) :: Nil => erase(body, next) @@ -79,7 +79,7 @@ final class Erasure private (using Config, Ctx, State): else if count == 0 then erase(res, Set.empty) else res.shapes.toList match case TypeShape.Alias(_, S(rhs)) :: Nil => fullResult(rhs, count, seen + res) - case TypeShape.Captured(base, _) :: Nil => fullResult(base, count, seen + res) + case TypeShape.Reference(base, _, _, _) :: Nil => fullResult(base, count, seen + res) case TypeShape.Applied(base, _) :: Nil => fullResult(base, count, seen + res) case TypeShape.Polymorphic(_, _, body) :: Nil => fullResult(body, count, seen + res) case TypeShape.Function(_, ret) :: Nil => fullResult(ret, count - 1, seen + res) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 61951e9b72..072a7c4714 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -61,12 +61,14 @@ object Elaborator: /** Functions and instantiated class bodies cross lexical resolution boundaries. * Function's NonReturnContext already supplies its boundary, so Function must - * not introduce it again. Modules/objects have no per-instance boundary. + * not introduce it again. Modules/objects have no per-instance boundary; + * type aliases substitute types without entering any value activation. * In C's method, referring to the outer C constructor crosses the old instance * boundary too. That capture cancels the old instance exit when the method's * result is consumed; the fresh constructor exit belongs to the new instance. */ def resolutionBoundary: Opt[AnyDefinitionSymbol] = this match + case NonReturnContext(S(_: TypeAliasSymbol)) => N case NonReturnContext(sym) => sym case InnerScope(sym: ClassSymbol) => S(sym) case _ => N diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 53d7252470..17735e2794 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -69,22 +69,42 @@ class NewResolver: // have no stored interpretation: their new inference graph must remain // private to this consumer, including listeners on synthesized hosts. rs.typeInterpretations(new Identity(term)) = result - def select(symbol: TypeSymbol)(using NewResolverState): Unit = + def selectInto(symbol: TypeSymbol, publish: ShapeListener[TypeShape])(using NewResolverState): Unit = term.withoutCaptures match case ref: NewResolvable => rstate.recordResolution(ref, ref.resolvedTargets.contains(symbol))(ref.resolvedTargets ::= symbol) case _ => () def definition(defn: Definition)(using NewResolverState): Unit = defn match - case cls: ClassLikeDef => result.publish(TypeShape.Nominal(cls)) + case cls: ClassLikeDef => publish(TypeShape.Nominal(cls)) case alias: TypeDef => - result.publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) + publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) // Validate unused annotations too, once their forward source graph // is complete. Interface observation uses the same gate below. withCanonicalType(DeclaredType(result, Map.empty, TypeSubstitution.empty, true)(N))(_ => ()) - case _ => result.publish(TypeShape.Abstract) + case _ => publish(TypeShape.Abstract) symbol.defn match case S(defn) => definition(defn) case N => symbol.defnListeners += definition + def select(symbol: TypeSymbol)(using NewResolverState): Unit = selectInto(symbol, result.publish) + def selectReference(symbol: TypeSymbol, shape: SymShape)(using NewResolverState): Unit = + val (source, instances) = shape match + case contextual: ContextualSymShape => (contextual.source, contextual.instances) + case source: CoreSymShape => (source, TypeSubstitution.empty) + val bindings = source match + case declared: DeclaredSymShape => declared.bindings + case _ => Map.empty[VarSymbol, DeclaredType] + if shape.markss.isEmpty && bindings.isEmpty && instances.isEmpty then select(symbol) + else + // A symbol identity determines erasure, but not the environment of + // its free binders. Keep the selected declaration at its own endpoint + // and transport it just as a syntactic Capture transports a type. + val key = (new Identity(term), symbol) + val base = rstate.selectedTypes.get(key).getOrElse: + val base = new TypeResolution(term, result.fail) + rstate.selectedTypes(key) = base + selectInto(symbol, base.publish) + base + result.publish(TypeShape.Reference(base, shape.markss, bindings, instances)) def reject(shape: Shape)(using NewResolverState): Unit = result.fail(msg"${shape.describe.capitalize} cannot be used as a type" -> shape.toLoc :: Nil) result.publish(TypeShape.Abstract) @@ -112,7 +132,8 @@ class NewResolver: // does not cross an invocation boundary. Captured function and class // parameters still retain their actual value-scope crossings. case Capture(base, _: TypeAliasSymbol) => typeResolution(base).listen(result.publish) - case Capture(base, thru) => result.publish(TypeShape.Captured(typeResolution(base), thru)) + case Capture(base, thru) => result.publish(TypeShape.Reference(typeResolution(base), + EntryMark(ResolutionBoundary(thru), N, NoMarks) :: Nil, Map.empty, TypeSubstitution.empty)) case TyApp(base, args) => result.publish(TypeShape.Applied(typeResolution(base), args.map(typeResolution))) case Forall(params, outer, body) => result.publish(TypeShape.Polymorphic(params.map: param => @@ -156,7 +177,7 @@ class NewResolver: case _ => listen(term, discardMarks = true): case shape: SymShape => shape.sym.onComplete: () => shape.sym.asTpe.orElse(shape.sym.asModOrObj) match - case S(symbol) => select(symbol) + case S(symbol) => selectReference(symbol, shape) case N => reject(shape) case shape: TermShape => reject(shape) result @@ -187,7 +208,7 @@ class NewResolver: @tailrec def loop(base: TypeResolution, seen: Set[TypeResolution]): Opt[TypeShape] = if seen(base) then N else base.currentShapes.toList match - case TypeShape.Captured(inner, _) :: Nil => loop(inner, seen + base) + case TypeShape.Reference(inner, _, _, _) :: Nil => loop(inner, seen + base) case shape :: Nil => S(shape) case _ => N loop(base, Set.empty) @@ -243,7 +264,9 @@ class NewResolver: case TypeShape.Alias(symbol, rhs) => val bound = symbol.defn.get.tparams.map(_.sym).toSet rhs.foreach(edge(_, bound)) - case TypeShape.Captured(base, _) => child(base) + case TypeShape.Reference(base, _, bindings, _) => + edge(base, bindings.keySet) + bindings.values.foreach(reference) case TypeShape.Applied(base, args) => child(base) // An alias argument contributes dependencies only if the body @@ -324,7 +347,7 @@ class NewResolver: def visit(res: TypeResolution): Unit = if visited.add(res) then res.currentShapes.foreach: case TypeShape.Alias(_, rhs) => rhs.foreach(visit) - case TypeShape.Captured(base, _) => visit(base) + case TypeShape.Reference(base, _, _, _) => visit(base) case TypeShape.Applied(base, args) => alias(base).foreach: (symbol, rhs) => applications += ((res, symbol, rhs, args)) @@ -368,7 +391,7 @@ class NewResolver: def follow(child: TypeResolution): Unit = checkGuarded(child, next) res.currentShapes.foreach: case TypeShape.Alias(_, rhs) => rhs.foreach(follow) - case TypeShape.Captured(base, _) => follow(base) + case TypeShape.Reference(base, _, _, _) => follow(base) case TypeShape.Applied(base, args) => follow(base) typeApplicationTarget(base) match @@ -498,7 +521,9 @@ class NewResolver: DeclaredType(resolution, relevant, TypeSubstitution.empty, positive)(N) resolution.currentShapes.toList match case TypeShape.Parameter(symbol, _) :: Nil if bindings.contains(symbol) => selectArgument(bindings(symbol), positive) - case TypeShape.Captured(base, thru) :: Nil => captureType(loop(base, bindings, aliases, positive), thru) + case (reference: TypeShape.Reference) :: Nil => + transportType(loop(reference.base, bindings ++ reference.bindings, aliases, positive) + .instantiate(reference.instances), reference.marks) case TypeShape.Union(left, right) :: Nil => combinedType(resolution, loop(left, bindings, aliases, positive), loop(right, bindings, aliases, positive), true) case TypeShape.Intersection(left, right) :: Nil => @@ -606,7 +631,7 @@ class NewResolver: declaredType(part, argument.copy(positive = if positive then argument.positive else !argument.positive)(argument.origin)) case TypeShape.Contextual(reference) :: Nil => transportType(selectArgument(reference.tpe.instantiate(argument.instances).withOrigin(argument.origin), positive), reference.marks) - case Nil | TypeShape.Captured(_, _) :: Nil => deferred + case Nil | TypeShape.Reference(_, _, _, _) :: Nil => deferred case _ => argument private def instanceType(instance: TypeParameterInstance)(using NewResolverState): DeclaredType = @@ -674,9 +699,8 @@ class NewResolver: output.fold(missingOutput(tpe.resolution))(declaredType(_, tpe)))) case TypeShape.Argument(parts) => listener(TypeArgument(parts.input.instantiate(tpe.instances), parts.output.instantiate(tpe.instances).withOrigin(tpe.origin))) - case TypeShape.Captured(base, thru) => - listenArgumentParts(declaredType(base, tpe)): parts => - listener(TypeArgument(captureType(parts.input, thru), captureType(parts.output, thru))) + case reference: TypeShape.Reference => + listenArgumentParts(referenceType(reference, tpe))(listener) case TypeShape.Contextual(reference) => listenArgumentParts(reference.tpe.instantiate(tpe.instances).withOrigin(tpe.origin)): parts => listener(TypeArgument(transportType(parts.input, reference.marks), transportType(parts.output, reference.marks))) @@ -756,8 +780,8 @@ class NewResolver: case NoShape => () case TypeShape.Parameter(symbol, _) => current.bindings.get(symbol).foreach(bound => follow(selectArgument(bound.instantiate(current.instances), current.positive), noTypeArguments, captures, next)) - case TypeShape.Captured(base, thru) => - follow(captureType(declaredType(base, current), thru), args, captures, next) + case reference: TypeShape.Reference => + follow(referenceType(reference, current), args, captures, next) case TypeShape.Contextual(reference) => follow(reference.tpe.instantiate(current.instances).withOrigin(current.origin), args.move(inverseMarks(reference.marks)), reference.marks ::: captures, next) @@ -960,8 +984,8 @@ class NewResolver: annotated match case value: TermShape => listenInstanceViews(instantiateShape(value, current.instances))(publish) case NoShape => () - case TypeShape.Captured(base, thru) => - follow(captureType(declaredType(base, current), thru), args, aliases, publish) + case reference: TypeShape.Reference => + follow(referenceType(reference, current), args, aliases, publish) case TypeShape.Contextual(reference) => // Arguments belong to the application scope, whereas the // template belongs to the reference's source scope. Move the @@ -1064,6 +1088,10 @@ class NewResolver: case Marked(_, path: SomeMarks) => path :: Nil case NoShape => lastWords("Inverting a scope path cannot reject a candidate") + private def referenceType(reference: TypeShape.Reference, context: DeclaredType)(using NewResolverState): DeclaredType = + transportType(declaredType(reference.base, context.bindings ++ reference.bindings, context.positive) + .instantiate(context.instances.withOverrides(reference.instances)).withOrigin(context.origin), reference.marks) + private[semantics] def captureType(bound: DeclaredType, scope: AnyDefinitionSymbol)(using NewResolverState): DeclaredType = transportType(bound, EntryMark(ResolutionBoundary(scope), N, NoMarks) :: Nil) @@ -1280,8 +1308,8 @@ class NewResolver: case NoShape => () case _ => () else publishParameter(destination, lower) - case TypeShape.Captured(base, thru) => - follow(captureType(declaredType(base, tpe), thru), args, captures, next) + case reference: TypeShape.Reference => + follow(referenceType(reference, tpe), args, captures, next) case TypeShape.Contextual(reference) => follow(reference.tpe.instantiate(tpe.instances).withOrigin(tpe.origin), args.move(inverseMarks(reference.marks)), reference.marks ::: captures, next) @@ -2081,15 +2109,23 @@ class NewResolver: // A class test can succeed for an external input. Its fields then // expose their declarations, not shapes observed in local calls. // Unknown type arguments retain the original diagnostic witness. - val base = testedClass.resolution - val arg = new TypeResolution(unknown.source, messages => resolError(unknown.source, messages)) - val contextual = transportShape(sh, inverseMarks(context)) - contextual match - case value: TermShape => arg.publish(TypeShape.Inferred(value)) - case NoShape => () - val applied = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) - applied.publish(TypeShape.Applied(base, cls.tparams.map(_ => arg))) - atPattern(declaredType(applied, Map.empty))(check(_, S(sh))) + // Refinement is a function of a static test and its input, not + // an allocation on every delivery. A recursive callback can + // feed the same unknown back to this test through Array[T]. + // Fresh argument hosts would make each otherwise identical + // refined Array a new candidate and prevent a fixed point. + val refined = rstate.patternRefinements.getOrElseUpdate((new Identity(ctor), cls.sym, context, sh), { + val base = testedClass.resolution + val arg = new TypeResolution(unknown.source, messages => resolError(unknown.source, messages)) + val contextual = transportShape(sh, inverseMarks(context)) + contextual match + case value: TermShape => arg.publish(TypeShape.Inferred(value)) + case NoShape => () + val applied = new TypeResolution(ctor.target, messages => resolError(ctor.target, messages)) + applied.publish(TypeShape.Applied(base, cls.tparams.map(_ => arg))) + declaredType(applied, Map.empty) + }) + atPattern(refined)(check(_, S(sh))) case Marked(opaque: OpaqueTypeShape, marks) => UnknownValueShape(opaque.source)(opaque.provenance).exit(marks) match case value: TermShape => narrow(value) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 38cb147bfc..dfe526ad7e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -237,6 +237,10 @@ final class NewResolverState private ( new Cache(inherited.map(_.defnShapes), identity) val typeInterpretations: Cache[Identity[Term], TypeResolution] = new Cache(inherited.map(_.typeInterpretations), identity) + // Qualification can deliver the same declaration in several environments. + // Its open source type is shared before any environment observes the body. + val selectedTypes: Cache[(Identity[Term], TypeSymbol), TypeResolution] = + new Cache(inherited.map(_.selectedTypes), identity) // Recorded before elaborating a declaration's members, including in legacy // exporters. Nominal interfaces may use any enclosing explicit type binder. val lexicalTypeBinders: Cache[AnyDefinitionSymbol, Set[VarSymbol]] = @@ -317,6 +321,10 @@ final class NewResolverState private ( new Cache(inherited.map(_.typeViews), identity) val patternTypes: Cache[(Identity[Pattern.Constructor], InnerSymbol), DeclaredType] = new Cache(inherited.map(_.patternTypes), identity) + // Repeated refinement of one input must reuse its synthesized type hosts. + // Constructor paths distinguish references to the same captured class. + val patternRefinements: Cache[(Identity[Pattern.Constructor], InnerSymbol, Ls[Marks], TermShape), DeclaredType] = + new Cache(inherited.map(_.patternRefinements), identity) val primitiveTypes: Cache[ClassSymbol, DeclaredType] = new Cache(inherited.map(_.primitiveTypes), identity) val extremeTypes: Cache[(Bool, Opt[TypeResolution]), DeclaredType] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index fc361286b1..f2253ef4b5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -88,6 +88,7 @@ final case class ResolutionBoundary private (symbol: AnyDefinitionSymbol): override def toString: Str = symbol.toString object ResolutionBoundary: def apply(symbol: AnyDefinitionSymbol): ResolutionBoundary = + assert(!symbol.isInstanceOf[TypeAliasSymbol], "Type aliases do not introduce value resolution scopes") new ResolutionBoundary(symbol match case ctor: ClassCtorSymbol => ctor.associatedCls case symbol => symbol) @@ -104,6 +105,13 @@ private val checkMarkPaths = true * Each direction traverses distinct lexical scopes, so recursive calls cannot * lengthen a normalized path indefinitely. Debug assertions check this invariant * without widening paths. + * + * A well-scoped walk's adjacent entry/exit steps traverse the same tree edge. + * Cancelling them preserves endpoints; a reduced walk only ascends, then + * descends. Neither run can revisit a boundary, and its length is bounded by + * the sum of the endpoint depths. Producers must preserve those endpoints, + * including through qualified type selections and deferred type references. + * See doc/new-resolution-scopes.md for the proof and the producer contracts. */ sealed abstract class Marks: def showDbg(using DebugPrinter): Str = this match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index c675d35a6b..45437a2323 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -43,7 +43,10 @@ enum TypeShape: // the element union of the Array supertype of a tuple. Singleton annotations // also retain their literal shape; no implementation is inspected. case Inferred(value: TermShape) - case Captured(base: TypeResolution, thru: AnyDefinitionSymbol) + // An open type expression reached through a reference. Unlike Contextual, + // its free binders still receive the observing declaration's substitution. + // Qualified references retain the receiver's bindings and lexical path too. + case Reference(base: TypeResolution, marks: Ls[Marks], bindings: Map[VarSymbol, DeclaredType], instances: TypeSubstitution) case Unit case Dynamic case Abstract @@ -74,17 +77,20 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => case _ => false if shapes.isEmpty then fail(msg"This type has no resolved target" -> source.toLoc :: Nil) else if ambiguousReceiver || shapes.sizeCompare(1) > 0 then - val candidates = source.withoutCaptures match - case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.map: (prefix, member) => - msg"candidate: ${member.describe}" -> member.toLoc - case _ => shapes.toList.flatMap: + def declarations(resolution: TypeResolution, seen: Set[TypeResolution]): Ls[(Message, Opt[Loc])] = + if seen(resolution) then Nil else resolution.shapes.toList.flatMap: case Nominal(defn) => msg"candidate: ${defn.sym.describeKind}" -> defn.toLoc :: Nil case Alias(symbol, _) => msg"candidate: ${symbol.describeKind}" -> symbol.toLoc :: Nil + case Reference(base, _, _, _) => declarations(base, seen + resolution) case _ => Nil + val candidates = source.withoutCaptures match + case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.map: (prefix, member) => + msg"candidate: ${member.describe}" -> member.toLoc + case _ => declarations(this, Set.empty).distinct fail(msg"This type is ambiguous, as it has multiple resolved targets" -> source.toLoc :: candidates) shapes.foreach: case Alias(_, rhs) => rhs.foreach(_.validate(next)) - case Captured(base, _) => base.validate(next) + case Reference(base, _, _, _) => base.validate(next) case Applied(base, args) => base.validate(next) args.foreach(_.validate(next)) @@ -95,7 +101,7 @@ final class TypeResolution(val source: Term, report: Ls[(Message, Opt[Loc])] => base.shapes.toList match case Nominal(defn) :: Nil => check(defn.sym.nme, defn.tparams.length, defn.toLoc) case Alias(symbol, _) :: Nil => symbol.defn.foreach(d => check(symbol.nme, d.tparams.length, d.toLoc)) - case Captured(inner, _) :: Nil => checkArity(inner, seen + base) + case Reference(inner, _, _, _) :: Nil => checkArity(inner, seen + base) case _ => () checkArity(base, Set.empty) case Wildcard(input, output) => input.foreach(_.validate(next)); output.foreach(_.validate(next)) diff --git a/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls b/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls index 8eb4d0c8a0..96c3493bd3 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InheritedTypeArguments.mls @@ -98,9 +98,6 @@ private fun read[A](parent: Types.Parent[A]) = parent.item // A qualified parent under an enclosing generic activation retains the outer // binder, rather than treating it as one of the child's own type parameters. -// FIXME: Reading the outer binder through the local module still loses the -// intervening read scope after the parent constraint has been transported. -:fixme :expect [6, 7] class First(val first: Int) class Second(val second: Int) @@ -112,7 +109,7 @@ private fun make[A](item: A) = fun read(parent: Types.Parent) = parent.item read(child()) [make(First(6)).first, make(Second(7)).second] -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: Entry and exit cross different lexical resolution scopes: term:make⁰ vs term:read⁰ for ContextualShape(AppShape(MarkedShape(member:First⁰, ↗⟨class:First¹⟩%⟨First²⟩ϵ), [6])) +//│ = [6, 7] // Constraints on a callable parent argument must also propagate its input. :expect 8 @@ -137,10 +134,10 @@ private fun read[A](parent: Types.Parent[A]) = parent.item read(make(Types.Second(9))).second read(make(Types.First(8))).second //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.138: read(make(Types.First(8))).second +//│ ║ l.135: read(make(Types.First(8))).second //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'First' does not contain member 'second' -//│ ║ l.138: read(make(Types.First(8))).second +//│ ║ l.135: read(make(Types.First(8))).second //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls index a735099f3f..7dd508ecf0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveBooleanInterfaces.mls @@ -51,6 +51,15 @@ type First = Second type Second = Third type Third = First //│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.50: type First = Second +//│ ║ ^^^^^^ +//│ ╟── Unguarded recursive type alias 'First'. +//│ ║ l.52: type Third = First +//│ ║ ^^^^^ +//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. +//│ ║ l.50: type First = Second +//│ ╙── ^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in member reference //│ ║ l.51: type Second = Third //│ ║ ^^^^^ //│ ╟── Unguarded recursive type alias 'Second'. @@ -68,15 +77,6 @@ type Third = First //│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. //│ ║ l.52: type Third = First //│ ╙── ^^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in member reference -//│ ║ l.50: type First = Second -//│ ║ ^^^^^^ -//│ ╟── Unguarded recursive type alias 'First'. -//│ ║ l.52: type Third = First -//│ ║ ^^^^^ -//│ ╟── Recursive references must pass through a record, tuple, function, or nominal type. -//│ ║ l.50: type First = Second -//│ ╙── ^^^^^ // A guarded branch does not guard a separate recursive alternative. :e diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveEquality.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveEquality.mls index 5af979b9d8..e902800d78 100644 --- a/hkmc2/shared/src/test/mlscript/newres/RecursiveEquality.mls +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveEquality.mls @@ -3,8 +3,6 @@ // Keep the unannotated deep-equality implementation separate from Predef's // public Any × Any -> Bool interface. Recursing on array elements must not // cause unbounded inference or a stack overflow. -// FIXME: Recursive inference through Array.every currently overflows the stack. -:fixme :expect [true, false, true, false] fun deepEqual(a, b) = if a === b then true @@ -15,7 +13,15 @@ fun deepEqual(a, b) = if ac !== undefined and ac === b!constructor and let md = ac!(Predef.Symbols.definitionMetadata) md !== undefined and - md.2.every(field => field !== null and deepEqual(a!(field), b!(field))) + md.2.every((field, ...) => field !== null and deepEqual(a!(field), b!(field))) class Box(val value: Int) [deepEqual([[1], [2]], [[1], [2]]), deepEqual([[1]], [[2]]), deepEqual(Box(3), Box(3)), deepEqual(Box(3), Box(4))] -//│ /!!!\ Uncaught error: java.lang.StackOverflowError +//│ = [true, false, true, false] + +// Refinement must also reach a fixed point through mutually recursive helpers. +// Exposure supplies unknown inputs independently of these concrete local calls. +:expect true +fun visit(value) = if value is Array as items then visitArray(items) else true +fun visitArray(items: Array) = items.every((item, ...) => visit(item)) +visit([1, [2, [3]], []]) +//│ = true diff --git a/hkmc2/shared/src/test/mlscript/newres/ScopePaths.mls b/hkmc2/shared/src/test/mlscript/newres/ScopePaths.mls new file mode 100644 index 0000000000..c197db069a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ScopePaths.mls @@ -0,0 +1,72 @@ +:. + +// The same generic method site sees both a locally created receiver and one +// captured from outside the function. Its parameter hosts have one endpoint. +:expect [2, 1, 3] +class Item(val value: Int) +let outside = [Item(1)] +private fun run(x, local) = + let inside = [x] + let values = if local then inside else outside + values.map((item, ...) => item) +[run(Item(2), true).0.value, run(Item(3), false).0.value, run(Item(3), true).0.value] +//│ = [2, 1, 3] +//│ outside = [Item(1)] + +// A local module's type path includes both the method and the enclosing class. +:expect [4, 5] +class First(val first: Int) +class Second(val second: Int) +class Factory[A](val item: A) with + module Types with + class Box(val value: A) + type Alias = Box + fun read() = + fun make(): Types.Alias = Types.Box(item) + fun use(box: Types.Alias) = box.value + use(make()) +[Factory(First(4)).read().first, Factory(Second(5)).read().second] +//│ = [4, 5] + +// A receiver can escape the generic function that declared its local class. +:expect [6, 7] +class First(val first: Int) +class Second(val second: Int) +private fun make[A](item: A) = + module Types with + class Box(val value: A) + type Alias = Box + fun get(): Types.Alias = Types.Box(item) + get +let first = make(First(6)) +let second = make(Second(7)) +[first().value.first, second().value.second] +//│ = [6, 7] +//│ first = fun get +//│ second = fun get + +// Different outer activations must not supply one another's member shapes. +:e +class First(val first: Int) +class Second(val second: Int) +private fun make[A](item: A) = + module Types with + class Box(val value: A) + type Alias = Box + fun get(): Types.Alias = Types.Box(item) + get() +make(Second(8)).value.second +make(First(9)).value.second +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.59: make(First(9)).value.second +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.59: make(First(9)).value.second +//│ ╙── ^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.59: make(First(9)).value.second +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Instance of class 'First' does not contain member 'second' +//│ ║ l.59: make(First(9)).value.second +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From edf802ab2140d1bef785891c7870e6119e56fcb1 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 2 Oct 2026 08:48:30 +0800 Subject: [PATCH 171/228] Minor --- doc/new-resolution-suite-migration.md | 2 +- hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala | 6 ------ hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls | 2 -- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index b418e5d3c0..27d84887e0 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -63,7 +63,7 @@ They count language directives in source files, not test-runner test cases. behavior, or rewrite a callable-constructor test to avoid the feature it tests. Review successful values and logs as well as failure markers: unsupported contextual calls can return function values without failing the test runner. -- Open operators explicitly when overriding builtins, as in `codegen/ImportedOps` +- Open operators explicitly when shadowing builtins, as in `codegen/ImportedOps` and `ucs/examples/ListFold`. - Use dynamic selection for reflective host access whose effects are not described by the static interface. For example, `ups/examples/DoubleTripleList` uses diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index f1653809df..7736c433ff 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -130,8 +130,6 @@ sealed trait ResolvableImpl extends ShapeHost, PossiblyErroneous: import Resolvable.CallableDefinition - - // private[semantics] val shapes: MutSet[Shape] = MutSet.empty def getShapes: Ls[ShapeEvent] = shapes.toList /** @@ -334,10 +332,6 @@ type AnyRef_ = AnyRefImpl & Term sealed trait AnyRefImpl: self: Term.Ref | Term.SimpleRef | Term.MemberRef | Term.SelfRef => def tree: Tree.Ident - // val refNum: Int - // val typ: Opt[Type] - // val sym: Symbol - // val resSym: FlowSymbol def sym: Symbol sealed trait NewRefImpl extends AnyRefImpl: diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 81aa64df99..362af7643f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -132,8 +132,6 @@ let methods = new GenericLibrary.Methods //│ = [7, 8] //│ methods = Methods -// FIXME: A callback reading a constructor field in a module initializer still -// crosses incompatible lexical capture paths, before any consumer imports it. :expect [5] module Local with class Known(val known: Int) From c71d6fa3d1ec573ccde8d069884b8e7f71874fd4 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 09:34:37 +0800 Subject: [PATCH 172/228] Migrate remaining unblocked compilation fixtures to new resolution --- doc/new-resolution-suite-migration.md | 35 ++- .../test/mlscript-compile/LazyFingerTree.mls | 43 ++-- .../src/test/mlscript-compile/Rendering.mls | 83 +++--- .../src/test/mlscript-compile/Runtime.mjs | 241 +++++++++++------- .../src/test/mlscript-compile/Runtime.mls | 226 ++++++++-------- .../shared/src/test/mlscript-compile/Term.mls | 50 ++-- .../apps/parsing/Extension.mls | 5 +- .../apps/parsing/ParseRule.mls | 9 +- .../mlscript/apps/parsing/ParseRuleTest.mls | 32 +++ .../src/test/mlscript/decls/Prelude.mls | 34 ++- .../src/test/mlscript/handlers/Effects.mls | 4 +- .../src/test/mlscript/newres/Arrays.mls | 24 +- .../test/mlscript/newres/GenericMethods.mls | 2 +- .../newres/MigratedFixtureInterfaces.mls | 72 ++++++ .../newres/PatternDefinitionBindings.mls | 2 +- .../test/mlscript/tailrec/MultiArgLists.mls | 12 +- .../src/test/mlscript/tailrec/TailRecOpt.mls | 4 +- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 18 files changed, 544 insertions(+), 338 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/apps/parsing/ParseRuleTest.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/MigratedFixtureInterfaces.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 27d84887e0..832595eaf2 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -13,7 +13,7 @@ resolver invariants and regression coverage live in the [resolver design notes](new-resolution-design.md). Backend-specific constraints are in [WASM resolution](new-resolution-wasm.md). -The checked-in worksheet headers give the following status as of 2026-10-01. +The checked-in worksheet headers give the following status as of 2026-10-02. Counts exclude shared `.mls` configurations, new-resolution regression tests, and the 51 files under `ucs/staging`, which the test runner excludes. @@ -23,21 +23,21 @@ Counts exclude shared `.mls` configurations, new-resolution regression tests, an | codegen | 93 | 33 | 126 | | ucs | 69 | 1 | 70 | | ups | 58 | 13 | 71 | -| apps | 15 | 1 | 16 | -| Total | 302 | 71 | 373 | +| apps | 16 | 1 | 17 | +| Total | 303 | 71 | 374 | | wasm (separate backend) | 22 | 1 | 23 | -All twelve `apps/parsing` worksheets use new resolution, but some implementation +All thirteen `apps/parsing` worksheets use new resolution, but some implementation modules they import still use legacy resolution. Worksheet migration does not imply that its dependencies have been migrated. | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | -| Main (including quotes, UPS, and regression fixtures) | 42 | 7 | 49 | -| Applications | 17 | 3 | 20 | +| Main (including quotes, UPS, and regression fixtures) | 45 | 4 | 49 | +| Applications | 18 | 2 | 20 | | Nofib | 37 | 2 | 39 | | WASM | 1 | 0 | 1 | -| Total | 97 | 12 | 109 | +| Total | 101 | 8 | 109 | These totals include the new-resolution `NamedFieldLibrary` regression fixture and the deliberately legacy `LegacyGenericLibrary` mixed-mode regression fixture. @@ -86,14 +86,11 @@ resolver. The remaining fixtures need the following work: | Legacy fixtures | Concrete work and known failures | | --- | --- | -| `LazyFingerTree` | Supply indexable interfaces and check its consumers. Imported generic method results are covered by the passing `newres/LegacyGenericMethods.mls`, including `FingerTreeList.mk(42).at(0)`. | | `FingerTreeList` | Enabling new resolution exceeds the compilation test's time limit. Diagnose inference growth around recursive trees and tuple spreads before calling this an annotation-only port. | -| `Runtime`, `Rendering` | Audit callable and host interfaces. No separate design blocker is established for their missing annotations. | | `CSP`, `QuoteExample1` | Quasiquote type selections and wildcard-reference lowering. | | `parsing/Lexer` | Calls to token constructors with trailing `using` parameters need automatic contextual argument insertion; `newres/LexerMigration.mls` records the missing behavior. | -| `parsing/ParseRule` | Correct `andThen`'s helper signature: it transforms a rule's result into a pair with the following rule's result, rather than preserving `B`. Recheck migration with lexical type captures preserved in legacy signatures. | -| `parsing/Extension` | Give rules their `ParseRule[Tree]` interfaces and validate calls to `ParseRule.andThen` together with that dependency's migration. | -| `nofib/lastpiece`, `nofib/sorting` | Both exceed the compilation time limit under new resolution. `sorting` first needs `int_of_char(c: Str)`; adding it reveals the timeout. Diagnose inference growth before retrying these ports. | +| `parsing/ParseRule` | The `andThen` helper now pairs its own result type with the following rule's result; `map` and `andThen` expose their result interfaces. Enabling new resolution still exceeds the 25-second compilation limit when compiling its consumers, even with cache-result annotations or with the recursive helper lifted out of the method. Keep the implementation on legacy resolution pending an inference-growth fix. | +| `nofib/lastpiece`, `nofib/sorting` | Both still exceed the 25-second compilation time limit under new resolution (retried 2026-10-02). `sorting` first needs `int_of_char(c: Str)`; adding it reveals the timeout. Diagnose inference growth before retrying these ports. | Array callbacks must accept the arguments supplied by the JS API: three for `map`/`filter`/`forEach` and four for `reduce`. Use `pass1(f)` or `pass2(f)` when @@ -108,6 +105,7 @@ To reproduce a blocker, temporarily add the language directive to the named compilation fixture and run `ctest ` or `catest ` as appropriate. The blocked fixtures retain their existing resolution mode. +`Runtime`, `Rendering`, `LazyFingerTree`, `Term`, `Extension`, `Predef`, `Block`, `Shape`, `LazyArray`, `Accounting`, `CSV`, `Parser`, `ParseRuleVisualizer`, and the parser web demo use new resolution. Nullable host results use pattern matching where possible; mutable traversal arrays use `mut`. @@ -118,6 +116,19 @@ The parser unwraps optional continuation rules and resumes module parsing after leading separators. The virtual `source` fields expand in both resolution modes (`newres/SourceLocations`); constructor metadata is covered by `newres/ShapeFixture`. +The runtime describes packed continuation arrays and nullable frame links explicitly; +casts decode the packed slots and initialized tail links. Tuple helpers use a structural +indexable interface so lazy views remain supported without nominal array casts. +Raw indexing preserves undefined elements and existing tuple fusion. Rendering uses typed callback +adapters and host collection interfaces, with dynamic access confined to reflective +properties. `newres/MigratedFixtureInterfaces` covers imported result interfaces and +lazy materialization. `Term` exposes typed printer interfaces and converts nullable +literals with `String`; the same regression checks literal printing and fresh names. +`apps/parsing/ParseRuleTest` checks heterogeneous sequencing. +`Extension` constructs `Choice[Stack[Tree]]` before mapping the accumulated trees to +an application expression. Existing rendering, handler, and parser consumers retain +their results. + The web demo compiles, but serving its checked-in HTML directly still encounters Node-only imports (`fs`, `process`, `path`, and `url`) through `Predef` and `Term`. Browser packaging must separate that dependency before direct startup works. diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls index 32b73bfa5e..5ea82b2942 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls @@ -1,7 +1,10 @@ +#lang(0.3.x) import "./Iter.mls" +import "./LazyArray.mls" import "./FingerTreeList.mls" open Iter +open LazyArray { Indexable } open LazyFingerTree open FingerTreeList @@ -9,7 +12,7 @@ fun normIdx(i, len) = if i < 0 then len + i else i -fun concatHelper(a, l, ...args) = +fun concatHelper(a: Array[Any], l: Int, ...args: Array[Indexable]) = let idx = 0 let init_len = a.length set a.length = init_len + args.length * 2 @@ -30,7 +33,7 @@ fun normIdxSlice(i, len) = else 0 else i -fun sliceHelper(beg, fin, arr) = +fun sliceHelper(beg: Int, fin: Int, arr: Indexable) = let len = arr.length set beg = normIdxSlice(beg, len) set fin = normIdxSlice(fin, len) @@ -53,9 +56,9 @@ fun sliceHelper(beg, fin, arr) = class LazyFT extends IterableBase with fun toString() = "[" + this joined(", ") + "]" - fun concat(...args) = concatHelper(mut [SpliceMarker, this], this.length, ...args) + fun concat(...args: Array[Indexable]) = concatHelper(mut [SpliceMarker, this], this.length, ...args) -class View(val underlying, val start, val end) extends LazyFT with +class View(val underlying: Indexable, val start: Int, val end: Int) extends LazyFT with fun iterator() = let idx = start @@ -67,7 +70,7 @@ class View(val underlying, val start, val end) extends LazyFT with Result.Next of next else Result.Done - fun materialize() = underlying.slice(start, underlying.length - end) + fun materialize(): Indexable = underlying.slice(start, underlying.length - end) fun at(i) = if i < -length || i >= length do @@ -80,22 +83,23 @@ class View(val underlying, val start, val end) extends LazyFT with fun toString() = "[" + this joined(", ") + "]" object SpliceMarker -class Splice(val bits, val len) extends LazyFT with +class Splice(val bits: Array[Any], val len: Int) extends LazyFT with let materialized = null fun iterator() = materialize().iterator() fun reify = let acc = FingerTreeList.mk() - let stack = mut [] + val stack: Array[[Int, Indexable]] = mut [] stack.push([0, bits]) while stack.length > 0 do - let e = stack.pop() - let idx = e.[0] - let vals = e.[1] + // The loop condition excludes the undefined result of popping an empty stack. + let e = stack.pop() as [Int, Indexable] + let idx = e.0 + let vals = e.1 - if vals is Splice do - set vals = vals.bits + if vals is Splice as splice do + set vals = splice.bits while (idx < vals.length and (vals.at(idx) is SpliceMarker) is false) do @@ -107,10 +111,11 @@ class Splice(val bits, val len) extends LazyFT with let next = vals.at(idx + 1) if FingerTreeList.isFingerTree(next) then set acc = acc ++ next - else stack.push([0, next]) + // SpliceMarker is always followed by an indexable sequence. + else stack.push([0, next as Indexable]) acc - fun materialize() = + fun materialize(): {length: Int, at: Int -> Any, slice: Function, iterator: () -> Iterator[Any]} = if materialized == null do set materialized = reify materialized @@ -118,7 +123,7 @@ class Splice(val bits, val len) extends LazyFT with fun at(i) = materialize().at(i) // called on eager spreads in matches fun slice(beg, fin) = sliceHelper(beg, fin, this) - fun concat(...args) = + fun concat(...args: Array[Indexable]) = if materialized === null then concatHelper(mut [SpliceMarker, this], len, ...args) else concatHelper(mut [SpliceMarker, materialized], len, ...args) @@ -131,15 +136,15 @@ module LazyFingerTree with... fun mk(...args) = View of FingerTreeList.mk(...args), 0, 0 -fun concat(...args) = concatHelper(mut [], 0, ...args) -fun dropLeftRight(beg, fin)(xs) = +fun concat(...args: Array[Indexable]) = concatHelper(mut [], 0, ...args) +fun dropLeftRight(beg: Int, fin: Int)(xs: Indexable) = if beg < 0 || fin < 0 do throw RangeError("LazyFingerTree.dropLeftRight: indices must be non-negative") if beg > xs.length || fin > xs.length do throw RangeError("LazyFingerTree.dropLeftRight: indices out of bounds") sliceHelper(beg, xs.length - fin, xs) -fun equals(xs, ys) = +fun equals(xs: Indexable, ys: Indexable) = if xs is [x, ...xs] then if ys is @@ -155,7 +160,7 @@ fun __concat(...args) = let idx = 0 while (idx < args.length) do if args.[idx] is SpliceMarker then - set len += args.[idx + 1].length + set len += (args.[idx + 1] as Indexable).length set idx += 2 else set len += 1 diff --git a/hkmc2/shared/src/test/mlscript-compile/Rendering.mls b/hkmc2/shared/src/test/mlscript-compile/Rendering.mls index 122d682765..4aaa0ef503 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Rendering.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Rendering.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) import "./RuntimeJS.mjs" @@ -7,15 +8,15 @@ module Rendering with ... let definitionMetadataSymbol = RuntimeJS.symbols.definitionMetadata let prettyPrintSymbol = RuntimeJS.symbols.prettyPrint -fun pass1(f)(...xs) = f(xs.0) -fun pass2(f)(...xs) = f(xs.0, xs.1) -fun pass3(f)(...xs) = f(xs.0, xs.1, xs.2) +fun pass1[A, B](f: A -> B)(...xs): B = f(xs.0) +fun pass2[A, B, C](f: (A, B) -> C)(...xs): C = f(xs.0, xs.1) +fun pass3[A, B, C, D](f: (A, B, C) -> D)(...xs): D = f(xs.0, xs.1, xs.2) -fun passing(f, ...args) = f.bind(null, ...args) +fun passing(f: Function, ...args) = f.bind(null, ...args) -fun map(f)(...xs) = xs.map(pass1(f)) +fun map[A, B](f: A -> B)(...xs: Array[A]): Array[B] = xs.map(pass1(f)) -fun fold(f)(init, ...rest) = +fun fold[A, B](f: (A, B) -> A)(init: A, ...rest: Array[B]): A = let i = 0 len = rest.length @@ -84,7 +85,7 @@ private fun symbolsForMap(n: Int) = start: headline + " {", end: "}", entrySeparator: ",", keyValueSeparator: " => ", empty: headline + " {}", padding: true -fun render(target, ...args) = +fun render(target, ...args): Str = let options = if args is [options, ...] then options else emptyOptions indent = if @@ -110,7 +111,7 @@ fun render(target, ...args) = // Add indentations to results of `toString` from other objects. fun indentText(text: Str, currentLevel: Int): Str = if indent is null then text else... - let indents = indent.repeat(currentLevel + 1) + let indents = (indent as Str).repeat(currentLevel + 1) text .split("\n") .map of (line, index, lines) => @@ -118,7 +119,7 @@ fun render(target, ...args) = let prefix = if index === 0 then "" else indents prefix + line + postfix .join("\n") - fun renderArray(array, level, keyLength, startPos) = + fun renderArray(array: Array[Any], level, keyLength, startPos) = let i = 0 length = array.length @@ -126,7 +127,7 @@ fun render(target, ...args) = // The length of indentations that should be prepended to items in this // array if this array is displayed in multi-lines. itemIndentationLength = - if indent is null then 0 else (level + 1) * indent.length + if indent is Str as indent then (level + 1) * indent.length else 0 // Return `true` if the array has no remaining elements. let done() = let isDone = null @@ -153,14 +154,14 @@ fun render(target, ...args) = valueStr else throw new Error("All items in this array has been rendered.") renderSequence of done, next, level, keyLength, startPos, symbolsForArray - fun renderSet(theSet, level, keyLength, startPos) = + fun renderSet(theSet: Set[Any], level, keyLength, startPos) = let - iterator = theSet.(Symbol.iterator)() + iterator = theSet.values() peek = iterator.next() // The length of indentations that should be prepended to items in this // array if this array is displayed in multi-lines. itemIndentationLength = - if indent is null then 0 else (level + 1) * indent.length + if indent is Str as indent then (level + 1) * indent.length else 0 // Return `true` if the array has no remaining elements. let done() = peek.done let next(isFirst: Bool, prefixLength: Int) = @@ -176,11 +177,11 @@ fun render(target, ...args) = renderSequence of done, next, level, keyLength, startPos, symbolsForSet(theSet.size) // Render instances of MLscript classes. - fun renderClassInstance(constructorName, fieldNames, instance, level, keyLength, startPos) = + fun renderClassInstance(constructorName: Str, fieldNames: Array[Str | null], instance: Any, level, keyLength, startPos) = let length = fieldNames.length i = 0 - itemIndentationLength = if indent is null then 0 else (level + 1) * indent.length + itemIndentationLength = if indent is Str as indent then (level + 1) * indent.length else 0 let done() = i >= length let next(isFirst: Bool, prefixLength: Int) = if fieldNames.[i] is do set i += 1 @@ -193,7 +194,7 @@ fun render(target, ...args) = then itemIndentationLength else prefixLengthIfSameLine renderValue(value, level + 1, 0, valueStartLength) renderSequence of done, next, level, keyLength, startPos, symbolsForClass(constructorName) - fun renderSequence(done, next, level: Int, keyLength: Int, startPos: Int, symbols) = + fun renderSequence(done: () -> Bool, next: (Bool, Int) -> Str, level: Int, keyLength: Int, startPos: Int, symbols: {start: Str, end: Str, separator: Str, empty: Str, padding: Bool}) = let items = mut [] hasMultiline = false @@ -201,8 +202,8 @@ fun render(target, ...args) = // brackets and spaces around items are excluded from the length. singleLineLength = startPos + symbols.start.length + (if symbols.padding then padding.length else 0) - parentIndent = if indent is null then null else indent.repeat(level) - itemIdent = if indent is null then null else indent.repeat(level + 1) + parentIndent = if indent is Str as indent then indent.repeat(level) else "" + itemIdent = if indent is Str as indent then indent.repeat(level + 1) else "" // Iterate through and render all items. while done() is false do let item = next(items.length is 0, singleLineLength) @@ -239,9 +240,9 @@ fun render(target, ...args) = length = entries.length i = 0 itemIndentationLength = - if indent is null then 0 else (level + 1) * indent.length + if indent is Str as indent then (level + 1) * indent.length else 0 let skipNonEnumerable() = - while i < length and entries.[i].1.enumerable is false do set i += 1 + while i < length and entries.[i].1!enumerable is false do set i += 1 let done() = i >= length let next(isFirst: Bool, prefixLength: Int) = let @@ -270,14 +271,14 @@ fun render(target, ...args) = skipNonEnumerable() renderRecordLike of done, next, level, keyLength, startPos, symbolsForObject, isRefinement - fun renderMap(map, level, keyLength, startPos) = + fun renderMap(map: Map[Any, Any], level, keyLength, startPos) = let - iterator = map.(Symbol.iterator)() + iterator = map.entries() peek = iterator.next() // The length of indentations that should be prepended to items in this // array if this array is displayed in multi-lines. itemIndentationLength = - if indent is null then 0 else (level + 1) * indent.length + if indent is Str as indent then (level + 1) * indent.length else 0 // Return `true` if the array has no remaining elements. let done() = peek.done let next(isFirst: Bool, prefixLength: Int) = @@ -301,7 +302,7 @@ fun render(target, ...args) = [keyStr, valueStr] renderRecordLike of done, next, level, keyLength, startPos, symbolsForMap(map.size), false - fun renderRecordLike(done, next, level, keyLength, startPos, symbols, isRefinement) = + fun renderRecordLike(done: () -> Bool, next: (Bool, Int) -> [Str, Str], level, keyLength, startPos, symbols: {start: Str, end: Str, entrySeparator: Str, keyValueSeparator: Str, empty: Str, padding: Bool}, isRefinement) = let startPadding = if isRefinement then " " @@ -314,8 +315,8 @@ fun render(target, ...args) = items = mut [] hasMultiline = false singleLineLength = startPos + symbols.start.length + startPadding.length - parentIndent = if indent is null then null else indent.repeat(level) - itemIdent = if indent is null then null else indent.repeat(level + 1) + parentIndent = if indent is Str as indent then indent.repeat(level) else "" + itemIdent = if indent is Str as indent then indent.repeat(level + 1) else "" while done() is false do let keyValue = next(items.length is 0, singleLineLength) @@ -347,22 +348,22 @@ fun render(target, ...args) = let lengthIfBreakParent = parentIndent.length + (singleLineLength - startPos + keyLength) lengthIfBreakParent <= breakLength then layoutSingleLine() else layoutMultiLine() - fun renderValue(arg, level, keyLength, startPos) = if + fun renderValue(arg: Any, level: Int, keyLength: Int, startPos: Int): Str = if arg is // Primitive values undefined then "undefined" null then "null" Str then JSON.stringify(arg) - BigInt then arg.toString() + "n" - Symbol and + BigInt as arg then arg.toString() + "n" + Symbol as arg and arg.description is undefined then "Symbol()" else "Symbol(\"" + arg.description + "\")" - Num then arg.toString() - RegExp then arg.toString() + Num as arg then arg.toString() + RegExp as arg then arg.toString() true then "true" false then "false" arg === () then "()" // Reuse the cached result if the display mode is inline. - visitedObjects.has(arg) and indent is null then visitedObjects.get(arg) + indent is null and visitedObjects.get(arg) is Str as cached then cached visitingObjects.has(arg) and visitingObjects.get(arg) is // We have already found this circular reference before Int as index then "ref'" + index @@ -383,15 +384,15 @@ fun render(target, ...args) = Map then renderMap of arg, level, keyLength, startPos WeakSet then "WeakSet { }" WeakMap then "WeakMap { }" - Error then arg.name + ": " + arg.message - Function then + Error as arg then arg.name + ": " + arg.message + Function as arg then let desc = Object.getOwnPropertyDescriptor(arg, "prototype") let head = if // ES 2015 classes: `desc.writable` is `false`. - desc is Object and desc.writable is false and + desc is Object as desc and desc!writable is false and // If it is from MLscript, use the kind written in metadata. - desc.value.constructor.hasOwnProperty(definitionMetadataSymbol) and - desc.value.constructor.(definitionMetadataSymbol) is [kind, name, ...] then + desc!value!constructor!hasOwnProperty(definitionMetadataSymbol) and + desc!value!constructor!(definitionMetadataSymbol) is [kind, name, ...] then kind + " " + name arg.name is "" then "class" // Show "class" if there is no name. else "class " + arg.name // Or, we can use the name. @@ -426,19 +427,19 @@ fun render(target, ...args) = // patterns, or parameter-less classes), use the constructor name. definitionMetadata is [kind, name] then // For class instances and objects, do not show the kind. - let head = if kind is + let head = (if kind is "class" | "object" then name - else kind + " " + name + else kind + " " + name) as Str let body = renderObject of arg, level, keyLength, startPos + head.length, true head + (if body is "{}" then "" else " " + body) // Otherwise, we print the name of the constructor and the properties. - let head = proto.constructor.name + " " + let head = (proto.constructor.name + " ") as Str else head + renderObject of arg, level, keyLength, startPos + head.length, true // Otherwise, the prototype is not a function. // If the prototype has its own implementation of `toString`, call it. - proto.hasOwnProperty("toString") then arg.toString() indentText(level) + proto.hasOwnProperty("toString") then arg!toString() indentText(level) // Otherwise, treat the input as a normal object. else renderObject(arg, level, keyLength, startPos, false) // We have finished visiting this object. diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index bb730e48b8..c82819783e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -5,7 +5,7 @@ import RuntimeJS from "./RuntimeJS.mjs"; import Rendering from "./Rendering.mjs"; import LazyArray from "./LazyArray.mjs"; import Iter from "./Iter.mjs"; -let continuation, Runtime1, lambda, lambda1, lambda2, lambda3, lambda4, lambda5, lambda$, lambda$1, Capture$scope301, lambda$2, Capture$scope321, lambda$3, lambda$4, continuation$; +let continuation, Runtime1, lambda, lambda1, lambda2, lambda3, lambda4, lambda5, lambda6, lambda$, lambda$1, Capture$scope341, lambda$2, Capture$scope361, lambda$3, lambda$4, continuation$; continuation$ = function continuation$(Runtime2, resume) { return (value) => { return continuation(Runtime2, resume, value) @@ -28,64 +28,70 @@ lambda$4 = (undefined, function (Runtime2, promise) { return runtime.safeCall(promise.then(continuation$here)) } }); -lambda3 = (undefined, function (Runtime2, promise, resume) { +lambda4 = (undefined, function (Runtime2, promise, resume) { let continuation$here; continuation$here = continuation$(Runtime2, resume); return runtime.safeCall(promise.then(continuation$here)) }); -(class Capture$scope32 { +(class Capture$scope36 { static { - Capture$scope321 = this + Capture$scope361 = this } constructor(result$0) { this.result$0 = result$0; } toString() { return runtime.render(this); } - static [definitionMetadata] = ["class", "Capture$scope32"]; + static [definitionMetadata] = ["class", "Capture$scope36"]; }); -lambda$3 = (undefined, function (scope32$cap, cont) { - return (m, marker) => { - return lambda2(scope32$cap, cont, m, marker) +lambda$3 = (undefined, function (scope36$cap, cont) { + return (m, marker, ..._) => { + return lambda3(scope36$cap, cont, m, marker, ..._) } }); -lambda2 = (undefined, function (scope32$cap, cont, m, marker) { +lambda3 = (undefined, function (scope36$cap, cont, m, marker, ..._) { let scrut, tmp, tmp1; scrut = runtime.safeCall(m.has(cont)); if (scrut === true) { tmp = ", " + marker; - tmp1 = scope32$cap.result$0 + tmp; - scope32$cap.result$0 = tmp1; + tmp1 = scope36$cap.result$0 + tmp; + scope36$cap.result$0 = tmp1; return runtime.Unit } return runtime.Unit; }); -(class Capture$scope30 { +(class Capture$scope34 { static { - Capture$scope301 = this + Capture$scope341 = this } constructor(result$0) { this.result$0 = result$0; } toString() { return runtime.render(this); } - static [definitionMetadata] = ["class", "Capture$scope30"]; + static [definitionMetadata] = ["class", "Capture$scope34"]; }); -lambda$2 = (undefined, function (scope30$cap, cont) { - return (m, marker) => { - return lambda1(scope30$cap, cont, m, marker) +lambda$2 = (undefined, function (scope34$cap, cont) { + return (m, marker, ..._) => { + return lambda2(scope34$cap, cont, m, marker, ..._) } }); -lambda1 = (undefined, function (scope30$cap, cont, m, marker) { +lambda2 = (undefined, function (scope34$cap, cont, m, marker, ..._) { let scrut, tmp, tmp1; scrut = runtime.safeCall(m.has(cont)); if (scrut === true) { tmp = ", " + marker; - tmp1 = scope30$cap.result$0 + tmp; - scope30$cap.result$0 = tmp1; + tmp1 = scope34$cap.result$0 + tmp; + scope34$cap.result$0 = tmp1; return runtime.Unit } return runtime.Unit; }); -lambda = (undefined, function (l) { +lambda1 = (undefined, function (l, ..._) { + let tmp, tmp1; + tmp = l.localName + "="; + tmp1 = runtime.safeCall(Rendering.render(l.value)); + return tmp + tmp1 +}); +lambda = (undefined, function (l, ..._) { let tmp, tmp1; tmp = l.localName + "="; tmp1 = runtime.safeCall(Rendering.render(l.value)); @@ -97,7 +103,7 @@ lambda$1 = (undefined, function (Runtime2) { return runtime.Unit } }); -lambda5 = (undefined, function (Runtime2, k) { +lambda6 = (undefined, function (Runtime2, k) { Runtime2.stackResume = k; return runtime.Unit }); @@ -106,7 +112,7 @@ lambda$ = (undefined, function (Runtime2, EffectHandle1, value) { return Runtime2.resume(EffectHandle1.reified.contTrace)(value) } }); -lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { +lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { return Runtime2.resume(EffectHandle1.reified.contTrace)(value) }); (class Runtime { @@ -206,9 +212,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { } #_reified; resumeWith(value) { - let lambda$here; - lambda$here = lambda$(Runtime, this, value); - return Runtime._try(lambda$here) + return Runtime._try(lambda$(Runtime, this, value)) } raise() { Runtime.curEffect = this.reified; @@ -526,6 +530,12 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { this.lastHandler = lastHandler; this.resumed = resumed; } + get lastFrame() { + return this.last; + } + get lastHandlerFrame() { + return this.lastHandler; + } toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "ContTrace", ["next", "last", "nextHandler", "lastHandler", "resumed"]]; }); @@ -746,13 +756,12 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { throw runtime.safeCall(globalThis.Error(tmp3)) } static _try(f) { - let res, scrut, tmp; + let res, scrut; res = runtime.safeCall(f()); - scrut = Runtime.curEffect !== null; - if (scrut === true) { - tmp = Runtime.curEffect; + scrut = Runtime.curEffect; + if (scrut instanceof Runtime.EffectSig.class) { Runtime.curEffect = null; - return Runtime.EffectHandle(tmp) + return Runtime.EffectHandle(scrut) } return res; } @@ -795,7 +804,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { } if (tr instanceof Runtime.EffectSig.class) { Runtime.curEffect = null; - tmp = "Error: Unhandled effect " + tr.handler.constructor.name; + tmp = "Error: Unhandled effect " + tr.handler["constructor"]["name"]; tmp1 = Runtime.showStackTrace(tmp, tr, debug, false); throw Runtime.CustomStackError(tmp1) } @@ -805,7 +814,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { let tmp, tmp1, tmp2, tmp3, tmp4; tmp = Runtime.curEffect; Runtime.curEffect = null; - tmp1 = "Error: Effect " + tmp.handler.constructor.name; + tmp1 = "Error: Effect " + tmp.handler["constructor"]["name"]; tmp2 = tmp1 + " is raised "; tmp3 = tmp2 + position; tmp4 = Runtime.showStackTrace(tmp3, tmp, false, false); @@ -818,35 +827,33 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { atTail = true; if (debug === true) { lbl: while (true) { - let scrut, cur, scrut1, tmp, tmp1; - scrut = curHandler !== null; - if (scrut === true) { + let cur, tmp, tmp1, tmp2, tmp3; + if (curHandler instanceof Runtime.ContTrace.class) { cur = curHandler.next; lbl1: while (true) { - let scrut2, curLocals, loc, scrut3, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10; - scrut2 = cur !== null; - if (scrut2 === true) { + let curLocals, loc, scrut, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12; + if (cur instanceof Runtime.FunctionContFrame.class) { curLocals = cur.getLocals; loc = cur.getLoc; if (showLocals === true) { - scrut3 = curLocals.length > 0; - if (scrut3 === true) { - tmp2 = runtime.safeCall(curLocals.map(lambda)); - tmp3 = runtime.safeCall(tmp2.join(", ")); - tmp4 = " with locals: " + tmp3; + scrut = curLocals.length > 0; + if (scrut === true) { + tmp4 = runtime.safeCall(curLocals.map(lambda)); + tmp5 = runtime.safeCall(tmp4.join(", ")); + tmp6 = " with locals: " + tmp5; } else { - tmp4 = ""; + tmp6 = ""; } } else { - tmp4 = ""; + tmp6 = ""; } - tmp5 = "\n\tat " + cur.getNme; - tmp6 = tmp5 + " ("; - tmp7 = tmp6 + loc; - tmp8 = tmp7 + ")"; - tmp9 = msg + tmp8; - tmp10 = tmp9 + tmp4; - msg = tmp10; + tmp7 = "\n\tat " + cur.getNme; + tmp8 = tmp7 + " ("; + tmp9 = tmp8 + loc; + tmp10 = tmp9 + ")"; + tmp11 = msg + tmp10; + tmp12 = tmp11 + tmp6; + msg = tmp12; cur = cur.next; atTail = false; continue lbl1 @@ -854,15 +861,55 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { break; } curHandler = curHandler.nextHandler; - scrut1 = curHandler !== null; - if (scrut1 === true) { - tmp = "\n\twith handler " + curHandler.handler.constructor.name; + if (curHandler instanceof Runtime.HandlerContFrame.class) { + tmp = "\n\twith handler " + curHandler.handler["constructor"]["name"]; tmp1 = msg + tmp; msg = tmp1; atTail = false; continue lbl } continue lbl; + } else if (curHandler instanceof Runtime.HandlerContFrame.class) { + cur = curHandler.next; + lbl2: while (true) { + let curLocals, loc, scrut, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12; + if (cur instanceof Runtime.FunctionContFrame.class) { + curLocals = cur.getLocals; + loc = cur.getLoc; + if (showLocals === true) { + scrut = curLocals.length > 0; + if (scrut === true) { + tmp4 = runtime.safeCall(curLocals.map(lambda1)); + tmp5 = runtime.safeCall(tmp4.join(", ")); + tmp6 = " with locals: " + tmp5; + } else { + tmp6 = ""; + } + } else { + tmp6 = ""; + } + tmp7 = "\n\tat " + cur.getNme; + tmp8 = tmp7 + " ("; + tmp9 = tmp8 + loc; + tmp10 = tmp9 + ")"; + tmp11 = msg + tmp10; + tmp12 = tmp11 + tmp6; + msg = tmp12; + cur = cur.next; + atTail = false; + continue lbl2 + } + break; + } + curHandler = curHandler.nextHandler; + if (curHandler instanceof Runtime.HandlerContFrame.class) { + tmp2 = "\n\twith handler " + curHandler.handler["constructor"]["name"]; + tmp3 = msg + tmp2; + msg = tmp3; + atTail = false; + continue lbl + } + continue lbl; } break; } @@ -874,27 +921,26 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { return header; } static showFunctionContChain(cont, hl, vis, reps) { - let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, scope30$cap, lambda$here; - scope30$cap = new Capture$scope301(undefined); + let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, scope34$cap; + scope34$cap = new Capture$scope341(undefined); if (cont instanceof Runtime.FunctionContFrame.class) { - tmp = cont.constructor.name + "(pc="; - scope30$cap.result$0 = tmp + cont.saved.at(1); - lambda$here = lambda$2(scope30$cap, cont); - runtime.safeCall(hl.forEach(lambda$here)); + tmp = cont["constructor"]["name"] + "(pc="; + scope34$cap.result$0 = tmp + cont.saved.at(1); + runtime.safeCall(hl.forEach(lambda$2(scope34$cap, cont))); scrut = runtime.safeCall(vis.has(cont)); if (scrut === true) { tmp1 = reps + 1; reps = tmp1; - scrut1 = tmp1 > 10; + scrut1 = reps > 10; if (scrut1 === true) { throw runtime.safeCall(globalThis.Error("10 repeated continuation frame (loop?)")) } - tmp2 = scope30$cap.result$0 + ", REPEAT"; - scope30$cap.result$0 = tmp2; + tmp2 = scope34$cap.result$0 + ", REPEAT"; + scope34$cap.result$0 = tmp2; } else { runtime.safeCall(vis.add(cont)); } - tmp3 = scope30$cap.result$0 + ") -> "; + tmp3 = scope34$cap.result$0 + ") -> "; tmp4 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp3 + tmp4 } @@ -905,26 +951,25 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { return "(NOT CONT)"; } static showHandlerContChain(cont, hl, vis, reps) { - let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, scope32$cap, lambda$here; - scope32$cap = new Capture$scope321(undefined); + let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, scope36$cap; + scope36$cap = new Capture$scope361(undefined); if (cont instanceof Runtime.HandlerContFrame.class) { - scope32$cap.result$0 = cont.handler.constructor.name; - lambda$here = lambda$3(scope32$cap, cont); - runtime.safeCall(hl.forEach(lambda$here)); + scope36$cap.result$0 = cont.handler["constructor"]["name"]; + runtime.safeCall(hl.forEach(lambda$3(scope36$cap, cont))); scrut = runtime.safeCall(vis.has(cont)); if (scrut === true) { tmp = reps + 1; reps = tmp; - scrut1 = tmp > 10; + scrut1 = reps > 10; if (scrut1 === true) { throw runtime.safeCall(globalThis.Error("10 repeated continuation frame (loop?)")) } - tmp1 = scope32$cap.result$0 + ", REPEAT"; - scope32$cap.result$0 = tmp1; + tmp1 = scope36$cap.result$0 + ", REPEAT"; + scope36$cap.result$0 = tmp1; } else { runtime.safeCall(vis.add(cont)); } - tmp2 = scope32$cap.result$0 + " -> "; + tmp2 = scope36$cap.result$0 + " -> "; tmp3 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp2 + tmp3 } @@ -976,9 +1021,8 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { runtime.safeCall(globalThis.console.log(tmp4)); cur = contTrace.nextHandler; lbl: while (true) { - let scrut2, tmp5; - scrut2 = cur !== null; - if (scrut2 === true) { + let tmp5; + if (cur instanceof Runtime.HandlerContFrame.class) { tmp5 = Runtime.showHandlerContChain(cur, hl, vis, 0); runtime.safeCall(globalThis.console.log(tmp5)); cur = cur.nextHandler; @@ -994,7 +1038,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { static debugEff(eff) { if (eff instanceof Runtime.EffectSig.class) { runtime.safeCall(globalThis.console.log("Debug EffectSig:")); - runtime.safeCall(globalThis.console.log("handler: ", eff.handler.constructor.name)); + runtime.safeCall(globalThis.console.log("handler: ", eff.handler["constructor"]["name"])); runtime.safeCall(globalThis.console.log("handlerFun: ", eff.handlerFun)); return Runtime.debugContTrace(eff.contTrace) } @@ -1002,10 +1046,11 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { return runtime.safeCall(globalThis.console.log(eff)); } static unwind(...saved) { - let tmp; + let effect, tmp; + effect = Runtime.curEffect; tmp = new Runtime.FunctionContFrame.class(null, saved); - Runtime.curEffect.contTrace.last.next = tmp; - Runtime.curEffect.contTrace.last = Runtime.curEffect.contTrace.last.next; + effect.contTrace.lastFrame.next = tmp; + effect.contTrace.last = effect.contTrace.lastFrame.next; return runtime.Unit } static mkEffect(handler, handlerFun) { @@ -1020,7 +1065,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { static handleBlockImpl(cur, handler) { let handlerFrame; handlerFrame = new Runtime.HandlerContFrame.class(null, null, handler); - cur.contTrace.lastHandler.nextHandler = handlerFrame; + cur.contTrace.lastHandlerFrame.nextHandler = handlerFrame; cur.contTrace.lastHandler = handlerFrame; cur.contTrace.last = handlerFrame; return Runtime.handleEffects(cur) @@ -1055,11 +1100,11 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { prevHandlerFrame = cur.contTrace; lbl: while (true) { let scrut7, scrut8; - scrut7 = prevHandlerFrame.nextHandler !== null; - if (scrut7 === true) { - scrut8 = prevHandlerFrame.nextHandler.handler !== cur.handler; + scrut7 = prevHandlerFrame.nextHandler; + if (scrut7 instanceof Runtime.HandlerContFrame.class) { + scrut8 = scrut7.handler !== cur.handler; if (scrut8 === true) { - prevHandlerFrame = prevHandlerFrame.nextHandler; + prevHandlerFrame = scrut7; continue lbl } } @@ -1099,7 +1144,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { cur = Runtime.curEffect; scrut4 = saved.next !== null; if (scrut4 === true) { - cur.contTrace.last.next = saved.next; + cur.contTrace.lastFrame.next = saved.next; } scrut5 = saved.last !== saved; if (scrut5 === true) { @@ -1107,7 +1152,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { } scrut6 = saved.nextHandler !== null; if (scrut6 === true) { - cur.contTrace.lastHandler.nextHandler = saved.nextHandler; + cur.contTrace.lastHandlerFrame.nextHandler = saved.nextHandler; cur.contTrace.lastHandler = saved.lastHandler; return cur } @@ -1132,14 +1177,15 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { cont = contTrace.next; handlerCont = contTrace.nextHandler; lbl: while (true) { - let old, scrut, scrut1, scrut2, tmp, tmp1, tmp2; + let frame, old, scrut, scrut1, scrut2, tmp, tmp1, tmp2; if (cont instanceof Runtime.FunctionContFrame.class) { + frame = cont; Runtime.curEffect = null; old = Runtime.stackDepth; try { tmp1 = Runtime.stackDepth + 3; Runtime.stackDepth = tmp1; - tmp2 = runtime.safeCall(cont.resume(value)); + tmp2 = cont.resume(value); tmp = tmp2; } finally { Runtime.stackDepth = old; @@ -1150,8 +1196,8 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { value = Runtime.curEffect; } if (value instanceof Runtime.EffectSig.class) { - value.contTrace.last.next = cont.next; - value.contTrace.lastHandler.nextHandler = handlerCont; + value.contTrace.lastFrame.next = frame.next; + value.contTrace.lastHandlerFrame.nextHandler = handlerCont; scrut1 = contTrace.last !== cont; if (scrut1 === true) { value.contTrace.last = contTrace.last; @@ -1163,7 +1209,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { } return value; } - cont = cont.next; + cont = frame.next; continue lbl; } if (handlerCont instanceof Runtime.HandlerContFrame.class) { @@ -1195,7 +1241,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { if (tmp === true) { tmp1 = Runtime.stackHandler !== null; if (tmp1 === true) { - return runtime.safeCall(Runtime.stackHandler.delay()) + return Runtime.stackHandler.delay() } return runtime.Unit; } @@ -1268,11 +1314,12 @@ export { lambda2 as _$_modulePrivate_$_lambda2 }; export { lambda3 as _$_modulePrivate_$_lambda3 }; export { lambda4 as _$_modulePrivate_$_lambda4 }; export { lambda5 as _$_modulePrivate_$_lambda5 }; +export { lambda6 as _$_modulePrivate_$_lambda6 }; export { lambda$ as _$_modulePrivate_$_lambda$ }; export { lambda$1 as _$_modulePrivate_$_lambda$1 }; -export { Capture$scope301 as _$_modulePrivate_$_Capture$scope30 }; +export { Capture$scope341 as _$_modulePrivate_$_Capture$scope34 }; export { lambda$2 as _$_modulePrivate_$_lambda$2 }; -export { Capture$scope321 as _$_modulePrivate_$_Capture$scope32 }; +export { Capture$scope361 as _$_modulePrivate_$_Capture$scope36 }; export { lambda$3 as _$_modulePrivate_$_lambda$3 }; export { lambda$4 as _$_modulePrivate_$_lambda$4 }; export { continuation$ as _$_modulePrivate_$_continuation$ }; diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 82d5904012..efa22dc912 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) import "./RuntimeJS.mjs" import "./Rendering.mjs" import "./LazyArray.mjs" @@ -6,6 +7,11 @@ import "./Iter.mjs" open annotations { pure } +// The runtime also exports a Str helper module; keep the host string type unambiguous. +type Text = Str +// Tuple patterns also accept lazy views and other IterableBase instances. +type Indexable = {length: Int, at: Int -> Any, slice: Function} + module Runtime with ... @@ -25,10 +31,10 @@ val short_or = RuntimeJS.short_or fun unreachable = throw Error("unreachable") -fun assertFail(file, line) = throw Error("Assertion failed (" + file + ":" + line + ")") +fun assertFail(file: Text, line: Text) = throw Error("Assertion failed (" + file + ":" + line + ")") -fun checkArgs(functionName, expected, isUB, got) = +fun checkArgs(functionName: Text, expected: Int, isUB: Bool, got: Int) = if got < expected || isUB && got > expected do let name = if functionName.length > 0 then " '" + functionName + "'" else "" // throw globalThis.Error("Function" + name + " expected " @@ -67,17 +73,16 @@ val bitor = RuntimeJS.bitor val shl = RuntimeJS.shl val try_catch = RuntimeJS.try_catch -class EffectHandle(_reified) with +class EffectHandle(_reified: EffectSig) with val reified = _reified fun resumeWith(value) = _try(() => resume(reified.contTrace)(value)) fun raise() = set curEffect = reified -fun _try(f) = +fun _try(f: () -> Any) = let res = f() - if curEffect !== null then - let tmp = curEffect + if curEffect is EffectSig as tmp then set curEffect = null EffectHandle(tmp) else res @@ -91,20 +96,21 @@ data class MatchFailure(errors) module Tuple with @pure - fun slice(xs, i, j) = xs.slice(i, xs.length - j) + fun slice(xs: Indexable, i: Int, j: Int) = xs.slice(i, xs.length - j) @pure - fun lazySlice(xs, i, j) = LazyArray.dropLeftRight(i, j)(xs) + fun lazySlice(xs: Indexable, i: Int, j: Int) = LazyArray.dropLeftRight(i, j)(xs) @pure fun lazyConcat(...args) = LazyArray.__concat(...args) @pure - fun get(xs, i) = + fun get(xs: Indexable, i: Int) = if i >= xs.length do throw RangeError("Tuple.get: index out of bounds") if i < -xs.length do throw RangeError("Tuple.get: negative index out of bounds") - xs.[i] + // Raw indexing preserves undefined elements; a foreign method call would normalize them to Unit. + xs![i] @pure fun isArrayLike(xs) = Iter.isArrayLike(xs) @@ -113,19 +119,19 @@ module Tuple with module Str with @pure - fun startsWith(string, prefix) = string.startsWith(prefix) + fun startsWith(string: Text, prefix: Text) = string.startsWith(prefix) @pure - fun get(string, i) = + fun get(string: Text, i: Int) = if i >= string.length then throw RangeError("Str.get: index out of bounds") else string.at(i) @pure - fun take(string, n) = string.slice(0, n) + fun take(string: Text, n: Int) = string.slice(0, n) @pure - fun leaveOut(string, n) = string.slice(n) + fun leaveOut(string: Text, n: Int) = string.slice(n) // Re-export rendering functions val render = Rendering.render @@ -143,11 +149,11 @@ module TraceLogger with set indentLvl = prev + 1 prev else () - fun resetIndent(n) = + fun resetIndent(n: Int) = if enabled then set indentLvl = n else () - fun log(msg) = + fun log(msg: Text) = if enabled then console.log("| ".repeat(indentLvl) + msg.replaceAll("\n", "\n" + " ".repeat(indentLvl))) else () @@ -157,8 +163,10 @@ module TraceLogger with // layout: // | f | pc | pcStr | debugInfo | this | argLists... (*) | localCnt | locals... | // (*) argLists is length encoded nested array +// The packed array mixes functions, counters, debug tables, and arbitrary saved values. +// Casts below decode slots whose types are fixed by this layout. -mut val curEffect = null +mut val curEffect: EffectSig | null = null mut val resumeValue = null mut val resumeArr = null mut val resumeIdx = null @@ -167,11 +175,11 @@ mut val resumePc = -1 object FatalEffect object PrintStackEffect -data class FunctionContFrame(next, saved) with +data class FunctionContFrame(next: FunctionContFrame | null, saved: Array[Any]) with fun resume(value) = let i = 0 - f = saved.[0] + f = saved.[0] as Function argListsLength = saved.[5] currentArgList = 6 set @@ -183,15 +191,15 @@ data class FunctionContFrame(next, saved) with while i < argListsLength - 1 do let argListLength = saved.[currentArgList] set - f = f.apply(saved.[4], saved.slice(currentArgList + 1, currentArgList + 1 + argListLength)) + f = f.apply(saved.[4], saved.slice(currentArgList + 1, currentArgList + 1 + argListLength)) as Function currentArgList += argListLength + 1 i += 1 let argListLength = saved.[currentArgList] set resumeIdx = currentArgList + argListLength + 2 f.apply(saved.[4], saved.slice(currentArgList + 1, currentArgList + 1 + argListLength)) - fun getLocals = - let debugInfo = saved.[3] + fun getLocals: Array[LocalVarInfo] = + let debugInfo = saved.[3] as Array[Any] let i = 0 let cur = 6 while i < saved.[5] do @@ -204,24 +212,35 @@ data class FunctionContFrame(next, saved) with res.push(new LocalVarInfo(debugInfo.[i + 1], saved.[cur + 1 + debugInfo.[i]])) set i += 2 res - fun getNme = saved.[3].[0] + fun getNme = (saved.[3] as Array[Any]).[0] fun getLoc = let loc = saved.[2] if loc is null then "pc=" + saved.[1] else loc -data class HandlerContFrame(next, nextHandler, handler) - -data class ContTrace(next, last, nextHandler, lastHandler, resumed) -data class EffectSig(contTrace, handler, handlerFun) +data class HandlerContFrame(next: FunctionContFrame | null, nextHandler: HandlerContFrame | null, handler: Any) + +data class ContTrace( + next: FunctionContFrame | null, + last: ContTrace | HandlerContFrame | FunctionContFrame | null, + nextHandler: HandlerContFrame | null, + lastHandler: ContTrace | HandlerContFrame | null, + resumed: Bool +) with + // mkEffect fills both tail links before publishing the trace in curEffect. + // Empty chains point back to the trace itself; null is only used during construction. + fun lastFrame: ContTrace | HandlerContFrame | FunctionContFrame = last as (ContTrace | HandlerContFrame | FunctionContFrame) + fun lastHandlerFrame: ContTrace | HandlerContFrame = lastHandler as (ContTrace | HandlerContFrame) +// PrintStackEffect stores the showLocals flag; FatalEffect has no handler function. +data class EffectSig(contTrace: ContTrace, handler: Any, handlerFun: Function | Bool | null) class NonLocalReturn with fun ret(value) -data class FnLocalsInfo(fnName, locals) -data class LocalVarInfo(localName, value) -data class CustomStackError(stack) with +data class FnLocalsInfo(fnName: Text, locals: Array[LocalVarInfo]) +data class LocalVarInfo(localName: Text, value: Any) +data class CustomStackError(stack: Text) with fun toString() = stack @@ -236,55 +255,55 @@ fun raisePrintStackEffect(showLocals) = fun topLevelEffect(debug) = let tr = curEffect let v = null - while tr is EffectSig and tr.handler === PrintStackEffect do - console.log(showStackTrace("Stack Trace:", tr, debug, tr.handlerFun)) + while tr is EffectSig as eff and eff.handler === PrintStackEffect do + console.log(showStackTrace("Stack Trace:", eff, debug, eff.handlerFun as Bool)) set curEffect = null - v = resume(tr.contTrace)(()) + v = resume(eff.contTrace)(()) tr = curEffect - if tr is EffectSig then + if tr is EffectSig as tr then set curEffect = null - throw CustomStackError(showStackTrace("Error: Unhandled effect " + tr.handler.constructor.name, tr, debug, false)) + throw CustomStackError(showStackTrace("Error: Unhandled effect " + tr.handler!constructor!name, tr, debug, false)) else v fun illegalEffect(position) = - let tmp = curEffect + let tmp = curEffect as EffectSig set curEffect = null - throw CustomStackError(showStackTrace("Error: Effect " + tmp.handler.constructor.name + " is raised " + position, tmp, false, false)) + throw CustomStackError(showStackTrace("Error: Effect " + tmp.handler!constructor!name + " is raised " + position, tmp, false, false)) -fun showStackTrace(header, tr, debug, showLocals) = +fun showStackTrace(header: Text, tr: EffectSig, debug: Bool, showLocals: Bool) = let msg = header curHandler = tr.contTrace atTail = true if debug do - while curHandler !== null do - let cur = curHandler.next - while cur !== null do - let curLocals = cur.getLocals - let loc = cur.getLoc + while curHandler is (ContTrace | HandlerContFrame) as handlerFrame do + let cur = handlerFrame.next + while cur is FunctionContFrame as frame do + let curLocals = frame.getLocals + let loc = frame.getLoc let localsMsg = if showLocals and curLocals.length > 0 - then " with locals: " + curLocals.map(l => l.localName + "=" + Rendering.render(l.value)).join(", ") + then " with locals: " + curLocals.map((l, ...) => l.localName + "=" + Rendering.render(l.value)).join(", ") else "" set - msg += "\n\tat " + cur.getNme + " (" + loc + ")" + msg += "\n\tat " + frame.getNme + " (" + loc + ")" msg += localsMsg - cur = cur.next + cur = frame.next atTail = false - set curHandler = curHandler.nextHandler - if curHandler !== null do + set curHandler = handlerFrame.nextHandler + if curHandler is HandlerContFrame as handlerFrame do set - msg += "\n\twith handler " + curHandler.handler.constructor.name + msg += "\n\twith handler " + handlerFrame.handler!constructor!name atTail = false if atTail do set msg += "\n\tat tail position" msg -fun showFunctionContChain(cont, hl, vis, reps) = - if cont is FunctionContFrame then - let result = cont.constructor.name + "(pc=" + cont.saved.[1] - hl.forEach((m, marker) => if m.has(cont) do set result += ", " + marker) +fun showFunctionContChain(cont: Any, hl: Map[Text, Set[Any]], vis: Set[Any], reps: Int): Text = + if cont is FunctionContFrame as frame then + let result = frame!constructor!name + "(pc=" + frame.saved.[1] + hl.forEach((m, marker, ...) => if m.has(cont) do set result += ", " + marker) if vis.has(cont) then set reps = reps + 1 if reps > 10 do @@ -292,16 +311,16 @@ fun showFunctionContChain(cont, hl, vis, reps) = set result += ", REPEAT" else vis.add(cont) - result + ") -> " + showFunctionContChain(cont.next, hl, vis, reps) + result + ") -> " + showFunctionContChain(frame.next, hl, vis, reps) else if cont === null then "(null)" else "(NOT CONT)" -fun showHandlerContChain(cont, hl, vis, reps) = - if cont is HandlerContFrame then - let result = cont.handler.constructor.name - hl.forEach((m, marker) => if m.has(cont) do set result += ", " + marker) +fun showHandlerContChain(cont: Any, hl: Map[Text, Set[Any]], vis: Set[Any], reps: Int): Text = + if cont is HandlerContFrame as frame then + let result = frame.handler!constructor!name + hl.forEach((m, marker, ...) => if m.has(cont) do set result += ", " + marker) if vis.has(cont) then set reps = reps + 1 if reps > 10 do @@ -309,7 +328,7 @@ fun showHandlerContChain(cont, hl, vis, reps) = set result += ", REPEAT" else vis.add(cont) - result + " -> " + showFunctionContChain(cont.next, hl, vis, reps) + result + " -> " + showFunctionContChain(frame.next, hl, vis, reps) else if cont === null then "(null)" else @@ -319,7 +338,7 @@ fun debugCont(cont) = console.log(showFunctionContChain(cont, new Map(), new Set fun debugHandler(cont) = console.log(showHandlerContChain(cont, new Map(), new Set(), 0)) fun debugContTrace(contTrace) = - if contTrace is ContTrace then + if contTrace is ContTrace as contTrace then console.log("resumed: ", contTrace.resumed) if contTrace.last === contTrace do console.log("") @@ -331,18 +350,18 @@ fun debugContTrace(contTrace) = hl.set("last-handler", new Set([contTrace.lastHandler])) console.log(showFunctionContChain(contTrace.next, hl, vis, 0)) let cur = contTrace.nextHandler - while cur !== null do - console.log(showHandlerContChain(cur, hl, vis, 0)) - set cur = cur.nextHandler + while cur is HandlerContFrame as frame do + console.log(showHandlerContChain(frame, hl, vis, 0)) + set cur = frame.nextHandler console.log() else console.log("Not a cont trace:") console.log(contTrace) fun debugEff(eff) = - if eff is EffectSig then + if eff is EffectSig as eff then console.log("Debug EffectSig:") - console.log("handler: ", eff.handler.constructor.name) + console.log("handler: ", eff.handler!constructor!name) console.log("handlerFun: ", eff.handlerFun) debugContTrace(eff.contTrace) else @@ -351,36 +370,37 @@ fun debugEff(eff) = // runtime implementations fun unwind(...saved) = + val effect = curEffect as EffectSig set - curEffect.contTrace.last.next = new mut FunctionContFrame(null, saved) - curEffect.contTrace.last = curEffect.contTrace.last.next + effect.contTrace.lastFrame.next = new mut FunctionContFrame(null, saved) + effect.contTrace.last = effect.contTrace.lastFrame.next -fun mkEffect(handler, handlerFun) = +fun mkEffect(handler: Any, handlerFun: Function | Bool | null) = let res = new mut EffectSig(new mut ContTrace(null, null, null, null, false), handler, handlerFun) set res.contTrace.last = res.contTrace res.contTrace.lastHandler = res.contTrace curEffect = res -fun handleBlockImpl(cur, handler) = +fun handleBlockImpl(cur: EffectSig, handler: Any) = let handlerFrame = new mut HandlerContFrame(null, null, handler) set - cur.contTrace.lastHandler.nextHandler = handlerFrame + cur.contTrace.lastHandlerFrame.nextHandler = handlerFrame cur.contTrace.lastHandler = handlerFrame cur.contTrace.last = handlerFrame handleEffects(cur) -fun enterHandleBlock(handler, body) = +fun enterHandleBlock(handler: Any, body: () -> Any) = let tmp = body() if curEffect === null then tmp else - handleBlockImpl(curEffect, handler) + handleBlockImpl(curEffect as EffectSig, handler) -fun handleEffects(cur) = +fun handleEffects(cur: Any): Any = while cur is - EffectSig then - let nxt = handleEffect(cur) + EffectSig as effect then + let nxt = handleEffect(effect) if cur === nxt then set curEffect = cur return null @@ -390,19 +410,19 @@ fun handleEffects(cur) = return cur // return either new effect, final result or the same continuation if there is no handler -fun handleEffect(cur) = +fun handleEffect(cur: EffectSig): Any = // debugEff(cur) // find the handle block corresponding to the current effect let prevHandlerFrame = cur.contTrace - while prevHandlerFrame.nextHandler !== null and prevHandlerFrame.nextHandler.handler !== cur.handler do - set prevHandlerFrame = prevHandlerFrame.nextHandler + while prevHandlerFrame.nextHandler is HandlerContFrame as next and next.handler !== cur.handler do + set prevHandlerFrame = next // no matching handle block if prevHandlerFrame.nextHandler === null do return cur // the matching handle block - let handlerFrame = prevHandlerFrame.nextHandler + let handlerFrame = prevHandlerFrame.nextHandler as HandlerContFrame // unlink and save frames let saved = new mut ContTrace( @@ -424,60 +444,60 @@ fun handleEffect(cur) = // handle the effect set curEffect = null - let tmp = set stackDepth += 2 in cur.handlerFun(resume(cur.contTrace)) + let tmp = set stackDepth += 2 in (cur.handlerFun as Function)(resume(cur.contTrace)) if curEffect !== null then - set cur = curEffect + set cur = curEffect as EffectSig // relink the saved frames if saved.next !== null do - set cur.contTrace.last.next = saved.next + set cur.contTrace.lastFrame.next = saved.next if saved.last !== saved do set cur.contTrace.last = saved.last if saved.nextHandler !== null do set - cur.contTrace.lastHandler.nextHandler = saved.nextHandler + cur.contTrace.lastHandlerFrame.nextHandler = saved.nextHandler cur.contTrace.lastHandler = saved.lastHandler cur else // resume the unlinked handle blocks resumeContTrace(saved, tmp) -fun resume(contTrace)(value) = +fun resume(contTrace: ContTrace)(value: Any): Any = if contTrace.resumed do throw Error("Multiple resumption") set contTrace.resumed = true handleEffects(resumeContTrace(contTrace, value)) -fun resumeContTrace(contTrace, value) = +fun resumeContTrace(contTrace: ContTrace, value: Any): Any = let cont = contTrace.next let handlerCont = contTrace.nextHandler while - cont is FunctionContFrame then + cont is FunctionContFrame as frame then set curEffect = null - set value = set stackDepth += 3 in cont.resume(value) + set value = set stackDepth += 3 in frame.resume(value) if curEffect !== null do set value = curEffect - if value is EffectSig then + if value is EffectSig as effect then set - value.contTrace.last.next = cont.next - value.contTrace.lastHandler.nextHandler = handlerCont + effect.contTrace.lastFrame.next = frame.next + effect.contTrace.lastHandlerFrame.nextHandler = handlerCont if contTrace.last !== cont do - set value.contTrace.last = contTrace.last + set effect.contTrace.last = contTrace.last if handlerCont !== null do - set value.contTrace.lastHandler = contTrace.lastHandler + set effect.contTrace.lastHandler = contTrace.lastHandler return value else - set cont = cont.next - handlerCont is HandlerContFrame then - set cont = handlerCont.next - set handlerCont = handlerCont.nextHandler + set cont = frame.next + handlerCont is HandlerContFrame as frame then + set cont = frame.next + set handlerCont = frame.nextHandler else return value // js async object AsyncEffectMarker -fun await(promise) = - mkEffect(AsyncEffectMarker, resume => +fun await(promise: Promise) = + mkEffect(AsyncEffectMarker, (resume: Any -> Any) => fun continuation(value) = let r = resume(value) if curEffect !== null do @@ -486,7 +506,7 @@ fun await(promise) = promise.then(continuation) ) -fun toJsAsync(thunk) = +fun toJsAsync(thunk: () -> Any) = let r = enterHandleBlock(AsyncEffectMarker, thunk) if curEffect !== null do illegalEffect("in exported async function") @@ -505,11 +525,11 @@ object StackDelayHandler with fun checkDepth() = if stackDepth >= stackLimit && stackHandler !== null then // this is a tail call to effectful function - stackHandler.delay() + (stackHandler as StackDelayHandler).delay() else () -fun runStackSafe(limit, f) = +fun runStackSafe(limit: Int, f: () -> Any) = set stackLimit = limit in... set stackDepth = 1 in... set stackHandler = StackDelayHandler in... @@ -517,7 +537,7 @@ fun runStackSafe(limit, f) = if curEffect !== null do throw new Error("Effect crossed through stack safe boundary") while stackResume !== null do - let saved = stackResume + let saved = stackResume as Function set stackResume = null stackDepth = 1 @@ -526,7 +546,7 @@ fun runStackSafe(limit, f) = throw new Error("Effect crossed through stack safe boundary") result -class Int31(v) with +class Int31(v: Int) with fun zext() = bitand(v, bitnot(shl(1, 31))) as Int fun sext() = bitor(v, shl(1, 31)) as Int diff --git a/hkmc2/shared/src/test/mlscript-compile/Term.mls b/hkmc2/shared/src/test/mlscript-compile/Term.mls index b14424ebc6..18392e711c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Term.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Term.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "fs" import "process" import "path" @@ -71,30 +73,28 @@ class Term with class Context(val names: Map[Str, Int], val bindings: Map[Symbol, Str], val dependencies: Set[Str], val printOnly: Bool) with - fun get(sym) = - if bindings.has(sym) then bindings.get(sym) + fun get(sym: Symbol): Str = + if bindings.get(sym) is Str as name then name else if printOnly then sym.name else throw Error(StrOps.concat("Invalid binding name ", sym.name)) - fun nest = Context(names, new Map(bindings), dependencies, printOnly) - fun add(sym) = + fun nest: Context = Context(names, new Map(bindings), dependencies, printOnly) + fun add(sym: Symbol): Str = let fn = freshName(sym.name) bindings.set(sym, fn) fn - fun depends(d) = dependencies.add(d) - fun freshName(name) = - if not names.has(name) do - names.set(name, 0) - let i = names.get(name) + fun depends(d: Str) = dependencies.add(d) + fun freshName(name: Str): Str = + let i = if names.get(name) is Int as i then i else 0 names.set(name, i + 1) StrOps.concat of name, "_", i.toString() -fun indent(str, ind, keepLeading) = - let res = str.split("\n").map(s => StrOps.concat(ind, s)).join("\n") +fun indent(str: Str, ind: Str, keepLeading: Bool): Str = + let res = str.split("\n").map((s, ...rest) => StrOps.concat(ind, s)).join("\n") if keepLeading then res else res.substring(ind.length) -fun showStmt(s, ctx) = +fun showStmt(s: Statement, ctx: Context): Str = if s is LetDecl(sym) then let freshName = ctx.add(sym) @@ -102,18 +102,18 @@ fun showStmt(s, ctx) = DefineVar(sym, value) then StrOps.concat of ctx.get(sym), " = ", show(value, ctx) -fun showPattern(p, ctx) = +fun showPattern(p: Pattern, ctx: Context): Str = if p is - LitPattern(lit) then lit.toString() + LitPattern(lit) then String(lit) // TODO: other patterns -fun paren(t, ctx) = +fun paren(t: Term, ctx: Context): Str = if (t is Ref) || (t is CSRef) || (t is Lit) || (t is Sel) then show(t, ctx) else StrOps.concat of "(", show(t, ctx), ")" -fun showSplit(s, ctx, isCont) = +fun showSplit(s: Split, ctx: Context, isCont: Bool): Str = if s is Cons(Branch(scrut, ptrn, cont), tail) then StrOps.concat of show(scrut, ctx), " is ", showPattern(ptrn, ctx), " then ", showSplit(cont, ctx, true), "\n", showSplit(tail, ctx, false) @@ -132,44 +132,44 @@ fun showSplit(s, ctx, isCont) = UseSplit(sym) then StrOps.concat of "$", ctx.get(sym) -fun show(t, ctx) = +fun show(t: Term, ctx: Context): Str = if t is Ref(sym) then ctx.get(sym) CSRef(Symbol(name), baseFile, file) then if file is undefined then ctx.depends(baseFile) else ctx.depends(path.join of path.dirname(baseFile), file) name - Lit(lit) then lit.toString() + Lit(lit) then String(lit) Builtin(name) then name Sel(prefix, name) then StrOps.concat of paren(prefix, ctx), ".", name App(lhs, rhs) then StrOps.concat of paren(lhs, ctx), show(rhs, ctx) - Tup(fields) then StrOps.concat of "(", fields.map(t => show(t, ctx)).join(", "), ")" + Tup(fields) then StrOps.concat of "(", fields.map((t, ...rest) => show(t, ctx)).join(", "), ")" Lam(params, body) then let nest = ctx.nest - let freshParams = params.map(s => nest.add(s)) + let freshParams = params.map((s, ...rest) => nest.add(s)) StrOps.concat of "(", freshParams.join(", "), ") =>\n", indent(show(body, nest), " ", true) Blk(stats, res) then let nest = ctx.nest - StrOps.concat of stats.map(s => showStmt(s, nest)).join("\n"), "\n", show(res, nest) + StrOps.concat of stats.map((s, ...rest) => showStmt(s, nest)).join("\n"), "\n", show(res, nest) IfLike(Keyword.If, split) then StrOps.concat of "if \n", indent(showSplit(split, ctx, false), " ", true) // TODO: other terms -fun print(t) = +fun print(t: Term) = let ctx = Context(new Map(), new Map(), new Set(), true) console.log(show(t, ctx)) -fun genImport(base, p) = +fun genImport(base: Str, p: Str): Str = StrOps.concat of "import \"./", path.relative(base, url.fileURLToPath(p)).slice(0, -4), ".mls\"" -fun codegen(t, file) = +fun codegen(t: Term, file: Str) = let ctx = Context(new Map(), new Map(), new Set(), false) let moduleName = path.parse(file).name let fullpath = path.join of process.cwd(), file let code = StrOps.concat of "module ", moduleName, " with ...\nfun res =\n", indent(show(t, ctx), " ", true), "\n" let dependencies = - Array.from(ctx.dependencies).map(s => genImport(path.dirname(fullpath), s)) + Array.from(ctx.dependencies).map((s, ...rest) => genImport(path.dirname(fullpath), s)) if not fs.existsSync(file) do fs.writeFileSync(file, "", "utf8") let originData = fs.readFileSync(file, "utf8") diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls index 345201a51f..7e1f8389a7 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Extension.mls @@ -1,3 +1,4 @@ +#lang(0.3.x) import "./Keywords.mls" import "./Rules.mls" import "./Token.mls" @@ -74,7 +75,7 @@ fun extendCategory(choiceBodyTree: Tree) = print of "Invalid syntax description: " + choiceBodyTree Tree.summary() fun parseChoiceTree(tree: Tree) = - fun go(trees: Stack[Tree]): Choice[Tree] = + fun go(trees: Stack[Tree]): Choice[Stack[Tree]] = let res = if trees is Tree.App(Tree.Ident("keyword", _), Tree.Literal(LiteralKind.String, name)) :: rest and Keywords.all |> MutMap.get(name) is Some(keyword) then @@ -88,7 +89,7 @@ fun parseChoiceTree(tree: Tree) = Tree.Tuple(categoryIdent :: choiceTree :: funcIdent :: Nil) and categoryIdent is Tree.Literal(LiteralKind.String, categoryName) and funcIdent is Tree.Ident and - let op = (trees) => trees + let op = (trees: Stack[Tree]) => trees Iter.fromStack() Iter.folded of funcIdent, (f, x) => Tree.App(f, x) choiceTree is diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls index 81001ea40b..649dfa15fb 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/ParseRule.mls @@ -152,14 +152,15 @@ fun lazy(init) = new Lazy of init data class ParseRule[A](name: Str, mut val choices: Stack[Choice[A]]) with open Choice { Keyword, Ref, End, Siding } - fun map[B](op: A -> B) = + fun map[B](op: A -> B): ParseRule[B] = new mut ParseRule of name, choices Iter.fromStack() Iter.mapping of choice => choice Choice.map(op) Iter.toStack() - fun andThen[B, C](rest: ParseRule[B], process: (A, B) -> C) = - fun go(rule: ParseRule[B]) = new ParseRule of rule.name, rule.choices + fun andThen[B, C](rest: ParseRule[B], process: (A, B) -> C): ParseRule[C] = + // Each choice may have a different intermediate result type; append B to that result. + fun go[X](rule: ParseRule[X]): ParseRule[[X, B]] = new ParseRule of rule.name, rule.choices Iter.fromStack() Iter.mapping of case Keyword(keyword, rest') then [Keyword(keyword, go(rest'))] @@ -245,7 +246,7 @@ data class ParseRule[A](name: Str, mut val choices: Stack[Choice[A]]) with this // Display parse rules as a tree in a BNF-like format. - fun display = + fun display: Str = /// Display a single `Choice`. fun displayChoice[A](choice: Choice[A]) = if choice is Choice.Keyword(keyword, rest) then diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/ParseRuleTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/ParseRuleTest.mls new file mode 100644 index 0000000000..a75adb8622 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/ParseRuleTest.mls @@ -0,0 +1,32 @@ +:. +:js + +import "../../../mlscript-compile/apps/parsing/ParseRule.mls" +import "../../../mlscript-compile/apps/parsing/Keywords.mls" + +open ParseRule { Choice, rule } + +// Sequencing combines different result types and exposes the resulting rule interface. +:expect Some("xxx") +val first: ParseRule.ParseRule[Str] = rule("first", Choice.end("x")) +val second: ParseRule.ParseRule[Int] = rule("second", Choice.end(3)) +first.andThen(second, (text, count) => text.repeat(count)).endChoice +//│ = Some("xxx") +//│ first = ParseRule("first", Cons(End("x"), Nil)) +//│ second = ParseRule("second", Cons(End(3), Nil)) + +// Recursion beneath a keyword preserves the result type of its continuation. +:expect true +val prefixed: ParseRule.ParseRule[Str] = rule("prefixed", Choice.keyword(Keywords._let)(Choice.end("x"))) +prefixed.andThen(second, (text, count) => text.repeat(count)).display.includes("let") +//│ = true +//│ prefixed = ParseRule( +//│ "prefixed", +//│ Cons( +//│ Keyword( +//│ Keyword(`let`, 3, 1), +//│ ParseRule("`let` keyword", Cons(End("x"), Nil)) +//│ ), +//│ Nil +//│ ) +//│ ) diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 9e18b1c946..2a2d20d7b3 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -7,17 +7,17 @@ declare type Nothing declare class Object with fun toString(): Str declare module Object with + fun entries(target): Array[[Str, Any]] + fun getOwnPropertyDescriptor(target, key): Object | undefined + fun getOwnPropertyDescriptors(target): Object fun create freeze assign - entries prototype fromEntries getPrototypeOf defineProperty - getOwnPropertyDescriptor - getOwnPropertyDescriptors declare class JSON declare module JSON with fun @@ -31,9 +31,13 @@ declare module Number with MAX_SAFE_INTEGER NEGATIVE_INFINITY POSITIVE_INFINITY -declare class BigInt +declare class BigInt with + fun toString(...radix): Str declare module BigInt -declare class Function +declare class Function with + val name: Str + fun bind(thisArg, ...args): Function + fun apply(thisArg, args: Array[Any]): Any declare module Function declare class String declare fun String(...args): Str @@ -43,6 +47,7 @@ declare module String with fromCodePoint raw declare class RegExp with + fun toString(): Str fun exec(input: Str): Array[Str] | null fun test(input: Str): Bool declare module RegExp @@ -52,6 +57,7 @@ declare class Set[V] with fun has(value: V): Bool fun delete(value: V): Bool fun clear(): undefined + fun values(): Iterator[V] fun union(other: Set[V]): Set[V] fun difference(other: Set[V]): Set[V] declare module Set @@ -65,12 +71,17 @@ declare class Map[K, V] with fun keys(): Iterator[K] fun values(): Iterator[V] fun entries(): Iterator[[K, V]] + fun forEach(f: (V, K, Map[K, V]) -> Any): undefined declare class Iterator[T] with fun next(): { value: T, done: Bool } declare module Map declare class WeakSet[V] declare module WeakSet -declare class WeakMap[K, V] +declare class WeakMap[K, V] with + fun get(key: K): V | undefined + fun has(key: K): Bool + fun delete(key: K): Bool + fun id"set"(key: K, value: V): WeakMap[K, V] declare module WeakMap declare class Error with val message: Str @@ -113,8 +124,12 @@ declare module BigInt64Array declare class BigUint64Array declare module BigUint64Array -declare class Promise -declare val Promise +declare class Promise with + fun id"then"(onFulfilled: Function, ...onRejected): Promise +declare module Promise with + fun resolve(value): Promise + fun reject(reason): Promise + fun all(values): Promise // MLscript-specific types declare class Bool @@ -204,7 +219,8 @@ declare module Array with isArray val prototype: Array[Any] -declare class Symbol +declare class Symbol with + val description: Str | undefined declare module Symbol with // The `TermDef` needs `rhs` to be defined to be recognized as `isMLsFun`. // Otherwise, it would be wrapped in `runtime.safeCall`, which accesses the diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index be34806400..822448ac6a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -197,8 +197,8 @@ fun f() = case [...x, y, z] then x //│ match caseScrut //│ Array(2+) => //│ let inlinedVal, tmp; -//│ set tmp = -⁰(caseScrut.length﹖, 2); -//│ set inlinedVal = caseScrut.slice﹖(0, tmp); +//│ set tmp = -⁰(caseScrut.length⁰, 2); +//│ set inlinedVal = caseScrut.slice⁰(0, tmp); //│ do runtime⁰.Tuple⁰.get⁰(caseScrut, -2); //│ do runtime⁰.Tuple⁰.get⁰(caseScrut, -1); //│ return inlinedVal diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 2eaf406f16..76baf82789 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.178: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.193: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -141,15 +141,15 @@ new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.158: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.173: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.159: fun join(...separators): Str +//│ ║ l.174: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.160: fun reverse(): Array[T] +//│ ║ l.175: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.161: fun sort(...compare): Array[T] +//│ ║ l.176: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.162: ... (more lines omitted) ... +//│ ║ l.177: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2] @@ -160,15 +160,15 @@ new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.158: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.173: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.159: fun join(...separators): Str +//│ ║ l.174: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.160: fun reverse(): Array[T] +//│ ║ l.175: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.161: fun sort(...compare): Array[T] +//│ ║ l.176: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.162: ... (more lines omitted) ... +//│ ║ l.177: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.192: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.207: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 362af7643f..1bd0768b0d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.178: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.193: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/newres/MigratedFixtureInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/MigratedFixtureInterfaces.mls new file mode 100644 index 0000000000..f4c52d1f8b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/MigratedFixtureInterfaces.mls @@ -0,0 +1,72 @@ +:. +:js + +import "../../mlscript-compile/Rendering.mls" +import "../../mlscript-compile/Runtime.mls" +import "../../mlscript-compile/LazyFingerTree.mls" + +// Callback adapters retain their result interfaces through an imported module. +:expect [4, 5] +class Box(val value: Int) +[3, 4].map(Rendering.pass1((n: Int) => Box(n + 1))).map((box, ...) => box.value) +//│ = [4, 5] + +:expect 6 +Rendering.render([1, 2]).length +//│ = 6 + +// Lazy splices expose indexing and iteration before and after materialization. +:expect [0, 1, 2, 3] +[...LazyFingerTree.concat(LazyFingerTree.mk(0, 1), [2, 3])] +//│ = [0, 1, 2, 3] + +:expect 4 +LazyFingerTree.concat([1, 2], [3, 4]).materialize().length +//│ = 4 + +:expect [2, 3] +Runtime.Tuple.slice([1, 2, 3, 4], 1, 1) +//│ = [2, 3] + +// Host collections retain typed values when used through declared interfaces. +:expect "value" +fun lookup(map: WeakMap[Object, Str], key: Object) = if map.get(key) is + Str as value then value + else "missing" +val key = new Object +val map = new WeakMap +map.set(key, "value") +lookup(map, key) +//│ = "value" +//│ key = {} +//│ map = WeakMap { } + +// This runtime primitive preserves undefined; user-call instrumentation forbids returning it. +:noSanityCheck +:expect true +Runtime.Tuple.get([undefined], 0) is undefined +//│ = true + +// Tuple runtime operations also support lazy views. +:expect 2 +Runtime.Tuple.get(LazyFingerTree.mk(1, 2), -1) +//│ = 2 + +import "../../mlscript-compile/Term.mls" + +let termContext = Term.Context(new Map(), new Map(), new Set(), true) +let termSymbol = Term.Symbol("x") +//│ termContext = Context(Map(0) {}, Map(0) {}, Set(0) {}, true) +//│ termSymbol = Symbol("x") + +:expect "(x_0) =>\n x_0" +Term.show(Term.Lam([termSymbol], Term.Ref(termSymbol)), termContext) +//│ = "(x_0) =>\n x_0" + +:expect ["null", "undefined", "true", "42"] +[null, undefined, true, 42].map((lit, ...rest) => Term.show(Term.Lit(lit), termContext)) +//│ = ["null", "undefined", "true", "42"] + +:expect "undefined" +Term.showPattern(Term.LitPattern(undefined), termContext) +//│ = "undefined" diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls index 4abd9e68c0..a6ce875201 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls @@ -99,7 +99,7 @@ pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Int' does not contain member 'toUpperCase' //│ ║ in ../decls/Prelude.mls -//│ ║ l.125: val length: Int +//│ ║ l.140: val length: Int //│ ╙── ^^^ // Conjunction produces a pair, not the string tested by its left operand. diff --git a/hkmc2/shared/src/test/mlscript/tailrec/MultiArgLists.mls b/hkmc2/shared/src/test/mlscript/tailrec/MultiArgLists.mls index a6a0ffc618..1e66425f2b 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/MultiArgLists.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/MultiArgLists.mls @@ -243,8 +243,8 @@ fun g(n)(m) = //│ set param0_tmp = param0; //│ set param0 = tmp; //│ set argList = [...tmp1, 3, ...tmp2, ...tmp3, 7, param0_tmp]; -//│ set tmp4 = -¹(argList.length﹖, 0); -//│ set inlinedVal = argList.slice﹖(4, tmp4); +//│ set tmp4 = -¹(argList.length⁰, 0); +//│ set inlinedVal = argList.slice⁰(4, tmp4); //│ set param1 = argList.0; //│ do argList.1; //│ do argList.2; @@ -287,8 +287,8 @@ fun f(a)(...z) = if z is //│ Array(1+) => //│ let inlinedVal, tmp1; //│ do runtime⁰.Tuple⁰.get⁰(z, 0); -//│ set tmp1 = -¹(z.length﹖, 0); -//│ set inlinedVal = z.slice﹖(1, tmp1); +//│ set tmp1 = -¹(z.length⁰, 0); +//│ set inlinedVal = z.slice⁰(1, tmp1); //│ set tmp = +⁰(a, 1); //│ set a = tmp; //│ set z = inlinedVal; @@ -494,8 +494,8 @@ fun f(n)(a)(...z) = //│ Array(1+) => //│ let inlinedVal, tmp2; //│ do runtime⁰.Tuple⁰.get⁰(z, 0); -//│ set tmp2 = -¹(z.length﹖, 0); -//│ set inlinedVal = z.slice﹖(1, tmp2); +//│ set tmp2 = -¹(z.length⁰, 0); +//│ set inlinedVal = z.slice⁰(1, tmp2); //│ set tmp1 = +⁰(a, 1); //│ set res = f$tailrec³(n, tmp1, inlinedVal); //│ do Predef⁰.print⁰("hi!"); diff --git a/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls b/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls index c6bc716852..f0a0bc6ef7 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls @@ -177,8 +177,8 @@ fun g(n) = //│ set tmp1 = [3, 4]; //│ set tmp2 = [5, param0]; //│ set argList = [param0, ...tmp, 3, ...tmp1, ...tmp2, 7, param0]; -//│ set tmp3 = -¹(argList.length﹖, 0); -//│ set inlinedVal = argList.slice﹖(5, tmp3); +//│ set tmp3 = -¹(argList.length⁰, 0); +//│ set inlinedVal = argList.slice⁰(5, tmp3); //│ set param0 = argList.0; //│ do argList.1; //│ do argList.2; diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index c214c279ff..166f6993ef 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.347: fun +//│ ║ l.363: fun //│ ║ ^^^ -//│ ║ l.348: const(c: Int): Int32 +//│ ║ l.364: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 66dd8e58749c8da69322784b8bfe00650170f2b2 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 09:52:44 +0800 Subject: [PATCH 173/228] Refine negated patterns and preserve null-check semantics --- .../scala/hkmc2/semantics/NewResolver.scala | 54 +++++- .../hkmc2/semantics/ups/SplitCompiler.scala | 20 +- .../src/test/mlscript-compile/Rendering.mls | 18 +- .../src/test/mlscript-compile/Runtime.mjs | 177 +++++++----------- .../src/test/mlscript-compile/Runtime.mls | 15 +- .../test/mlscript/newres/NegatedPatterns.mls | 171 +++++++++++++++++ .../mlscript/ucs/patterns/BooleansOps.mls | 2 +- 7 files changed, 318 insertions(+), 139 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/NegatedPatterns.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 17735e2794..c02f495506 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1984,17 +1984,61 @@ class NewResolver: // need a separate output interface. Do not reuse the input's fields for them. def unknownOutput: UnknownValueShape = UnknownValueShape(Term.Error().withLocOf(pattern))(ShapeProvenance( msg"The output shape of this pattern is not yet known." -> pattern.toLoc :: Nil)) + def isNominal(ctor: Pattern.Constructor): Bool = + ctor.resolvedTargets.nonEmpty && ctor.resolvedTargets.forall: + case _: ClassSymbol | _: ModuleOrObjectSymbol => true + case _ => false def preservesInput(pat: Pattern): Bool = pat match case Pattern.Wildcard() | Pattern.Literal(_) | Pattern.Range(_, _, _) | Pattern.Negation(_) => true case Pattern.Alias(inner, _) => preservesInput(inner) case Pattern.Guarded(inner, _) => preservesInput(inner) case Pattern.Annotated(inner, _) => preservesInput(inner) case ctor: Pattern.Constructor => - ctor.resolvedTargets.nonEmpty && ctor.resolvedTargets.forall: - case _: ClassSymbol | _: ModuleOrObjectSymbol => true - case _ => false - && ctor.arguments.forall(_.forall(preservesInput)) + isNominal(ctor) && ctor.arguments.forall(_.forall(preservesInput)) case _ => false + // Negation filters the input, never a transformed output. Only discard a + // candidate when the whole shape must match the excluded pattern. In + // particular, failing to prove membership in a class is not proof of its + // complement: abstract and structural inputs may still contain instances. + def exclude(input: Shape, pat: Pattern)(receive: ShapeListener[Shape])(using NewResolverState): Unit = + input match + case value @ Marked(_: InstanceShape, _) => + listenInstanceViews(value)(exclude(_, pat)(receive)) + case symbol: SymShape => fromSymbol(symbol, exclude(_, pat)(receive), + Term.Error().withLocOf(pat), _ => (), false) + case value: TermShape => pat match + case Pattern.Wildcard() => () + case Pattern.Literal(literal) => shapeParts(value).value match + case Marked(intro: IntroShape, _) => intro.trm match + case Lit(actual) if actual === literal => () + case _ => receive(value) + case _ => receive(value) + case Pattern.Alias(inner, _) => exclude(value, inner)(receive) + case Pattern.Annotated(inner, _) => exclude(value, inner)(receive) + case Pattern.Composition(true, left, right) => + // Not (left or right): exclude both alternatives from the input. + exclude(value, left)(exclude(_, right)(receive)) + case Pattern.Composition(false, left, right) => + // Not (left and right): either failed test admits the input. + exclude(value, left)(receive) + exclude(value, right)(receive) + case Pattern.Negation(inner) if preservesInput(inner) => + matchShapePat(value, inner)(receive) + case ctor: Pattern.Constructor if isNominal(ctor) => + def unrestricted(pat: Pattern): Bool = pat match + case Pattern.Wildcard() => true + case Pattern.Alias(inner, _) => unrestricted(inner) + case Pattern.Annotated(inner, _) => unrestricted(inner) + case _ => false + ctor.subscribeToShapes: + case CtorPatternShape(cls, fields, _, _, _) => + // A nominal match alone cannot exclude C(p) when p tests a + // field's value. Retain it unless every field accepts anything. + if !value.isInstanceOfClass(cls) || !fields.forall((_, p) => unrestricted(p)) then receive(value) + // Guards, extractors, and patterns with transformed intermediate + // results may fail even on a matching nominal input. Retain their + // input interfaces until those tests have a definite shape semantics. + case _ => receive(value) pattern match case al @ Pattern.Alias(pat, _) => matchShapePat(shape, pat): sh => @@ -2017,7 +2061,7 @@ class NewResolver: matchShapePat(shape, right)(matched) case Pattern.Negation(pat) => matchShapePat(shape, pat)(_ => ()) - matched(shape) + exclude(shape, pat)(matched) case Pattern.Concatenation(left, right) => // Prefix matching and concatenating transformed results need their own // shape rules. Still check nested bindings; no output interface is assumed. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala index c3b322a858..5fecd56947 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala @@ -723,11 +723,21 @@ class SplitCompiler(using codegen.Erasure)(using tl: TL)(using State, Ctx, Raise // to be a function that takes a diagnostic information generation // function. val outputSymbol = new LazyScrut() - makeMatchSplit(scrutinee, pattern, false)( - (_output, _bindings) => alternative, // The output and bindings are discarded. - // The place where the diagnostic information should be stored. - outputSymbol.toLet(scrutinee(), makeConsequent(outputSymbol, SeqMap.empty) ~~: alternative) - ) + val success = outputSymbol.toLet(scrutinee(), makeConsequent(outputSymbol, SeqMap.empty) ~~: alternative) + if alternative.isFull then + makeMatchSplit(scrutinee, pattern, false)( + (_output, _bindings) => alternative, // The output and bindings are discarded. + success) + else + // An unfinished split means fallthrough, not a committed rejection. + // Swapping it into the inner pattern's consequent would let a match + // fall through to `success` (notably when a while loop should exit). + // Complete both inner outcomes before branching on the match result. + val test = Term.SynthIf(makeMatchSplit(scrutinee, pattern, false)( + (_output, _bindings) => Split.Else(Term.Lit(Tree.BoolLit(true))), + Split.Else(Term.Lit(Tree.BoolLit(false))))) + tempLet("negated", test): result => + Branch(result.safeRef, FlatPattern.Lit(Tree.BoolLit(false)), success) ~: alternative // Note that we might duplicate the alternative split here. case Wildcard() => (makeConsequent, alternative) => makeConsequent(scrutinee, SeqMap.empty) ~~: alternative case Literal(literal) => (makeConsequent, alternative) => diff --git a/hkmc2/shared/src/test/mlscript-compile/Rendering.mls b/hkmc2/shared/src/test/mlscript-compile/Rendering.mls index 4aaa0ef503..71511663ff 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Rendering.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Rendering.mls @@ -127,7 +127,7 @@ fun render(target, ...args): Str = // The length of indentations that should be prepended to items in this // array if this array is displayed in multi-lines. itemIndentationLength = - if indent is Str as indent then (level + 1) * indent.length else 0 + if indent is ~null as indent then (level + 1) * indent.length else 0 // Return `true` if the array has no remaining elements. let done() = let isDone = null @@ -161,7 +161,7 @@ fun render(target, ...args): Str = // The length of indentations that should be prepended to items in this // array if this array is displayed in multi-lines. itemIndentationLength = - if indent is Str as indent then (level + 1) * indent.length else 0 + if indent is ~null as indent then (level + 1) * indent.length else 0 // Return `true` if the array has no remaining elements. let done() = peek.done let next(isFirst: Bool, prefixLength: Int) = @@ -181,7 +181,7 @@ fun render(target, ...args): Str = let length = fieldNames.length i = 0 - itemIndentationLength = if indent is Str as indent then (level + 1) * indent.length else 0 + itemIndentationLength = if indent is ~null as indent then (level + 1) * indent.length else 0 let done() = i >= length let next(isFirst: Bool, prefixLength: Int) = if fieldNames.[i] is do set i += 1 @@ -202,8 +202,8 @@ fun render(target, ...args): Str = // brackets and spaces around items are excluded from the length. singleLineLength = startPos + symbols.start.length + (if symbols.padding then padding.length else 0) - parentIndent = if indent is Str as indent then indent.repeat(level) else "" - itemIdent = if indent is Str as indent then indent.repeat(level + 1) else "" + parentIndent = if indent is ~null as indent then indent.repeat(level) else "" + itemIdent = if indent is ~null as indent then indent.repeat(level + 1) else "" // Iterate through and render all items. while done() is false do let item = next(items.length is 0, singleLineLength) @@ -240,7 +240,7 @@ fun render(target, ...args): Str = length = entries.length i = 0 itemIndentationLength = - if indent is Str as indent then (level + 1) * indent.length else 0 + if indent is ~null as indent then (level + 1) * indent.length else 0 let skipNonEnumerable() = while i < length and entries.[i].1!enumerable is false do set i += 1 let done() = i >= length @@ -278,7 +278,7 @@ fun render(target, ...args): Str = // The length of indentations that should be prepended to items in this // array if this array is displayed in multi-lines. itemIndentationLength = - if indent is Str as indent then (level + 1) * indent.length else 0 + if indent is ~null as indent then (level + 1) * indent.length else 0 // Return `true` if the array has no remaining elements. let done() = peek.done let next(isFirst: Bool, prefixLength: Int) = @@ -315,8 +315,8 @@ fun render(target, ...args): Str = items = mut [] hasMultiline = false singleLineLength = startPos + symbols.start.length + startPadding.length - parentIndent = if indent is Str as indent then indent.repeat(level) else "" - itemIdent = if indent is Str as indent then indent.repeat(level + 1) else "" + parentIndent = if indent is ~null as indent then indent.repeat(level) else "" + itemIdent = if indent is ~null as indent then indent.repeat(level + 1) else "" while done() is false do let keyValue = next(items.length is 0, singleLineLength) diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index c82819783e..3d3940b031 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -5,7 +5,7 @@ import RuntimeJS from "./RuntimeJS.mjs"; import Rendering from "./Rendering.mjs"; import LazyArray from "./LazyArray.mjs"; import Iter from "./Iter.mjs"; -let continuation, Runtime1, lambda, lambda1, lambda2, lambda3, lambda4, lambda5, lambda6, lambda$, lambda$1, Capture$scope341, lambda$2, Capture$scope361, lambda$3, lambda$4, continuation$; +let continuation, Runtime1, lambda, lambda1, lambda2, lambda3, lambda4, lambda5, lambda$, lambda$1, Capture$scope321, lambda$2, Capture$scope341, lambda$3, lambda$4, continuation$; continuation$ = function continuation$(Runtime2, resume) { return (value) => { return continuation(Runtime2, resume, value) @@ -28,69 +28,63 @@ lambda$4 = (undefined, function (Runtime2, promise) { return runtime.safeCall(promise.then(continuation$here)) } }); -lambda4 = (undefined, function (Runtime2, promise, resume) { +lambda3 = (undefined, function (Runtime2, promise, resume) { let continuation$here; continuation$here = continuation$(Runtime2, resume); return runtime.safeCall(promise.then(continuation$here)) }); -(class Capture$scope36 { +(class Capture$scope34 { static { - Capture$scope361 = this + Capture$scope341 = this } constructor(result$0) { this.result$0 = result$0; } toString() { return runtime.render(this); } - static [definitionMetadata] = ["class", "Capture$scope36"]; + static [definitionMetadata] = ["class", "Capture$scope34"]; }); -lambda$3 = (undefined, function (scope36$cap, cont) { +lambda$3 = (undefined, function (scope34$cap, cont) { return (m, marker, ..._) => { - return lambda3(scope36$cap, cont, m, marker, ..._) + return lambda2(scope34$cap, cont, m, marker, ..._) } }); -lambda3 = (undefined, function (scope36$cap, cont, m, marker, ..._) { +lambda2 = (undefined, function (scope34$cap, cont, m, marker, ..._) { let scrut, tmp, tmp1; scrut = runtime.safeCall(m.has(cont)); if (scrut === true) { tmp = ", " + marker; - tmp1 = scope36$cap.result$0 + tmp; - scope36$cap.result$0 = tmp1; + tmp1 = scope34$cap.result$0 + tmp; + scope34$cap.result$0 = tmp1; return runtime.Unit } return runtime.Unit; }); -(class Capture$scope34 { +(class Capture$scope32 { static { - Capture$scope341 = this + Capture$scope321 = this } constructor(result$0) { this.result$0 = result$0; } toString() { return runtime.render(this); } - static [definitionMetadata] = ["class", "Capture$scope34"]; + static [definitionMetadata] = ["class", "Capture$scope32"]; }); -lambda$2 = (undefined, function (scope34$cap, cont) { +lambda$2 = (undefined, function (scope32$cap, cont) { return (m, marker, ..._) => { - return lambda2(scope34$cap, cont, m, marker, ..._) + return lambda1(scope32$cap, cont, m, marker, ..._) } }); -lambda2 = (undefined, function (scope34$cap, cont, m, marker, ..._) { +lambda1 = (undefined, function (scope32$cap, cont, m, marker, ..._) { let scrut, tmp, tmp1; scrut = runtime.safeCall(m.has(cont)); if (scrut === true) { tmp = ", " + marker; - tmp1 = scope34$cap.result$0 + tmp; - scope34$cap.result$0 = tmp1; + tmp1 = scope32$cap.result$0 + tmp; + scope32$cap.result$0 = tmp1; return runtime.Unit } return runtime.Unit; }); -lambda1 = (undefined, function (l, ..._) { - let tmp, tmp1; - tmp = l.localName + "="; - tmp1 = runtime.safeCall(Rendering.render(l.value)); - return tmp + tmp1 -}); lambda = (undefined, function (l, ..._) { let tmp, tmp1; tmp = l.localName + "="; @@ -103,7 +97,7 @@ lambda$1 = (undefined, function (Runtime2) { return runtime.Unit } }); -lambda6 = (undefined, function (Runtime2, k) { +lambda5 = (undefined, function (Runtime2, k) { Runtime2.stackResume = k; return runtime.Unit }); @@ -112,7 +106,7 @@ lambda$ = (undefined, function (Runtime2, EffectHandle1, value) { return Runtime2.resume(EffectHandle1.reified.contTrace)(value) } }); -lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { +lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { return Runtime2.resume(EffectHandle1.reified.contTrace)(value) }); (class Runtime { @@ -759,11 +753,11 @@ lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { let res, scrut; res = runtime.safeCall(f()); scrut = Runtime.curEffect; - if (scrut instanceof Runtime.EffectSig.class) { - Runtime.curEffect = null; - return Runtime.EffectHandle(scrut) + if (scrut === null) { + return res } - return res; + Runtime.curEffect = null; + return Runtime.EffectHandle(scrut); } static printRaw(x) { let rcd, tmp; @@ -827,88 +821,48 @@ lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { atTail = true; if (debug === true) { lbl: while (true) { - let cur, tmp, tmp1, tmp2, tmp3; - if (curHandler instanceof Runtime.ContTrace.class) { + let cur, nextHandler, tmp, tmp1; + if (curHandler !== null) { cur = curHandler.next; lbl1: while (true) { - let curLocals, loc, scrut, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12; - if (cur instanceof Runtime.FunctionContFrame.class) { + let curLocals, loc, scrut, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10; + if (cur !== null) { curLocals = cur.getLocals; loc = cur.getLoc; if (showLocals === true) { scrut = curLocals.length > 0; if (scrut === true) { - tmp4 = runtime.safeCall(curLocals.map(lambda)); - tmp5 = runtime.safeCall(tmp4.join(", ")); - tmp6 = " with locals: " + tmp5; + tmp2 = runtime.safeCall(curLocals.map(lambda)); + tmp3 = runtime.safeCall(tmp2.join(", ")); + tmp4 = " with locals: " + tmp3; } else { - tmp6 = ""; + tmp4 = ""; } } else { - tmp6 = ""; + tmp4 = ""; } - tmp7 = "\n\tat " + cur.getNme; - tmp8 = tmp7 + " ("; - tmp9 = tmp8 + loc; - tmp10 = tmp9 + ")"; - tmp11 = msg + tmp10; - tmp12 = tmp11 + tmp6; - msg = tmp12; + tmp5 = "\n\tat " + cur.getNme; + tmp6 = tmp5 + " ("; + tmp7 = tmp6 + loc; + tmp8 = tmp7 + ")"; + tmp9 = msg + tmp8; + tmp10 = tmp9 + tmp4; + msg = tmp10; cur = cur.next; atTail = false; continue lbl1 } break; } - curHandler = curHandler.nextHandler; - if (curHandler instanceof Runtime.HandlerContFrame.class) { - tmp = "\n\twith handler " + curHandler.handler["constructor"]["name"]; - tmp1 = msg + tmp; - msg = tmp1; - atTail = false; - continue lbl - } - continue lbl; - } else if (curHandler instanceof Runtime.HandlerContFrame.class) { - cur = curHandler.next; - lbl2: while (true) { - let curLocals, loc, scrut, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12; - if (cur instanceof Runtime.FunctionContFrame.class) { - curLocals = cur.getLocals; - loc = cur.getLoc; - if (showLocals === true) { - scrut = curLocals.length > 0; - if (scrut === true) { - tmp4 = runtime.safeCall(curLocals.map(lambda1)); - tmp5 = runtime.safeCall(tmp4.join(", ")); - tmp6 = " with locals: " + tmp5; - } else { - tmp6 = ""; - } - } else { - tmp6 = ""; - } - tmp7 = "\n\tat " + cur.getNme; - tmp8 = tmp7 + " ("; - tmp9 = tmp8 + loc; - tmp10 = tmp9 + ")"; - tmp11 = msg + tmp10; - tmp12 = tmp11 + tmp6; - msg = tmp12; - cur = cur.next; - atTail = false; - continue lbl2 - } - break; - } - curHandler = curHandler.nextHandler; - if (curHandler instanceof Runtime.HandlerContFrame.class) { - tmp2 = "\n\twith handler " + curHandler.handler["constructor"]["name"]; - tmp3 = msg + tmp2; - msg = tmp3; - atTail = false; + nextHandler = curHandler.nextHandler; + curHandler = nextHandler; + if (nextHandler === null) { continue lbl } + tmp = "\n\twith handler " + nextHandler.handler["constructor"]["name"]; + tmp1 = msg + tmp; + msg = tmp1; + atTail = false; continue lbl; } break; @@ -921,12 +875,12 @@ lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { return header; } static showFunctionContChain(cont, hl, vis, reps) { - let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, scope34$cap; - scope34$cap = new Capture$scope341(undefined); + let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, scope32$cap; + scope32$cap = new Capture$scope321(undefined); if (cont instanceof Runtime.FunctionContFrame.class) { tmp = cont["constructor"]["name"] + "(pc="; - scope34$cap.result$0 = tmp + cont.saved.at(1); - runtime.safeCall(hl.forEach(lambda$2(scope34$cap, cont))); + scope32$cap.result$0 = tmp + cont.saved.at(1); + runtime.safeCall(hl.forEach(lambda$2(scope32$cap, cont))); scrut = runtime.safeCall(vis.has(cont)); if (scrut === true) { tmp1 = reps + 1; @@ -935,12 +889,12 @@ lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { if (scrut1 === true) { throw runtime.safeCall(globalThis.Error("10 repeated continuation frame (loop?)")) } - tmp2 = scope34$cap.result$0 + ", REPEAT"; - scope34$cap.result$0 = tmp2; + tmp2 = scope32$cap.result$0 + ", REPEAT"; + scope32$cap.result$0 = tmp2; } else { runtime.safeCall(vis.add(cont)); } - tmp3 = scope34$cap.result$0 + ") -> "; + tmp3 = scope32$cap.result$0 + ") -> "; tmp4 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp3 + tmp4 } @@ -951,11 +905,11 @@ lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { return "(NOT CONT)"; } static showHandlerContChain(cont, hl, vis, reps) { - let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, scope36$cap; - scope36$cap = new Capture$scope361(undefined); + let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, scope34$cap; + scope34$cap = new Capture$scope341(undefined); if (cont instanceof Runtime.HandlerContFrame.class) { - scope36$cap.result$0 = cont.handler["constructor"]["name"]; - runtime.safeCall(hl.forEach(lambda$3(scope36$cap, cont))); + scope34$cap.result$0 = cont.handler["constructor"]["name"]; + runtime.safeCall(hl.forEach(lambda$3(scope34$cap, cont))); scrut = runtime.safeCall(vis.has(cont)); if (scrut === true) { tmp = reps + 1; @@ -964,12 +918,12 @@ lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { if (scrut1 === true) { throw runtime.safeCall(globalThis.Error("10 repeated continuation frame (loop?)")) } - tmp1 = scope36$cap.result$0 + ", REPEAT"; - scope36$cap.result$0 = tmp1; + tmp1 = scope34$cap.result$0 + ", REPEAT"; + scope34$cap.result$0 = tmp1; } else { runtime.safeCall(vis.add(cont)); } - tmp2 = scope36$cap.result$0 + " -> "; + tmp2 = scope34$cap.result$0 + " -> "; tmp3 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp2 + tmp3 } @@ -1022,7 +976,7 @@ lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { cur = contTrace.nextHandler; lbl: while (true) { let tmp5; - if (cur instanceof Runtime.HandlerContFrame.class) { + if (cur !== null) { tmp5 = Runtime.showHandlerContChain(cur, hl, vis, 0); runtime.safeCall(globalThis.console.log(tmp5)); cur = cur.nextHandler; @@ -1101,7 +1055,7 @@ lambda5 = (undefined, function (Runtime2, EffectHandle1, value) { lbl: while (true) { let scrut7, scrut8; scrut7 = prevHandlerFrame.nextHandler; - if (scrut7 instanceof Runtime.HandlerContFrame.class) { + if (scrut7 !== null) { scrut8 = scrut7.handler !== cur.handler; if (scrut8 === true) { prevHandlerFrame = scrut7; @@ -1314,12 +1268,11 @@ export { lambda2 as _$_modulePrivate_$_lambda2 }; export { lambda3 as _$_modulePrivate_$_lambda3 }; export { lambda4 as _$_modulePrivate_$_lambda4 }; export { lambda5 as _$_modulePrivate_$_lambda5 }; -export { lambda6 as _$_modulePrivate_$_lambda6 }; export { lambda$ as _$_modulePrivate_$_lambda$ }; export { lambda$1 as _$_modulePrivate_$_lambda$1 }; -export { Capture$scope341 as _$_modulePrivate_$_Capture$scope34 }; +export { Capture$scope321 as _$_modulePrivate_$_Capture$scope32 }; export { lambda$2 as _$_modulePrivate_$_lambda$2 }; -export { Capture$scope361 as _$_modulePrivate_$_Capture$scope36 }; +export { Capture$scope341 as _$_modulePrivate_$_Capture$scope34 }; export { lambda$3 as _$_modulePrivate_$_lambda$3 }; export { lambda$4 as _$_modulePrivate_$_lambda$4 }; export { continuation$ as _$_modulePrivate_$_continuation$ }; diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index efa22dc912..fc9f798996 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -82,7 +82,7 @@ class EffectHandle(_reified: EffectSig) with fun _try(f: () -> Any) = let res = f() - if curEffect is EffectSig as tmp then + if curEffect is ~null as tmp then set curEffect = null EffectHandle(tmp) else res @@ -278,9 +278,9 @@ fun showStackTrace(header: Text, tr: EffectSig, debug: Bool, showLocals: Bool) = curHandler = tr.contTrace atTail = true if debug do - while curHandler is (ContTrace | HandlerContFrame) as handlerFrame do + while curHandler is ~null as handlerFrame do let cur = handlerFrame.next - while cur is FunctionContFrame as frame do + while cur is ~null as frame do let curLocals = frame.getLocals let loc = frame.getLoc let localsMsg = if showLocals and curLocals.length > 0 @@ -291,8 +291,9 @@ fun showStackTrace(header: Text, tr: EffectSig, debug: Bool, showLocals: Bool) = msg += localsMsg cur = frame.next atTail = false - set curHandler = handlerFrame.nextHandler - if curHandler is HandlerContFrame as handlerFrame do + let nextHandler = handlerFrame.nextHandler + set curHandler = nextHandler + if nextHandler is ~null as handlerFrame do set msg += "\n\twith handler " + handlerFrame.handler!constructor!name atTail = false @@ -350,7 +351,7 @@ fun debugContTrace(contTrace) = hl.set("last-handler", new Set([contTrace.lastHandler])) console.log(showFunctionContChain(contTrace.next, hl, vis, 0)) let cur = contTrace.nextHandler - while cur is HandlerContFrame as frame do + while cur is ~null as frame do console.log(showHandlerContChain(frame, hl, vis, 0)) set cur = frame.nextHandler console.log() @@ -414,7 +415,7 @@ fun handleEffect(cur: EffectSig): Any = // debugEff(cur) // find the handle block corresponding to the current effect let prevHandlerFrame = cur.contTrace - while prevHandlerFrame.nextHandler is HandlerContFrame as next and next.handler !== cur.handler do + while prevHandlerFrame.nextHandler is ~null as next and next.handler !== cur.handler do set prevHandlerFrame = next // no matching handle block diff --git a/hkmc2/shared/src/test/mlscript/newres/NegatedPatterns.mls b/hkmc2/shared/src/test/mlscript/newres/NegatedPatterns.mls new file mode 100644 index 0000000000..85980eda74 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/NegatedPatterns.mls @@ -0,0 +1,171 @@ +:. +:js + +// Negation removes only the excluded value, and emits a null test rather than instanceof. +:sjs +fun nullableLength(value: Str | null) = + if value is ~null as text then text.length else 0 +//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— +//│ let nullableLength; +//│ nullableLength = function nullableLength(value) { +//│ if (value === null) { return 0 } +//│ return value.length; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:expect [3, 0] +[nullableLength("abc"), nullableLength(null)] +//│ = [3, 0] + +// Other singleton literals use the same refinement rule. +:expect [3, 0] +fun definedLength(value: Str | undefined) = + if value is ~undefined as text then text.length else 0 +[definedLength("abc"), definedLength(undefined)] +//│ = [3, 0] + +:expect [3, 0] +fun truthyLength(value: Str | false) = + if value is ~false as text then text.length else 0 +[truthyLength("abc"), truthyLength(false)] +//│ = [3, 0] + +// Excluding a class keeps the other union members and their declared fields. +class TextBox(val text: Str) +class Empty + +:expect [3, 0] +fun boxLength(value: TextBox | Empty) = + if value is ~Empty as box then box.text.length else 0 +[boxLength(TextBox("abc")), boxLength(new Empty)] +//│ = [3, 0] + +// De Morgan's laws apply to both alternatives and conjunctions. +:expect [3, 0, 0] +fun presentLength(value: Str | null | undefined) = + if value is ~(null | undefined) as text then text.length else 0 +[presentLength("abc"), presentLength(null), presentLength(undefined)] +//│ = [3, 0, 0] + +:expect [3, 0] +fun nonNullLength(value: Str | null) = + if value is ~(null & _) as text then text.length else 0 +[nonNullLength("abc"), nonNullLength(null)] +//│ = [3, 0] + +:expect [3, 0] +fun stringLength(value: Str | Int) = + if value is ~(~Str) as text then text.length else 0 +[stringLength("abc"), stringLength(42)] +//│ = [3, 0] + +// A negative test of a subclass must retain possible instances of its base class. +class Base(val text: Str) +class Child extends Base("child") + +:expect [3, 0] +fun baseLength(value: Base) = + if value is ~Child as base then base.text.length else 0 +[baseLength(Base("abc")), baseLength(new Child)] +//│ = [3, 0] + +// Field tests and guards can fail even when the constructor matches. +:expect [3, 0] +fun nonEmptyLength(value: TextBox) = + if value is ~TextBox("") as box then box.text.length else 0 +[nonEmptyLength(TextBox("abc")), nonEmptyLength(TextBox(""))] +//│ = [3, 0] + +:expect 3 +fun guardedLength(value: TextBox) = + if value is ~(TextBox where false) as box then box.text.length else 0 +guardedLength(TextBox("abc")) +//│ = 3 + +// A named extractor is not an unconditional nominal test. +pattern Blank = TextBox("") + +:expect 3 +fun extractedLength(value: TextBox) = + if value is ~Blank as box then box.text.length else 0 +extractedLength(TextBox("abc")) +//│ = 3 + +// Excluding null does not remove undefined or manufacture an interface for Any. +:ge +fun stillUndefined(value: Str | null | undefined) = + if value is ~null as text then text.length else 0 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.97: if value is ~null as text then text.length else 0 +//│ ║ ^^^^^^^^^^^ +//│ ╟── Undefined literal does not contain member 'length' +//│ ║ l.96: fun stillUndefined(value: Str | null | undefined) = +//│ ╙── ^^^^^^^^^ + +:ge +fun stillUnknown(value: Any) = + if value is ~TextBox as other then other.text else "" +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.107: if value is ~TextBox as other then other.text else "" +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'text' of a value with unknown shape. +//│ ║ l.106: fun stillUnknown(value: Any) = +//│ ║ ^^^ +//│ ╟── Type 'Any' is declared without a member interface. +//│ ║ in ../decls/Prelude.mls +//│ ║ l.2: declare type Any +//│ ╙── ^^^ + +// An impossible conjunction must not exclude either of its alternatives. +:ge +fun stillNull(value: Str | null) = + if value is ~(null & Str) as text then text.length else 0 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.122: if value is ~(null & Str) as text then text.length else 0 +//│ ║ ^^^^^^^^^^^ +//│ ╟── Null literal does not contain member 'length' +//│ ║ l.121: fun stillNull(value: Str | null) = +//│ ╙── ^^^^ + +// A negated loop condition is checked again after assigning the next link. +:expect [0, 2] +class Link(val next: Link | null) +fun linkCount(head: Link | null) = + let cur = head + let count = 0 + while cur is ~null as node do + set count += 1 + set cur = node.next + count +[linkCount(null), linkCount(Link(Link(null)))] +//│ = [0, 2] + +// Rewritten loops used by effect handlers must also reach the failure exit. +:lift +:effectHandlers +:expect [0, 2] +fun countLinks(head: Link | null) = + let cur = head + let count = 0 + while cur is ~null as node do + set count += 1 + set cur = node.next + count +[countLinks(null), countLinks(Link(Link(null)))] +//│ = [0, 2] + +// A failed negation in an imperative conditional skips its body. +:expect [0, 1] +fun mark(value: TextBox | Empty) = + let count = 0 + if value is ~Empty do set count = 1 + count +[mark(new Empty), mark(TextBox("abc"))] +//│ = [0, 1] + +// A nominal constructor with unrestricted fields can be excluded as a whole. +:expect [0, 3] +fun unboxedLength(value: TextBox | Str) = + if value is ~TextBox(_) as text then text.length else 0 +[unboxedLength(TextBox("abc")), unboxedLength("abc")] +//│ = [0, 3] diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls index de1e03cc59..f445f2130f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls @@ -38,7 +38,7 @@ if 5 is ~a then a //│ ╔══[COMPILATION ERROR] Name not found: a //│ ║ l.37: if 5 is ~a then a //│ ╙── ^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Error: match error if 5 is ~Bool as c then c //│ = 5 From ec5cc9ca6b3fdc86affdd42d60911ffab412fecc Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 2 Oct 2026 11:31:02 +0800 Subject: [PATCH 174/228] Minor --- .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../src/test/mlscript-compile/LazyArray.mls | 2 +- .../src/test/mlscript-compile/Runtime.mjs | 6 ++-- .../src/test/mlscript-compile/Runtime.mls | 30 +++++++++---------- .../test/mlscript/codegen/RuntimeUsage.mls | 16 +++++----- 5 files changed, 27 insertions(+), 29 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 072a7c4714..f3c6510b24 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -558,7 +558,7 @@ object Elaborator: .getOrElse(lastWords(s"Runtime.mls module '${module.nme}' does not define term '$memberName'.")) val tuple = modOrObj("Tuple") - val str = modOrObj("Str") + val str = modOrObj("StrOps") RuntimeSymbols( unit = modOrObj("Unit"), loopEnd = modOrObj("LoopEnd"), diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls index 4f9e4eb5f9..67f6bf11f8 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls @@ -13,7 +13,7 @@ fun enforceArray = case (LazyArr | Array | String | Str | TypedArray) as a then a as Indexable ow then throw Error("Expected an Array, got: " + String(ow)) -fun concatHelper(a: Array[Any], l: Int, ...args: Array[Indexable]) = +fun concatHelper(a: Array, l: Int, ...args: Array[Indexable]) = let idx = 0 init_len = a.length diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index 3d3940b031..dd4ed1f198 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -281,9 +281,9 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "Tuple"]; }); - (class Str { + (class StrOps { static { - Runtime.Str = this + Runtime.StrOps = this } static startsWith(string, prefix) { return runtime.safeCall(string.startsWith(prefix)) @@ -303,7 +303,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { return runtime.safeCall(string.slice(n)) } toString() { return runtime.render(this); } - static [definitionMetadata] = ["class", "Str"]; + static [definitionMetadata] = ["class", "StrOps"]; }); Runtime.render = Rendering.render; (class TraceLogger { diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index fc9f798996..ac79b21c84 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -7,8 +7,6 @@ import "./Iter.mjs" open annotations { pure } -// The runtime also exports a Str helper module; keep the host string type unambiguous. -type Text = Str // Tuple patterns also accept lazy views and other IterableBase instances. type Indexable = {length: Int, at: Int -> Any, slice: Function} @@ -31,10 +29,10 @@ val short_or = RuntimeJS.short_or fun unreachable = throw Error("unreachable") -fun assertFail(file: Text, line: Text) = throw Error("Assertion failed (" + file + ":" + line + ")") +fun assertFail(file: Str, line: Str) = throw Error("Assertion failed (" + file + ":" + line + ")") -fun checkArgs(functionName: Text, expected: Int, isUB: Bool, got: Int) = +fun checkArgs(functionName: Str, expected: Int, isUB: Bool, got: Int) = if got < expected || isUB && got > expected do let name = if functionName.length > 0 then " '" + functionName + "'" else "" // throw globalThis.Error("Function" + name + " expected " @@ -116,22 +114,22 @@ module Tuple with fun isArrayLike(xs) = Iter.isArrayLike(xs) val split = LazyArray.__split -module Str with +module StrOps with @pure - fun startsWith(string: Text, prefix: Text) = string.startsWith(prefix) + fun startsWith(string: Str, prefix: Str) = string.startsWith(prefix) @pure - fun get(string: Text, i: Int) = + fun get(string: Str, i: Int) = if i >= string.length then throw RangeError("Str.get: index out of bounds") else string.at(i) @pure - fun take(string: Text, n: Int) = string.slice(0, n) + fun take(string: Str, n: Int) = string.slice(0, n) @pure - fun leaveOut(string: Text, n: Int) = string.slice(n) + fun leaveOut(string: Str, n: Int) = string.slice(n) // Re-export rendering functions val render = Rendering.render @@ -153,7 +151,7 @@ module TraceLogger with if enabled then set indentLvl = n else () - fun log(msg: Text) = + fun log(msg: Str) = if enabled then console.log("| ".repeat(indentLvl) + msg.replaceAll("\n", "\n" + " ".repeat(indentLvl))) else () @@ -238,9 +236,9 @@ data class EffectSig(contTrace: ContTrace, handler: Any, handlerFun: Function | class NonLocalReturn with fun ret(value) -data class FnLocalsInfo(fnName: Text, locals: Array[LocalVarInfo]) -data class LocalVarInfo(localName: Text, value: Any) -data class CustomStackError(stack: Text) with +data class FnLocalsInfo(fnName: Str, locals: Array[LocalVarInfo]) +data class LocalVarInfo(localName: Str, value: Any) +data class CustomStackError(stack: Str) with fun toString() = stack @@ -272,7 +270,7 @@ fun illegalEffect(position) = set curEffect = null throw CustomStackError(showStackTrace("Error: Effect " + tmp.handler!constructor!name + " is raised " + position, tmp, false, false)) -fun showStackTrace(header: Text, tr: EffectSig, debug: Bool, showLocals: Bool) = +fun showStackTrace(header: Str, tr: EffectSig, debug: Bool, showLocals: Bool) = let msg = header curHandler = tr.contTrace @@ -301,7 +299,7 @@ fun showStackTrace(header: Text, tr: EffectSig, debug: Bool, showLocals: Bool) = set msg += "\n\tat tail position" msg -fun showFunctionContChain(cont: Any, hl: Map[Text, Set[Any]], vis: Set[Any], reps: Int): Text = +fun showFunctionContChain(cont: Any, hl: Map[Str, Set[Any]], vis: Set[Any], reps: Int): Str = if cont is FunctionContFrame as frame then let result = frame!constructor!name + "(pc=" + frame.saved.[1] hl.forEach((m, marker, ...) => if m.has(cont) do set result += ", " + marker) @@ -318,7 +316,7 @@ fun showFunctionContChain(cont: Any, hl: Map[Text, Set[Any]], vis: Set[Any], rep else "(NOT CONT)" -fun showHandlerContChain(cont: Any, hl: Map[Text, Set[Any]], vis: Set[Any], reps: Int): Text = +fun showHandlerContChain(cont: Any, hl: Map[Str, Set[Any]], vis: Set[Any], reps: Int): Str = if cont is HandlerContFrame as frame then let result = frame.handler!constructor!name hl.forEach((m, marker, ...) => if m.has(cont) do set result += ", " + marker) diff --git a/hkmc2/shared/src/test/mlscript/codegen/RuntimeUsage.mls b/hkmc2/shared/src/test/mlscript/codegen/RuntimeUsage.mls index b1d56e52d2..54de37c33e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/RuntimeUsage.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/RuntimeUsage.mls @@ -62,29 +62,29 @@ print of Tuple.get([1, 2, 3, 4], -1) print of Tuple.get([1, 2, 3, 4], 5) //│ ═══[RUNTIME ERROR] RangeError: Tuple.get: index out of bounds -print of Str.leaveOut("hello, world", 2) +print of StrOps.leaveOut("hello, world", 2) //│ > llo, world -print of Str.leaveOut("hello, world", -2) +print of StrOps.leaveOut("hello, world", -2) //│ > ld -print of Str.get("hello, world", 2) +print of StrOps.get("hello, world", 2) //│ > l :re -print of Str.get("hello, world", 22) +print of StrOps.get("hello, world", 22) //│ ═══[RUNTIME ERROR] RangeError: Str.get: index out of bounds -print of Str.get("hello, world", -1) +print of StrOps.get("hello, world", -1) //│ > d -print of Str.startsWith("hello, world", "hello") +print of StrOps.startsWith("hello, world", "hello") //│ > true -print of Str.startsWith("hello, world", "") +print of StrOps.startsWith("hello, world", "") //│ > true -print of Str.startsWith("hello, world", "farewell") +print of StrOps.startsWith("hello, world", "farewell") //│ > false From 87c51a0e7ac7aa07e384d8080656e3e58840b49c Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 11:44:15 +0800 Subject: [PATCH 175/228] Centralize collection read interfaces and make Iterator covariant --- .../src/test/mlscript-compile/LazyArray.mls | 8 +++---- .../test/mlscript-compile/LazyFingerTree.mls | 8 +++---- .../src/test/mlscript-compile/Rendering.mls | 6 +++--- .../src/test/mlscript-compile/Runtime.mls | 12 +++++------ .../src/test/mlscript/decls/Prelude.mls | 9 ++++++-- .../newres/MigratedFixtureInterfaces.mls | 21 +++++++++++++++++++ 6 files changed, 42 insertions(+), 22 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls index 67f6bf11f8..b9b04a5fdc 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls @@ -84,7 +84,7 @@ class View(val underlying: Indexable, val start: Int, val end: Int) extends Lazy fun toString() = "[" + this joined(", ") + "]" object SpliceMarker -class Splice(val bits: Array[Any], val len: Int) extends LazyArr with +class Splice(val bits: Array, val len: Int) extends LazyArr with let materialized = null fun iterator() = @@ -97,7 +97,7 @@ class Splice(val bits: Array[Any], val len: Int) extends LazyArr with SpliceMarker then if uitr === null do set uitr = bits.[idx + 1].(Symbol.iterator)() - let n = (uitr as Iterator[Any]).next() + let n = (uitr as Iterator).next() if n.done then set uitr = null set idx += 2 @@ -135,7 +135,7 @@ class Splice(val bits: Array[Any], val len: Int) extends LazyArr with stack.push([0, vals.at(idx + 1) as Indexable]) arr - fun materialize(): Array[Any] = + fun materialize(): Array = if materialized === null do set materialized = reify materialized @@ -153,8 +153,6 @@ class Splice(val bits: Array[Any], val len: Int) extends LazyArr with module LazyArray with... -// Arrays, strings, and lazy views share this indexable interface. -type Indexable = {length: Int, at: Int -> Any, slice: Function} fun mk(...args) = View of args, 0, 0 diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls index 5ea82b2942..fd93bfe4ba 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls @@ -1,10 +1,8 @@ #lang(0.3.x) import "./Iter.mls" -import "./LazyArray.mls" import "./FingerTreeList.mls" open Iter -open LazyArray { Indexable } open LazyFingerTree open FingerTreeList @@ -12,7 +10,7 @@ fun normIdx(i, len) = if i < 0 then len + i else i -fun concatHelper(a: Array[Any], l: Int, ...args: Array[Indexable]) = +fun concatHelper(a: Array, l: Int, ...args: Array[Indexable]) = let idx = 0 let init_len = a.length set a.length = init_len + args.length * 2 @@ -83,7 +81,7 @@ class View(val underlying: Indexable, val start: Int, val end: Int) extends Lazy fun toString() = "[" + this joined(", ") + "]" object SpliceMarker -class Splice(val bits: Array[Any], val len: Int) extends LazyFT with +class Splice(val bits: Array, val len: Int) extends LazyFT with let materialized = null fun iterator() = materialize().iterator() @@ -115,7 +113,7 @@ class Splice(val bits: Array[Any], val len: Int) extends LazyFT with else stack.push([0, next as Indexable]) acc - fun materialize(): {length: Int, at: Int -> Any, slice: Function, iterator: () -> Iterator[Any]} = + fun materialize(): IterableIndexable = if materialized == null do set materialized = reify materialized diff --git a/hkmc2/shared/src/test/mlscript-compile/Rendering.mls b/hkmc2/shared/src/test/mlscript-compile/Rendering.mls index 71511663ff..4e05be4deb 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Rendering.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Rendering.mls @@ -119,7 +119,7 @@ fun render(target, ...args): Str = let prefix = if index === 0 then "" else indents prefix + line + postfix .join("\n") - fun renderArray(array: Array[Any], level, keyLength, startPos) = + fun renderArray(array: Array, level, keyLength, startPos) = let i = 0 length = array.length @@ -154,7 +154,7 @@ fun render(target, ...args): Str = valueStr else throw new Error("All items in this array has been rendered.") renderSequence of done, next, level, keyLength, startPos, symbolsForArray - fun renderSet(theSet: Set[Any], level, keyLength, startPos) = + fun renderSet(theSet: Set, level, keyLength, startPos) = let iterator = theSet.values() peek = iterator.next() @@ -271,7 +271,7 @@ fun render(target, ...args): Str = skipNonEnumerable() renderRecordLike of done, next, level, keyLength, startPos, symbolsForObject, isRefinement - fun renderMap(map: Map[Any, Any], level, keyLength, startPos) = + fun renderMap(map: Map, level, keyLength, startPos) = let iterator = map.entries() peek = iterator.next() diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index ac79b21c84..1d8c37a5c6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -7,8 +7,6 @@ import "./Iter.mjs" open annotations { pure } -// Tuple patterns also accept lazy views and other IterableBase instances. -type Indexable = {length: Int, at: Int -> Any, slice: Function} module Runtime with ... @@ -173,7 +171,7 @@ mut val resumePc = -1 object FatalEffect object PrintStackEffect -data class FunctionContFrame(next: FunctionContFrame | null, saved: Array[Any]) with +data class FunctionContFrame(next: FunctionContFrame | null, saved: Array) with fun resume(value) = let i = 0 @@ -197,7 +195,7 @@ data class FunctionContFrame(next: FunctionContFrame | null, saved: Array[Any]) resumeIdx = currentArgList + argListLength + 2 f.apply(saved.[4], saved.slice(currentArgList + 1, currentArgList + 1 + argListLength)) fun getLocals: Array[LocalVarInfo] = - let debugInfo = saved.[3] as Array[Any] + let debugInfo = saved.[3] as Array let i = 0 let cur = 6 while i < saved.[5] do @@ -210,7 +208,7 @@ data class FunctionContFrame(next: FunctionContFrame | null, saved: Array[Any]) res.push(new LocalVarInfo(debugInfo.[i + 1], saved.[cur + 1 + debugInfo.[i]])) set i += 2 res - fun getNme = (saved.[3] as Array[Any]).[0] + fun getNme = (saved.[3] as Array).[0] fun getLoc = let loc = saved.[2] if @@ -299,7 +297,7 @@ fun showStackTrace(header: Str, tr: EffectSig, debug: Bool, showLocals: Bool) = set msg += "\n\tat tail position" msg -fun showFunctionContChain(cont: Any, hl: Map[Str, Set[Any]], vis: Set[Any], reps: Int): Str = +fun showFunctionContChain(cont: Any, hl: Map[Str, Set], vis: Set, reps: Int): Str = if cont is FunctionContFrame as frame then let result = frame!constructor!name + "(pc=" + frame.saved.[1] hl.forEach((m, marker, ...) => if m.has(cont) do set result += ", " + marker) @@ -316,7 +314,7 @@ fun showFunctionContChain(cont: Any, hl: Map[Str, Set[Any]], vis: Set[Any], reps else "(NOT CONT)" -fun showHandlerContChain(cont: Any, hl: Map[Str, Set[Any]], vis: Set[Any], reps: Int): Str = +fun showHandlerContChain(cont: Any, hl: Map[Str, Set], vis: Set, reps: Int): Str = if cont is HandlerContFrame as frame then let result = frame.handler!constructor!name hl.forEach((m, marker, ...) => if m.has(cont) do set result += ", " + marker) diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 2a2d20d7b3..911f91ce0d 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -37,7 +37,7 @@ declare module BigInt declare class Function with val name: Str fun bind(thisArg, ...args): Function - fun apply(thisArg, args: Array[Any]): Any + fun apply(thisArg, args: Array): Any declare module Function declare class String declare fun String(...args): Str @@ -72,7 +72,7 @@ declare class Map[K, V] with fun values(): Iterator[V] fun entries(): Iterator[[K, V]] fun forEach(f: (V, K, Map[K, V]) -> Any): undefined -declare class Iterator[T] with +declare class Iterator[out T] with fun next(): { value: T, done: Bool } declare module Map declare class WeakSet[V] @@ -503,3 +503,8 @@ declare class HTMLElement with val classList: dyn //│ ———————————————————————————————————————————————————————————————————————————————— + +// Shared read interfaces for arrays, strings, and lazy views. Element types are +// covariant, so using these interfaces does not widen an invariant collection. +type Indexable[out A] = {length: Int, at: Int -> A, slice: Function} +type IterableIndexable[out A] = {length: Int, at: Int -> A, slice: Function, iterator: () -> Iterator[A]} diff --git a/hkmc2/shared/src/test/mlscript/newres/MigratedFixtureInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/MigratedFixtureInterfaces.mls index f4c52d1f8b..6cde48ae71 100644 --- a/hkmc2/shared/src/test/mlscript/newres/MigratedFixtureInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/MigratedFixtureInterfaces.mls @@ -70,3 +70,24 @@ Term.show(Term.Lam([termSymbol], Term.Ref(termSymbol)), termContext) :expect "undefined" Term.showPattern(Term.LitPattern(undefined), termContext) //│ = "undefined" + +// Shared read interfaces retain element types without invariant Array[Any] annotations. +:expect [4, 5, 6] +class Item(val value: Int) +fun firstValue(xs: Indexable[Item]) = xs.at(0).value +fun iteratedValue(xs: IterableIndexable[Item]) = xs.iterator().next().value.value +[firstValue([Item(4)]), firstValue(LazyFingerTree.mk(Item(5))), + iteratedValue(LazyFingerTree.concat([Item(6)]).materialize())] +//│ = [4, 5, 6] + +// Iterator is covariant: a more specific iterator remains usable through its base interface. +:expect 7 +class SpecialItem extends Item(7) +fun iteratorValue(xs: Iterator[Item]) = xs.next().value.value +val items: Set[SpecialItem] = new Set +items.add(new SpecialItem) +val specialized: Iterator[SpecialItem] = items.values() +iteratorValue(specialized) +//│ = 7 +//│ items = Set(1) {SpecialItem { value: 7 }} +//│ specialized = Iterator {} From 384f271f4682216bc8832c95ecfc9b6ac7805753 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 11:58:35 +0800 Subject: [PATCH 176/228] Merge record intersection interfaces through deferred field types --- .../test/scala/hkmc2/TypeRelationTest.scala | 26 +++++ .../scala/hkmc2/semantics/NewResolver.scala | 78 +++++++++++--- .../main/scala/hkmc2/semantics/Shape.scala | 17 +-- .../src/test/mlscript/decls/Prelude.mls | 2 +- .../mlscript/newres/RecordIntersections.mls | 100 ++++++++++++++++++ 5 files changed, 204 insertions(+), 19 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/RecordIntersections.mls diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 2b71cc3c32..62cdb7663d 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -539,3 +539,29 @@ class TypeRelationTest extends AnyFunSuite: assert(exporter.isEmpty) assert(output.currentShapes.isEmpty) detach() + + test("recursive record intersections reuse type views and stop adding graph edges"): + // Diff tests check member behavior; this measures graph growth and object + // reuse after saturation, which their golden output cannot observe. + val h = new Harness + import h.given + val left = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + val right = new TypeResolution(Term.UnitVal(), _ => fail("Unexpected type error")) + def recursive(resolution: TypeResolution): Unit = + val next = RcdField.signature(Term.Lit(Tree.StrLit("next")), Term.UnitVal()) + val source: Term.Rcd = Term.Rcd(false, List(next)) + resolution.publish(TypeShape.Record(source, List(next -> resolution))) + recursive(left) + recursive(right) + val intersection = h.tpe(TypeShape.Intersection(left, right)) + def record(tpe: DeclaredType): RecordTypeShape = h.observe(ContextualType(tpe, Nil)).toList match + case (value: RecordTypeShape) :: Nil => value + case _ => fail("Expected one merged record") + val first = record(intersection) + val next = first.fields("next") + val saturated = record(next) + val counts = (left.inferenceHost.listeners.size, right.inferenceHost.listeners.size) + (1 to 1000).foreach: _ => + assert(record(saturated.fields("next")) eq saturated) + assert((left.inferenceHost.listeners.size, right.inferenceHost.listeners.size) == counts) + assert(h.state.allocatedTypeInstanceCount == 0) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index c02f495506..d031654d44 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -867,9 +867,7 @@ class NewResolver: nominal.bindings.map((parameter, bound) => parameter -> bound.instantiate(instances)) nominal.copy(bindings = bindings, parent = nominal.parent.map(instantiateShape(_, instances)))(nominal.annotation)(this) case record: RecordTypeShape => - val bindings = instances.map((parameter, instance) => parameter -> instanceType(instance)) ++ - record.bindings.map((parameter, bound) => parameter -> bound.instantiate(instances)) - record.copy(bindings = bindings) + RecordTypeShape(record.fields.map((name, tpe) => name -> tpe.instantiate(instances)))(record.source, record.declarations) case contextual: ContextualShape => contextual.copy(instances = instances.withOverrides(contextual.instances)) case specialized: SpecializedShape => specialized.copy( arguments = specialized.arguments.map(_.instantiate(instances)), instances = instances.withOverrides(specialized.instances)) @@ -902,6 +900,29 @@ class NewResolver: ShapeParts(Marked(declaration, marks), instances, S(arguments)) case source: CoreShape => ShapeParts(Marked(source, marks), TypeSubstitution.empty, N) + /** Merge only the outer fields, retaining symbolic references at every depth. + * Shared names use the same interned Boolean combinations as written types; + * recursively selecting a merged field therefore returns to existing typeViews. + * No merged shape is embedded in another or used as a new type atom. A finite + * set of regular field references yields finitely many normalized field types. + * The enclosing typeViews host caches the observation, and combinedTypes caches + * field combinations; a separate cache keyed by whole record sets is unnecessary. + */ + private def intersectViews(values: Ls[TermShape])(publish: Listener)(using NewResolverState): Unit = + val records = values.collect: + case Marked(record: RecordTypeShape, marks) => + RecordTypeShape(record.fields.map((name, tpe) => name -> transportType(tpe, marks :: Nil)))(record.source, record.declarations) + records.headOption.foreach: first => + val merged = records.tail.foldLeft(first): (left, right) => + val fields = right.fields.foldLeft(left.fields): + case (fields, (name, tpe)) => fields.updated(name, fields.get(name).fold(tpe)(previous => + combinedType(previous.resolution, previous, tpe, false))) + RecordTypeShape(fields)(left.source, right.declarations ++ left.declarations) + publish(merged) + values.distinct.foreach: + case Marked(_: RecordTypeShape, _) => () + case other => publish(other) + private def listenTypeViews(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = // Equal types can be observed through different input-only arguments. Reuse // their semantic host, but report this observation's witness rather than the @@ -923,6 +944,12 @@ class NewResolver: def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = typeBindings(current, args, params) def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), noTypeArguments, aliases, publish) + // Each type can expose several union alternatives. Form products + // within a conjunction, never across separate union clauses. + def conjunction(remaining: Ls[DeclaredType], values: Ls[TermShape])(using NewResolverState): Unit = remaining match + case head :: tail => follow(head, noTypeArguments, aliases, + value => conjunction(tail, value :: values)) + case Nil => intersectViews(values.reverse)(publish) shape match case TypeShape.Dynamic => publish(DynShape()) case TypeShape.Top => publish(OpaqueTypeShape(tpe.resolution.source)( @@ -1018,11 +1045,18 @@ class NewResolver: case TypeShape.Tuple(fields) => publish(TupleShape(current.resolution.source, fields.map(field => TupleShape.TypedField(declaredType(field, current), Nil)))(this)) - case TypeShape.Record(source, fields) => publish(RecordTypeShape(source, fields, effectiveBindings(current), current.positive)) + case TypeShape.Record(source, fields) => + publish(RecordTypeShape(fields.map((field, sign) => field.sym.nme -> declaredType(sign, current)).toMap)( + source, fields.map((field, _) => field.sym.nme -> field).toMap)) case TypeShape.Union(left, right) => next(left); next(right) - case TypeShape.Intersection(left, right) => next(left); next(right) - case TypeShape.Combined(formula) => formula.orderedAtoms.foreach: atom => - follow(atom.instantiate(current.instances), noTypeArguments, aliases, publish) + case TypeShape.Intersection(left, right) => + conjunction(declaredType(left, current) :: declaredType(right, current) :: Nil, Nil) + case TypeShape.Combined(formula) => + // Choose witnesses in source order, independently of set hashing. + val clauses = formula.clauses.toList.sortWith((left, right) => + formula.orderedAtoms.find(atom => left(atom) != right(atom)).exists(left)) + clauses.foreach: clause => + conjunction(formula.orderedAtoms.filter(clause).map(_.instantiate(current.instances)).toList, Nil) case TypeShape.Negation(_) => publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.Negated(current.resolution.source))) case TypeShape.Unit | TypeShape.Abstract => publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.Unavailable(current.resolution.source))) @@ -1334,7 +1368,14 @@ class NewResolver: case NoShape => () case _ => () case _ => () - case TypeShape.Record(_, fields) => observe(constrainRecord(fields, effectiveBindings(tpe), tpe.positive, _, marks)) + case TypeShape.Intersection(left, right) => + follow(declaredType(left, tpe), noTypeArguments, captures, next) + follow(declaredType(right, tpe), noTypeArguments, captures, next) + // Every conjunct is required. Disjunctions need alternative matching; + // propagating to every branch would instead impose their intersection. + case TypeShape.Combined(formula) if formula.clauses.size == 1 => + formula.orderedAtoms.foreach(atom => follow(atom.instantiate(tpe.instances), noTypeArguments, captures, next)) + case TypeShape.Record(_, fields) => observe(constrainRecord(fields.map((field, sign) => field -> declaredType(sign, tpe)), _, marks)) case TypeShape.Function(_, _) => observe(constrainFunction(tpe, _, marks)) case TypeShape.Polymorphic(_, _, body) => follow(declaredType(body, tpe), args, captures, next) @@ -1406,10 +1447,9 @@ class NewResolver: * argument inference and callback checking without refining the annotation * with additional properties from an actual record. */ - private def constrainRecord(fields: Ls[(RcdField, TypeResolution)], bindings: Map[VarSymbol, DeclaredType], positive: Bool, + private def constrainRecord(fields: Ls[(RcdField, DeclaredType)], actual: TermShape, marks: Ls[Marks])(using NewResolverState): Unit = - fields.foreach: (field, sign) => - val expected = declaredType(sign, bindings, positive) + fields.foreach: (field, expected) => val flow = rstate.fieldProjectionSite(field.sym) def receive(value: TermShape)(using NewResolverState): Unit = inferTypeArguments(expected, value, marks) def member(info: MemberLookup, instances: TypeSubstitution)(using NewResolverState): Unit = info match @@ -1422,6 +1462,8 @@ class NewResolver: transportShape(instantiateShape(value, instances), inner) match case value: TermShape => receive(value) case NoShape => () + case MemberLookup.Typed(_, tpe, inner) => + receive(InstanceShape(transportType(tpe.instantiate(instances), inner))) case MemberLookup.Dynamic(inner) => DynShape().exit(inner) match case value: TermShape => receive(value) case NoShape => () @@ -1485,7 +1527,7 @@ class NewResolver: case _ => matchLists(ret, tail) case Nil => () case Marked(record: RecordTypeShape, _) => - constrainRecord(record.fields, record.bindings, record.positive, actual, marks) + constrainRecord(record.fields.toList.map((name, tpe) => record.declarations(name) -> tpe), actual, marks) case _ => () actual match case Marked(_: UnknownValueShape, _) => @@ -2465,6 +2507,11 @@ class NewResolver: DeclaredSymShape(member, flow, marks, bindings, annotation, positive)) publishViewed(host, shape, instances) + private def publishTyped(host: NewResolvable & ShapeHost, field: RcdField, tpe: DeclaredType, + marks: Ls[Marks], instances: TypeSubstitution)(using NewResolverState): Unit = + rstate.recordResolution(host, host.resolvedTargets.contains(field.tsym))(host.resolvedTargets ::= field.tsym) + publishActivated(host, InstanceShape(transportType(tpe.instantiate(instances), marks))) + private def publishDynamic(host: NewResolvable & ShapeHost, marks: Ls[Marks])(using NewResolverState): Unit = DynShape().exit(marks) match case shape: TermShape => @@ -2493,6 +2540,10 @@ class NewResolver: val candidate = prefix -> member rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) publishDeclared(ref, member, ref.resSym, bindings, marks, annotation, positive, instances) + case MemberLookup.Typed(field, tpe, marks) if rstate.canResolve(ref) || ref.resolvedMembers.contains(prefix -> field.sym) => + val candidate = prefix -> field.sym + rstate.recordResolution(ref, ref.resolvedMembers.contains(candidate))(ref.resolvedMembers ::= candidate) + publishTyped(ref, field, tpe, marks, instances) case MemberLookup.Indexed(_, _) if rstate.canResolve(ref) => resolError(ref, msg"Tuple elements must be selected by index." -> ref.toLoc :: Nil) case MemberLookup.Dynamic(marks) if rstate.canResolve(ref) || ref.dynamicPrefixes.contains(prefix) => @@ -2577,6 +2628,9 @@ class NewResolver: case MemberLookup.Declared(member, bindings, marks, annotation, positive) if rstate.canResolve(sel) || sel.resolvedMembers.contains(member) => rstate.recordResolution(sel, sel.resolvedMembers.contains(member))(sel.resolvedMembers ::= member) publishDeclared(sel, member, sel.resSym, bindings, marks, annotation, positive, instances) + case MemberLookup.Typed(field, tpe, marks) if rstate.canResolve(sel) || sel.resolvedMembers.contains(field.sym) => + rstate.recordResolution(sel, sel.resolvedMembers.contains(field.sym))(sel.resolvedMembers ::= field.sym) + publishTyped(sel, field, tpe, marks, instances) case MemberLookup.Indexed(fields, marks) if rstate.canResolve(sel) || sel.tupleIndex == sel.id.name.toIntOption => val index = sel.id.name.toIntOption softAssert(index.exists(_ >= 0), "Tuple lookup must identify a nonnegative index") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index f2253ef4b5..6ef5821767 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -330,6 +330,8 @@ enum MemberLookup: // Declared members expose signatures only. Their marks transport dependent // type arguments; they never authorize reading an implementation's value flow. case Declared(member: BlockMemberSymbol, bindings: Map[VarSymbol, DeclaredType], marks: Ls[Marks], annotation: Opt[Term], positive: Bool) + // A structural field already has its lexical bindings and intersection applied. + case Typed(field: RcdField, tpe: DeclaredType, marks: Ls[Marks]) // A preceding spread can place several different fields at the same index. case Indexed(fields: Ls[TupleShape.Fixed], marks: Ls[Marks]) case Dynamic(marks: Ls[Marks]) @@ -340,6 +342,7 @@ enum MemberLookup: case Contextual(source, instances) => Contextual(source.withMarks(marks), instances) case Found(member, inner) => Found(member, inner ::: marks) case Declared(member, bindings, inner, annotation, positive) => Declared(member, bindings, inner ::: marks, annotation, positive) + case Typed(field, tpe, inner) => Typed(field, tpe, inner ::: marks) case Indexed(fields, inner) => Indexed(fields, inner ::: marks) case Dynamic(inner) => Dynamic(inner ::: marks) case _ => this @@ -455,15 +458,17 @@ class BaseShape(val defn: ClassLikeDef, val ext: Opt[TermShape]) extends CoreHea /** A structural annotation exposes only its declared fields. Their symbols belong * to the annotation, not to whichever record happens to be passed by a caller. + * Field types are deferred references, including for recursive intersections. + * Source declarations are witnesses for diagnostics and property selection, not + * part of semantic equality: a merged field may have several such witnesses. */ -final case class RecordTypeShape(source: Term.Rcd, fields: Ls[(RcdField, TypeResolution)], - bindings: Map[VarSymbol, DeclaredType], positive: Bool) extends CoreHeadShape: +final case class RecordTypeShape(fields: Map[Str, DeclaredType])( + val source: Term.Rcd, val declarations: Map[Str, RcdField]) extends CoreHeadShape: def describe: Str = "record type" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - fields.reverseIterator.collectFirst { - case (field, _) if field.sym.nme == name => MemberLookup.Declared(field.sym, bindings, Nil, S(source), positive) - }.getOrElse(MemberLookup.Missing) + fields.get(name).fold[MemberLookup](MemberLookup.Missing)(tpe => + MemberLookup.Typed(declarations(name), tpe, Nil)) /** An instance described by a type, in either position of a constraint. Keep the * type reference intact during transport: expanding it to its current positive @@ -880,7 +885,7 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] case Missing => N case Found(member: RecordMember, inner) => S(Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil)) - case Found(_: BlockMemberSymbol, _) | Declared(_, _, _, _, _) | Indexed(_, _) => + case Found(_: BlockMemberSymbol, _) | Declared(_, _, _, _, _) | Typed(_, _, _) | Indexed(_, _) => // Record spreads recursively look up RecordShapes, which only create RecordMembers. lastWords("Record lookup returned a nominal member") spread(shape.getMember(name)).getOrElse(loop(rest)) diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 911f91ce0d..e35c8747bc 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -507,4 +507,4 @@ declare class HTMLElement with // Shared read interfaces for arrays, strings, and lazy views. Element types are // covariant, so using these interfaces does not widen an invariant collection. type Indexable[out A] = {length: Int, at: Int -> A, slice: Function} -type IterableIndexable[out A] = {length: Int, at: Int -> A, slice: Function, iterator: () -> Iterator[A]} +type IterableIndexable[out A] = Indexable[A] & {iterator: () -> Iterator[A]} diff --git a/hkmc2/shared/src/test/mlscript/newres/RecordIntersections.mls b/hkmc2/shared/src/test/mlscript/newres/RecordIntersections.mls new file mode 100644 index 0000000000..e451f8aa7c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/RecordIntersections.mls @@ -0,0 +1,100 @@ +:. + +// Aliases contribute complementary fields, and common fields retain both types. +:expect ["LEFT", 3, 4, "RIGHT"] +type Left[A] = {left: A, both: {x: Int}} +type Right = {right: Int, both: {y: Str}} +fun read(value: Left[Str] & Right) = + [value.left.toUpperCase(), value.right, value.both.x, value.both.y.toUpperCase()] +read(left: "left", right: 3, both: {x: 4, y: "right"}) +//│ = ["LEFT", 3, 4, "RIGHT"] + +// Grouping, repetition, and operand order do not change the interface. +:expect [1, 2, 3] +type A = {a: Int} +type B = {b: Int} +type C = {c: Int} +fun regroup(value: (A & B) & (C & (B & A))) = [value.a, value.b, value.c] +regroup(a: 1, b: 2, c: 3) +//│ = [1, 2, 3] + +// Distribution preserves union alternatives instead of combining their fields. +:expect ["L", 5] +type Choice = ({left: Str} | {right: Int}) & {tag: Str, common: Int} +fun common(value: Choice) = [value.tag.toUpperCase(), value.common] +common(left: "left", common: 5, tag: "l") +//│ = ["L", 5] + +:e +fun missing(value: Choice) = value.left +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.29: fun missing(value: Choice) = value.left +//│ ║ ^^^^^^^^^^ +//│ ╟── Record type does not contain member 'left' +//│ ║ l.23: type Choice = ({left: Str} | {right: Int}) & {tag: Str, common: Int} +//│ ╙── ^^^^^^^^^^ + +// Common fields may themselves contain unions of record intersections. +:expect "YES" +type Nested = ({item: ({x: Int} | {z: Bool})} & {item: {y: Str}}) +fun nested(value: Nested) = value.item.y.toUpperCase() +nested(item: {x: 1, y: "yes"}) +//│ = "YES" + +// Productive mutual recursion is observed one field at a time. +:expect [1, "END"] +type X = {next: X, x: Int} +type Y = {next: Y, y: Str} +fun descend(value: X & Y) = [value.next.next.x, value.next.next.y.toUpperCase()] +class Link(val x: Int, val y: Str) with + val next = this +descend(Link(1, "end")) +//│ = [1, "END"] + +// Recursive Boolean substitutions must stabilize rather than nest merged shapes. +type Loop[A, B] = {value: A, next: Loop[A & B, B & A]} +fun cycle(value: Loop[{x: Int}, {y: Str}]) = + [value.next.next.next.value.x, value.next.next.next.value.y.toUpperCase()] + +// The annotation remains an abstraction boundary. +:e +fun hidden(value: A & B) = value.extra +hidden(a: 1, b: 2, extra: 3) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.61: fun hidden(value: A & B) = value.extra +//│ ║ ^^^^^^^^^^^ +//│ ╟── Record type does not contain member 'extra' +//│ ║ l.14: type A = {a: Int} +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A wildcard open reads a single runtime field even when two records declare it. +:expect 7 +fun opened(value: {shared: Int} & {shared: Int, extra: Bool}) = + open value + shared +opened(shared: 7, extra: true) +//│ = 7 + +// Intersection constraints infer generic fields and check callback parameters. +:expect "TYPED" +fun extract[T](value: {item: T} & {tag: Int}): T = value.item +extract(item: "typed", tag: 0).toUpperCase() +//│ = "TYPED" + +:expect "CALLBACK" +fun callback(value: {run: Str -> Str} & {tag: Int}) = value.run("callback") +callback(run: x => x.toUpperCase(), tag: 0) +//│ = "CALLBACK" + +:e +fun invalidCallback(): {run: Object -> Int} & {tag: Int} = + {run: x => x.secret, tag: 0} +invalidCallback() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.92: {run: x => x.secret, tag: 0} +//│ ║ ^^^^^^^^ +//│ ╟── Value of type 'Object' does not contain member 'secret' +//│ ║ l.91: fun invalidCallback(): {run: Object -> Int} & {tag: Int} = +//│ ╙── ^^^^^^ +//│ = {run: fun, tag: 0} From bb03a2d6009f21cbd10fa0f9bc111eff7d8a117d Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 13:53:13 +0800 Subject: [PATCH 177/228] Exclude compiler setup from concurrent import timeout Prepare the shared prelude and runtime before timing the two uncached root compilations. Keep the opposite-lock barrier and ten-second deadlock deadline, and report worker states and stacks when that deadline expires. Restore unrelated blank-line spacing found while reviewing the full PR diff. Validation: ctest, CompilerCacheTest, and hkmc2AllTests/test (1,057 tests). An injected eleven-second prelude read fails before the change and passes after it. Temporarily bypassing cycle rejection still produces the expected timeout with both workers blocked on artifact locks. No golden outputs changed. --- .../test/scala/hkmc2/CompilerCacheTest.scala | 22 +++++++++++++++++-- .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../src/test/mlscript-compile/LazyArray.mls | 1 - .../src/test/mlscript-compile/Runtime.mls | 1 - .../src/test/scala/hkmc2/MLsDiffMaker.scala | 1 + 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala index e0a2a344b4..152437ee91 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala @@ -1,6 +1,6 @@ package hkmc2 -import java.util.concurrent.{ConcurrentLinkedQueue, CountDownLatch, Executors, ThreadFactory, TimeUnit} +import java.util.concurrent.{ConcurrentLinkedQueue, CountDownLatch, Executors, ThreadFactory, TimeUnit, TimeoutException} import java.util.concurrent.atomic.AtomicInteger import collection.concurrent.TrieMap import scala.concurrent.{Await, ExecutionContext, Future} @@ -10,6 +10,7 @@ import scala.jdk.CollectionConverters.* import org.scalatest.funsuite.AnyFunSuite import hkmc2.CompilerCache.{ActiveDependencyGraph, ArtifactCache} +import hkmc2.utils.TraceLogger class CompilerCacheTest extends AnyFunSuite: @@ -137,9 +138,11 @@ class CompilerCacheTest extends AnyFunSuite: def getLastChangedTimestamp(path: io.Path): Long = underlyingFs.getLastChangedTimestamp(path) + val workerThreads = new ConcurrentLinkedQueue[Thread] val threadFactory: ThreadFactory = runnable => val thread = new Thread(runnable, "compiler-cache-deadlock-reproducer") thread.setDaemon(true) + workerThreads.add(thread) thread val executor = Executors.newFixedThreadPool(2, threadFactory) val executionContext = ExecutionContext.fromExecutorService(executor) @@ -153,6 +156,17 @@ class CompilerCacheTest extends AnyFunSuite: Config.default(TestFolders.mainTestDir(os.pwd)), ) + // Compile shared prerequisites before starting the deadlock deadline: a cold prelude and + // runtime can take longer than it under CI load. A and B remain uncached, so the barrier + // still forces their builds to hold opposite path locks before either requests its import. + locally: + given DebugPrinter = new DebugPrinter + given TraceLogger = new TraceLogger: + override def doTrace: Boolean = false + given Raise = diagnostic => fail(s"Unexpected setup diagnostic: ${diagnostic.theMsg}") + val prelude = CompilerCtx.get.getPrelude(paths.preludeFile).ctx + CompilerCtx.get.getElaboratedBlock(paths.runtimeSourceFile, prelude) + def compile(file: io.Path): Future[Unit] = Future({ val compiler = MLsCompiler(mkRaise = _ => diagnostic => diagnostics.add(diagnostic) @@ -160,7 +174,11 @@ class CompilerCacheTest extends AnyFunSuite: compiler.compileModule(file) })(using executionContext) - Await.result(Future.sequence(List(compile(fileA), compile(fileB))), 10.seconds) + try Await.result(Future.sequence(List(compile(fileA), compile(fileB))), 10.seconds) + catch case timeout: TimeoutException => + val workerStacks = workerThreads.asScala.map: thread => + s"${thread.getName}: ${thread.getState}\n${thread.getStackTrace.mkString("\n")}" + fail(s"Concurrent circular imports did not finish:\n${workerStacks.mkString("\n\n")}", timeout) assert(diagnostics.asScala.exists(_.theMsg.contains("Circular imports")), "A circular import diagnostic should be reported") finally diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f3c6510b24..72ccf2987a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -751,7 +751,7 @@ extends Importer: lazy val illegalMemberNameTail = msg"Member names must start with a letter or underscore, followed by letters, digits, or underscores." -> N :: Nil - + def mkLetBinding(sym: LocalVarSymbol | TermSymbol, rhs: Term, annotations: Ls[Annot])(loc: Opt[Loc]): Ls[Statement] = LetDecl(sym, annotations)(loc) :: defineVar(sym, rhs)(loc) :: Nil diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls index b9b04a5fdc..3f1f45870c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls @@ -154,7 +154,6 @@ class Splice(val bits: Array, val len: Int) extends LazyArr with module LazyArray with... - fun mk(...args) = View of args, 0, 0 fun concat(...args: Array[Indexable]) = concatHelper(mut [], 0, ...args) fun dropLeftRight(beg: Int, fin: Int)(xs: Indexable) = diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 1d8c37a5c6..34955316c2 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -7,7 +7,6 @@ import "./Iter.mjs" open annotations { pure } - module Runtime with ... diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 00755f80e4..b1a8283e23 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -500,6 +500,7 @@ abstract class MLsDiffMaker extends DiffMaker: doc" #{ ${e.showTopLevel(using flowScp)} #} ".mkString(output.ColWidth) processTerm(e, inImport = false) + def processTerm(trm: semantics.Term.Blk, inImport: Bool)(using Config, Raise): Unit = From 06241b2bc329e88c42182dce9bf56435c05566c2 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 14:04:49 +0800 Subject: [PATCH 178/228] Preserve cold setup in the concurrent import regression Keep prelude and runtime initialization inside the concurrent compilations. Preparing them on the test thread changes the execution being tested and could conceal a cold-initialization concurrency failure. Retain the worker stack diagnostics and the original ten-second deadline. Local measurements show cold prelude setup at 0.47 seconds and runtime compilation at 0.80 seconds. An injected delay demonstrates the timeout's scope, but does not establish the cause of the original CI failure. Validation: ctest, eight fresh-JVM runs of all 97 JVM tests with two available processors, and hkmc2AllTests/test (1,057 tests). No golden outputs changed. --- .../jvm/src/test/scala/hkmc2/CompilerCacheTest.scala | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala index 152437ee91..47bdb651ed 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala @@ -10,7 +10,6 @@ import scala.jdk.CollectionConverters.* import org.scalatest.funsuite.AnyFunSuite import hkmc2.CompilerCache.{ActiveDependencyGraph, ArtifactCache} -import hkmc2.utils.TraceLogger class CompilerCacheTest extends AnyFunSuite: @@ -156,17 +155,6 @@ class CompilerCacheTest extends AnyFunSuite: Config.default(TestFolders.mainTestDir(os.pwd)), ) - // Compile shared prerequisites before starting the deadlock deadline: a cold prelude and - // runtime can take longer than it under CI load. A and B remain uncached, so the barrier - // still forces their builds to hold opposite path locks before either requests its import. - locally: - given DebugPrinter = new DebugPrinter - given TraceLogger = new TraceLogger: - override def doTrace: Boolean = false - given Raise = diagnostic => fail(s"Unexpected setup diagnostic: ${diagnostic.theMsg}") - val prelude = CompilerCtx.get.getPrelude(paths.preludeFile).ctx - CompilerCtx.get.getElaboratedBlock(paths.runtimeSourceFile, prelude) - def compile(file: io.Path): Future[Unit] = Future({ val compiler = MLsCompiler(mkRaise = _ => diagnostic => diagnostics.add(diagnostic) From 6c20c195f6b4469db60ae5d8c1c866f0dfe5288e Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 14:15:40 +0800 Subject: [PATCH 179/228] Allow fifteen-second compiler cache waits on CI Use fifteen seconds for worker startup barriers and compilation completion when the CI environment variable is present. Preserve the local five-second startup and ten-second completion limits, and retain cold initialization and worker stack diagnostics. Validation: Metals compilation, ctest, and hkmc2AllTests/test with CI=true. --- .../test/scala/hkmc2/CompilerCacheTest.scala | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala index 47bdb651ed..6fabb90896 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/CompilerCacheTest.scala @@ -15,6 +15,11 @@ import hkmc2.CompilerCache.{ActiveDependencyGraph, ArtifactCache} class CompilerCacheTest extends AnyFunSuite: private given ExecutionContext = ExecutionContext.global + // Allow more time for both worker startup and completion on shared CI runners. + private val (startupTimeout, completionTimeout) = + if sys.env.contains("CI") then (15.seconds, 15.seconds) + else (5.seconds, 10.seconds) + private final class Entry private def newCache(): ArtifactCache[Entry] = @@ -35,9 +40,9 @@ class CompilerCacheTest extends AnyFunSuite: val requests = List(request(io.Path("/A.mls")), request(io.Path("/B.mls"))) val overlapped = - try bothStarted.await(5, TimeUnit.SECONDS) + try bothStarted.await(startupTimeout.toMillis, TimeUnit.MILLISECONDS) finally release.countDown() - Await.result(Future.sequence(requests), 10.seconds) + Await.result(Future.sequence(requests), completionTimeout) assert(overlapped, "Building one path should not hold the cache monitor while it evaluates") @@ -58,11 +63,11 @@ class CompilerCacheTest extends AnyFunSuite: }) val first = request() - assert(buildStarted.await(5, TimeUnit.SECONDS), "The first artifact build should start") + assert(buildStarted.await(startupTimeout.toMillis, TimeUnit.MILLISECONDS), "The first artifact build should start") val second = request() release.countDown() - val firstResult = Await.result(first, 10.seconds) - val secondResult = Await.result(second, 10.seconds) + val firstResult = Await.result(first, completionTimeout) + val secondResult = Await.result(second, completionTimeout) assert(buildCount.get() == 1, "Concurrent requesters should share one build for a path") assert(firstResult eq secondResult, "Concurrent requesters should observe one artifact identity") @@ -128,7 +133,7 @@ class CompilerCacheTest extends AnyFunSuite: def read(path: io.Path): String = if synchronizedRoots.contains(path) then bothRootsLocked.countDown() - assert(bothRootsLocked.await(5, TimeUnit.SECONDS), + assert(bothRootsLocked.await(startupTimeout.toMillis, TimeUnit.MILLISECONDS), "Both root compilations should reach source parsing while holding their path locks") underlyingFs.read(path) @@ -162,7 +167,7 @@ class CompilerCacheTest extends AnyFunSuite: compiler.compileModule(file) })(using executionContext) - try Await.result(Future.sequence(List(compile(fileA), compile(fileB))), 10.seconds) + try Await.result(Future.sequence(List(compile(fileA), compile(fileB))), completionTimeout) catch case timeout: TimeoutException => val workerStacks = workerThreads.asScala.map: thread => s"${thread.getName}: ${thread.getState}\n${thread.getStackTrace.mkString("\n")}" From 5d43add8232cded7707456bf21bcbbdc971907aa Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 2 Oct 2026 15:16:50 +0800 Subject: [PATCH 180/228] Migrate mlscript/basics/MiscArrayTests.mls --- .../scala/hkmc2/semantics/NewResolver.scala | 3 +- .../test/mlscript/basics/MiscArrayTests.mls | 31 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index d031654d44..bcdf60a91c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1527,7 +1527,8 @@ class NewResolver: case _ => matchLists(ret, tail) case Nil => () case Marked(record: RecordTypeShape, _) => - constrainRecord(record.fields.toList.map((name, tpe) => record.declarations(name) -> tpe), actual, marks) + constrainRecord(record.fields.iterator.map((name, tpe) => record.declarations(name) -> tpe).toList, + actual, marks) case _ => () actual match case Marked(_: UnknownValueShape, _) => diff --git a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls index fb121754f0..47365b5af1 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls @@ -1,3 +1,4 @@ +:. :js @@ -26,8 +27,16 @@ f() xs.reverse() //│ = [2, 1, 0] +:e :re xs.map(x => x * 2) +//│ ╔══[COMPILATION ERROR] Resolution error in function type +//│ ║ l.193: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Callback parameter list does not match its declared function type. +//│ ║ in MiscArrayTests.mls:32 +//│ ║ l.32: xs.map(x => x * 2) +//│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 3 xs.map((x, i, a) => x * 2) @@ -39,20 +48,23 @@ xs.map((x, ...) => x * 2) xs.map((x, ..._) => x * 2) //│ = [4, 2, 0] -fun map(xs, f) = xs.map((x, ...bs) => f(x)) +fun map(xs: Array, f) = xs.map((x, ...bs) => f(x)) xs \map(x => x * 2) \print() //│ > [4, 2, 0] -// * Note: the RHS of `\` is a self-contained block, thus implicitly parenthesized. +// * Note: the RHS of this `\` is a self-contained block, thus implicitly parenthesized. :sjs :e :re xs \ map(x => x * 2) -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 1 -//│ ║ l.52: map(x => x * 2) -//│ ╙── ^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.61: map(x => x * 2) +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Function 'map' expected 2 arguments, but got 1 +//│ ║ l.51: fun map(xs: Array, f) = xs.map((x, ...bs) => f(x)) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let lambda5, tmp; //│ lambda5 = (undefined, function (x) { @@ -67,9 +79,12 @@ xs \ :re xs\ map(x => x * 2) -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 1 -//│ ║ l.69: map(x => x * 2) -//│ ╙── ^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.81: map(x => x * 2) +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Function 'map' expected 2 arguments, but got 1 +//│ ║ l.51: fun map(xs: Array, f) = xs.map((x, ...bs) => f(x)) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'map' expected 2 arguments but got 1 :pt From e83900233f567bf1fb3f081b9fddfd384add05c5 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 16:20:21 +0800 Subject: [PATCH 181/228] Port unblocked diff tests to new resolution Migrate 198 semantic worksheets with explicit interfaces, host declarations, and checked dynamic receivers where tests exercise runtime failures. Keep runtime results and negative-test coverage, and refresh the embedded goldens. Make flat parser token preprocessing stack-safe on JavaScript while preserving unchanged token tails; the expanded prelude exposed the recursive traversal. Replace migration history with the current inventory and remaining constraints. Validation: ctest, catest, cntest, cwtest, affected diff suites, and the complete hkmc2AllTests/test run passed. No new Any annotations or failure suppressions. --- doc/new-resolution-suite-migration.md | 365 +++++-------- .../src/main/scala/hkmc2/syntax/Parser.scala | 55 +- .../apps/parsing-web-demo/Examples.mls | 3 +- .../test/mlscript/apps/parsing-web-demo/.mls | 1 + .../apps/parsing-web-demo/ExamplesTest.mls | 5 +- hkmc2/shared/src/test/mlscript/backlog/.mls | 1 + .../backlog/DestructuringBindings.mls | 23 +- .../src/test/mlscript/backlog/ForLLM.mls | 15 +- .../backlog/NonReturningStatements.mls | 1 + .../test/mlscript/backlog/OfStatements.mls | 11 +- .../shared/src/test/mlscript/backlog/UCS.mls | 40 +- .../test/mlscript/basics/BadOverloading.mls | 68 +-- .../test/mlscript/basics/DynamicFields.mls | 2 + .../mlscript/basics/DynamicInstantiation.mls | 31 +- .../test/mlscript/basics/MiscArrayTests.mls | 2 +- .../src/test/mlscript/basics/MutRcd.mls | 16 +- hkmc2/shared/src/test/mlscript/basics/New.mls | 73 ++- .../src/test/mlscript/basics/NewMut.mls | 30 +- .../src/test/mlscript/basics/NewlineOps.mls | 42 +- .../src/test/mlscript/basics/NewlineSels.mls | 7 +- .../src/test/mlscript/basics/StrTest.mls | 32 +- .../src/test/mlscript/basics/Underscores.mls | 41 +- .../src/test/mlscript/block-staging/.mls | 1 + .../src/test/mlscript/block-staging/Casts.mls | 1 + .../test/mlscript/block-staging/Nested.mls | 1 + .../test/mlscript/block-staging/PrintCode.mls | 2 + .../test/mlscript/block-staging/Syntax.mls | 1 + .../test/mlscript/codegen/ClassMatching.mls | 29 +- .../src/test/mlscript/codegen/ConsoleLog.mls | 5 +- .../test/mlscript/codegen/CurriedClasses.mls | 41 +- .../src/test/mlscript/codegen/Getters.mls | 12 +- .../src/test/mlscript/codegen/Hygiene.mls | 18 +- .../test/mlscript/codegen/ImportExample.mls | 14 +- .../shared/src/test/mlscript/codegen/Open.mls | 29 +- .../src/test/mlscript/codegen/PartialApps.mls | 52 +- .../shared/src/test/mlscript/codegen/Pwd.mls | 5 +- .../test/mlscript/codegen/RuntimeUsage.mls | 1 + .../test/mlscript/codegen/SanityChecks.mls | 52 +- .../src/test/mlscript/codegen/While.mls | 71 +-- hkmc2/shared/src/test/mlscript/ctx/.mls | 1 + .../test/mlscript/ctx/MissingDefinitions1.mls | 15 +- .../test/mlscript/ctx/MissingDefinitions2.mls | 3 +- .../src/test/mlscript/ctx/TypeResolution.mls | 127 ++--- .../src/test/mlscript/dead-param-elim/.mls | 1 + .../test/mlscript/dead-param-elim/basic.mls | 8 +- .../test/mlscript/dead-param-elim/clash.mls | 3 +- .../mlscript/dead-param-elim/class-in-fun.mls | 3 +- .../mlscript/dead-param-elim/config-flags.mls | 1 + .../mlscript/dead-param-elim/dead-ref.mls | 1 + .../dead-param-elim/inherited-field.mls | 1 + .../test/mlscript/dead-param-elim/lambda.mls | 1 + .../test/mlscript/dead-param-elim/module.mls | 1 + .../dead-param-elim/multiArgLists.mls | 1 + .../test/mlscript/dead-param-elim/purity.mls | 10 +- .../mlscript/dead-param-elim/recursive.mls | 1 + .../mlscript/dead-param-elim/refresher.mls | 5 +- .../test/mlscript/dead-param-elim/todos.mls | 23 +- .../src/test/mlscript/dead-param-elim/ups.mls | 3 +- .../src/test/mlscript/decls/Prelude.mls | 8 +- hkmc2/shared/src/test/mlscript/deforest/.mls | 1 + .../src/test/mlscript/deforest/append.mls | 29 +- .../src/test/mlscript/deforest/basic.mls | 147 +++--- .../src/test/mlscript/deforest/clashes.mls | 5 +- .../src/test/mlscript/deforest/cyclic.mls | 29 +- .../test/mlscript/deforest/determinism.mls | 27 +- .../src/test/mlscript/deforest/dropLast.mls | 1 + .../test/mlscript/deforest/erasedTypes.mls | 52 +- .../test/mlscript/deforest/eta-expansion/.mls | 1 + .../mlscript/deforest/eta-expansion/basic.mls | 41 +- .../deforest/eta-expansion/recursive.mls | 17 +- .../src/test/mlscript/deforest/fusibility.mls | 186 +++---- .../src/test/mlscript/deforest/imperative.mls | 7 +- .../mlscript/deforest/listComprehension.mls | 265 +++++----- .../src/test/mlscript/deforest/module.mls | 89 ++-- .../test/mlscript/deforest/multiArgLists.mls | 11 +- .../test/mlscript/deforest/nestedMatch.mls | 311 +++++------ .../src/test/mlscript/deforest/recursive.mls | 491 +++++++++--------- .../mlscript/deforest/relaxedPrograms.mls | 3 +- .../deforest/selectionsInNestedMatch.mls | 60 +-- .../src/test/mlscript/deforest/simple.mls | 455 ++++++++-------- .../src/test/mlscript/deforest/todos.mls | 77 +-- .../src/test/mlscript/deforest/zipunzip.mls | 117 ++--- hkmc2/shared/src/test/mlscript/handlers/.mls | 1 + .../handlers/CurriedFunctionCalls.mls | 5 +- .../test/mlscript/handlers/EffectsHygiene.mls | 9 +- .../test/mlscript/handlers/Fallthrough.mls | 13 +- .../test/mlscript/handlers/HandlerReset.mls | 1 + .../mlscript/handlers/HandlersScratch.mls | 1 + .../src/test/mlscript/handlers/Loops.mls | 1 + .../src/test/mlscript/handlers/NoHandler.mls | 9 +- .../src/test/mlscript/handlers/SavedVars.mls | 5 +- .../test/mlscript/handlers/ZCombinator.mls | 1 + hkmc2/shared/src/test/mlscript/interop/.mls | 1 + .../src/test/mlscript/interop/Arrays.mls | 56 +- .../src/test/mlscript/interop/CtorBypass.mls | 8 +- .../src/test/mlscript/interop/Functions.mls | 11 +- .../test/mlscript/interop/JSCollections.mls | 1 + .../src/test/mlscript/interop/JSDerp.mls | 1 + .../shared/src/test/mlscript/interop/Null.mls | 1 + .../src/test/mlscript/interop/Number.mls | 1 + .../src/test/mlscript/interop/Objects.mls | 15 +- .../src/test/mlscript/interop/Symbols.mls | 1 + .../test/mlscript/interop/Virtualization.mls | 15 + .../mlscript/lifter/ClassWithCompanion.mls | 5 +- .../test/mlscript/lifter/CompanionsInFun.mls | 5 +- .../mlscript/lifter/CurriedClassInFun.mls | 1 + .../src/test/mlscript/lifter/DefnsInClass.mls | 1 + .../src/test/mlscript/lifter/FunInFun.mls | 7 +- .../src/test/mlscript/lifter/FunInMethod.mls | 1 + .../src/test/mlscript/lifter/Imports.mls | 1 + .../src/test/mlscript/lifter/Labels.mls | 1 + .../test/mlscript/lifter/ModulesObjects.mls | 32 +- .../src/test/mlscript/lifter/Mutation.mls | 3 +- .../src/test/mlscript/lifter/PatternInFun.mls | 1 + hkmc2/shared/src/test/mlscript/meta/.mls | 1 + .../test/mlscript/meta/BlockDiffTesting.mls | 1 + .../test/mlscript/meta/CompilerFiction.mls | 20 +- .../src/test/mlscript/meta/ImportedTest.mls | 1 + .../src/test/mlscript/meta/ImporterTest.mls | 1 + .../src/test/mlscript/meta/LocDebugging.mls | 1 + .../src/test/mlscript/newres/Arrays.mls | 24 +- .../test/mlscript/newres/GenericMethods.mls | 2 +- .../newres/PatternDefinitionBindings.mls | 2 +- hkmc2/shared/src/test/mlscript/nofib/.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/ansi.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/atom.mls | 1 + .../shared/src/test/mlscript/nofib/awards.mls | 1 + .../shared/src/test/mlscript/nofib/banner.mls | 1 + .../shared/src/test/mlscript/nofib/boyer.mls | 1 + .../shared/src/test/mlscript/nofib/boyer2.mls | 1 + .../src/test/mlscript/nofib/calendar.mls | 1 + .../src/test/mlscript/nofib/cichelli.mls | 1 + .../src/test/mlscript/nofib/circsim.mls | 1 + .../src/test/mlscript/nofib/clausify.mls | 1 + .../src/test/mlscript/nofib/constraints.mls | 1 + .../src/test/mlscript/nofib/cryptarithm1.mls | 1 + .../src/test/mlscript/nofib/cryptarithm2.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/cse.mls | 1 + .../shared/src/test/mlscript/nofib/eliza.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/fish.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/gcd.mls | 1 + .../src/test/mlscript/nofib/integer.mls | 1 + .../src/test/mlscript/nofib/knights.mls | 1 + .../shared/src/test/mlscript/nofib/lambda.mls | 1 + .../src/test/mlscript/nofib/last-piece.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/lcss.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/life.mls | 1 + .../shared/src/test/mlscript/nofib/mandel.mls | 1 + .../src/test/mlscript/nofib/mandel2.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/mate.mls | 1 + .../src/test/mlscript/nofib/minimax.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/para.mls | 1 + .../shared/src/test/mlscript/nofib/power.mls | 1 + .../shared/src/test/mlscript/nofib/pretty.mls | 1 + .../src/test/mlscript/nofib/primetest.mls | 1 + .../shared/src/test/mlscript/nofib/puzzle.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/rsa.mls | 1 + hkmc2/shared/src/test/mlscript/nofib/scc.mls | 1 + .../src/test/mlscript/nofib/secretary.mls | 1 + .../src/test/mlscript/nofib/sorting.mls | 1 + .../shared/src/test/mlscript/nofib/sphere.mls | 1 + .../src/test/mlscript/nofib/treejoin.mls | 1 + hkmc2/shared/src/test/mlscript/objbuf/.mls | 1 + .../mlscript/objbuf/ObjectBufferAllocator.mls | 1 + hkmc2/shared/src/test/mlscript/opt/.mls | 1 + .../src/test/mlscript/opt/AbortivePrefix.mls | 1 + .../src/test/mlscript/opt/AlmostTailCall.mls | 1 + .../src/test/mlscript/opt/BasicVarPropag.mls | 1 + .../mlscript/opt/CapturedVariableMutation.mls | 1 + .../src/test/mlscript/opt/CaseOfCase.mls | 24 +- .../src/test/mlscript/opt/ConstantFolding.mls | 1 + .../mlscript/opt/DeadAssignmentRemoval.mls | 1 + .../test/mlscript/opt/DeadBranchRemoval.mls | 1 + .../test/mlscript/opt/DeadConstructorElim.mls | 31 +- .../src/test/mlscript/opt/DeadObjRemoval.mls | 1 + .../src/test/mlscript/opt/DeadSelRemoval.mls | 1 + .../src/test/mlscript/opt/DeadStatRemoval.mls | 1 + .../src/test/mlscript/opt/DeadVarRemoval.mls | 1 + .../src/test/mlscript/opt/EtaExpansion.mls | 11 +- .../test/mlscript/opt/IllFormedAccesses.mls | 1 + .../test/mlscript/opt/InfiniteInlining.mls | 1 + .../test/mlscript/opt/InlineAcrossFiles.mls | 1 + .../test/mlscript/opt/InlineModuleMethods.mls | 1 + .../opt/InlineNestedFunctionsAcrossFiles.mls | 1 + .../shared/src/test/mlscript/opt/LoopOpt.mls | 5 +- .../test/mlscript/opt/MatchSimplification.mls | 1 + .../src/test/mlscript/opt/NestedLoopOpt.mls | 1 + .../src/test/mlscript/opt/NoCaseOfCase.mls | 14 +- .../src/test/mlscript/opt/PrivateFields.mls | 1 + .../test/mlscript/opt/PureCallPropagation.mls | 1 + .../src/test/mlscript/opt/TailRecOpt.mls | 1 + .../test/mlscript/opt/UnspecifiedBehavior.mls | 1 + .../src/test/mlscript/opt/ValPropag.mls | 1 + .../src/test/mlscript/opt/VarPropagStress.mls | 1 + .../src/test/mlscript/opt/WorkerWrapper.mls | 1 + hkmc2/shared/src/test/mlscript/std/.mls | 1 + .../test/mlscript/std/FingerTreeListTest.mls | 37 +- .../shared/src/test/mlscript/std/IterTest.mls | 3 +- .../src/test/mlscript/std/LazyArrayTest.mls | 2 + .../src/test/mlscript/std/MutMapTest.mls | 1 + .../src/test/mlscript/std/PredefTest.mls | 47 +- .../src/test/mlscript/std/PrettyPrintTest.mls | 14 +- .../src/test/mlscript/std/RecordTest.mls | 5 +- .../src/test/mlscript/std/StackTests.mls | 1 + hkmc2/shared/src/test/mlscript/syntax/.mls | 1 + .../mlscript/syntax/AbusiveSuspensions.mls | 21 +- .../test/mlscript/syntax/BackslashApps.mls | 1 + .../src/test/mlscript/syntax/BasicIfs.mls | 1 + .../test/mlscript/syntax/KeywordStutters.mls | 7 +- .../src/test/mlscript/syntax/TypesInTerms.mls | 7 +- .../test/mlscript/syntax/WeirdBrackets.mls | 47 +- .../src/test/mlscript/syntax/annotations/.mls | 1 + .../annotations/AnnotationPrecedence.mls | 92 ++-- .../test/mlscript/syntax/annotations/Call.mls | 23 +- .../syntax/annotations/Declarations.mls | 61 +-- .../mlscript/syntax/annotations/Pattern.mls | 15 +- .../syntax/annotations/Unsupported.mls | 33 +- hkmc2/shared/src/test/mlscript/tailrec/.mls | 1 + .../src/test/mlscript/tailrec/Errors.mls | 53 +- .../test/mlscript/tailrec/MultiArgLists.mls | 3 +- .../test/mlscript/ups/fixpoint/ListFusion.mls | 17 +- .../src/test/mlscript/wasm/Binaryen.mls | 1 + .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 223 files changed, 2555 insertions(+), 2203 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/apps/parsing-web-demo/.mls create mode 100644 hkmc2/shared/src/test/mlscript/backlog/.mls create mode 100644 hkmc2/shared/src/test/mlscript/block-staging/.mls create mode 100644 hkmc2/shared/src/test/mlscript/ctx/.mls create mode 100644 hkmc2/shared/src/test/mlscript/dead-param-elim/.mls create mode 100644 hkmc2/shared/src/test/mlscript/deforest/.mls create mode 100644 hkmc2/shared/src/test/mlscript/deforest/eta-expansion/.mls create mode 100644 hkmc2/shared/src/test/mlscript/handlers/.mls create mode 100644 hkmc2/shared/src/test/mlscript/interop/.mls create mode 100644 hkmc2/shared/src/test/mlscript/nofib/.mls create mode 100644 hkmc2/shared/src/test/mlscript/objbuf/.mls create mode 100644 hkmc2/shared/src/test/mlscript/opt/.mls create mode 100644 hkmc2/shared/src/test/mlscript/std/.mls create mode 100644 hkmc2/shared/src/test/mlscript/syntax/.mls create mode 100644 hkmc2/shared/src/test/mlscript/syntax/annotations/.mls create mode 100644 hkmc2/shared/src/test/mlscript/tailrec/.mls diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 832595eaf2..7e1e538b6b 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -1,244 +1,161 @@ -NOTE: This document was written by Codex Astra and has not been deeply reviewed; -it is not meant to be official documentation and -is in fact likely to contain parts that are unintelligible to readers who lack sufficient context. - - # New-resolution suite migration +This document must describe only the **current state** of the migration: active +configuration, remaining work, and validation requirements. Remove obsolete blockers +and historical progress notes when updating it; Git history records earlier states. + ## Scope and status -This is the worklist for migrating existing worksheets and compilation fixtures -to new resolution. Language rules live in the [language reference](reference.md#resolution-interfaces); -resolver invariants and regression coverage live in the -[resolver design notes](new-resolution-design.md). Backend-specific constraints -are in [WASM resolution](new-resolution-wasm.md). +Language rules are in the [language reference](reference.md#resolution-interfaces). +Resolver invariants are in the [design notes](new-resolution-design.md), and +backend constraints are in [WASM resolution](new-resolution-wasm.md). -The checked-in worksheet headers give the following status as of 2026-10-02. -Counts exclude shared `.mls` configurations, new-resolution regression tests, and the 51 files under -`ucs/staging`, which the test runner excludes. +The table counts semantic worksheets, following their `:.` and `:..` configuration +imports. It excludes configuration files, shared declarations, dedicated `newres` +regressions, root scratch files, parser-only tests, the separate InvalML checker, +and the inactive `ucs/staging` directory. A migrated worksheet can import a legacy +compilation fixture; mixed-mode imports are supported. -| Suite | Migrated | Legacy configuration | Active files | +| Diff-test suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | -| basics | 67 | 23 | 90 | -| codegen | 93 | 33 | 126 | +| apps | 17 | 0 | 17 | +| backlog | 5 | 3 | 8 | +| basics | 78 | 12 | 90 | +| block-staging | 4 | 3 | 7 | +| codegen | 106 | 20 | 126 | +| ctx | 3 | 10 | 13 | +| dead-param-elim | 14 | 0 | 14 | +| deforest | 21 | 0 | 21 | +| flows | 0 | 7 | 7 | +| handlers | 9 | 29 | 38 | +| interop | 10 | 0 | 10 | +| lifter | 11 | 5 | 16 | +| meta | 7 | 0 | 7 | +| nofib | 38 | 0 | 38 | +| objbuf | 1 | 2 | 3 | +| opt | 30 | 0 | 30 | +| std | 8 | 2 | 10 | +| syntax | 11 | 0 | 11 | +| tailrec | 2 | 3 | 5 | | ucs | 69 | 1 | 70 | -| ups | 58 | 13 | 71 | -| apps | 16 | 1 | 17 | -| Total | 303 | 71 | 374 | -| wasm (separate backend) | 22 | 1 | 23 | - -All thirteen `apps/parsing` worksheets use new resolution, but some implementation -modules they import still use legacy resolution. Worksheet migration does not -imply that its dependencies have been migrated. +| ups | 59 | 12 | 71 | +| wasm | 23 | 0 | 23 | +| Total | 526 | 109 | 635 | | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | -| Main (including quotes, UPS, and regression fixtures) | 45 | 4 | 49 | +| Main | 45 | 4 | 49 | | Applications | 18 | 2 | 20 | | Nofib | 37 | 2 | 39 | | WASM | 1 | 0 | 1 | | Total | 101 | 8 | 109 | -These totals include the new-resolution `NamedFieldLibrary` regression fixture -and the deliberately legacy `LegacyGenericLibrary` mixed-mode regression fixture. -They count language directives in source files, not test-runner test cases. - -## Migration procedure - -- Start migrated worksheets with `:.` before any blank line. Suite `.mls` files - inherit the common `#lang(0.3.x)` configuration through `:..`, including in - nested directories. The general suites use non-strict resolution; `newres` - enables strict resolution and `newres/loose` explicitly overrides it. -- Preserve each worksheet's execution flags. The common language configuration - disables JS execution; a parser-only test must not acquire runtime execution - merely because its resolution configuration changes. WASM has its own flags. -- Add `#lang(0.3.x)` to compilation fixtures only after checking both standalone - compilation and existing consumers. Real files check their exposed interfaces - even in non-strict mode. Add the callable, nominal, or structural interfaces - their public APIs require. Making a helper private changes the exported API; - using `dyn` to bypass a missing interface changes the language contract. -- Preserve successful results and negative-test intent. Existing invalid - operations may need compilation-error expectations in addition to runtime-error - expectations. Do not remove an expectation without resolving the intended - behavior, or rewrite a callable-constructor test to avoid the feature it tests. - Review successful values and logs as well as failure markers: unsupported - contextual calls can return function values without failing the test runner. -- Open operators explicitly when shadowing builtins, as in `codegen/ImportedOps` - and `ucs/examples/ListFold`. -- Use dynamic selection for reflective host access whose effects are not described - by the static interface. For example, `ups/examples/DoubleTripleList` uses - `testList!head` to observe an installed JavaScript getter; reads through the - declared `val` interface are pure and may be discarded when unused. - `newres/GetterReadEffects.mls` covers getters, dynamic reads, and field interfaces. -- Keep blocked files on their existing configuration with their source and - goldens intact. Do not add per-file `:todo`, `:fixme`, `:ignore`, or resolution - exceptions to claim a port. Use focused regressions for unresolved compiler bugs. - -## Remaining work - -### Mixed-mode imports and fixture interfaces - -Some migrated worksheets import legacy compilation fixtures. An import across -resolution modes is supported and is not itself a migration blocker. Check each -fixture's exposed interfaces and its consumers together. Routine annotations and -source corrections should be attempted before attributing a failure to the -resolver. The remaining fixtures need the following work: - -| Legacy fixtures | Concrete work and known failures | +`LegacyGenericLibrary` deliberately uses legacy resolution to test mixed-mode +imports. The other legacy compilation fixtures are listed below. Counts describe +source configuration, not the number of test cases reported by SBT. + +## Remaining compilation fixtures + +| Fixtures | Remaining work | | --- | --- | -| `FingerTreeList` | Enabling new resolution exceeds the compilation test's time limit. Diagnose inference growth around recursive trees and tuple spreads before calling this an annotation-only port. | -| `CSP`, `QuoteExample1` | Quasiquote type selections and wildcard-reference lowering. | -| `parsing/Lexer` | Calls to token constructors with trailing `using` parameters need automatic contextual argument insertion; `newres/LexerMigration.mls` records the missing behavior. | -| `parsing/ParseRule` | The `andThen` helper now pairs its own result type with the following rule's result; `map` and `andThen` expose their result interfaces. Enabling new resolution still exceeds the 25-second compilation limit when compiling its consumers, even with cache-result annotations or with the recursive helper lifted out of the method. Keep the implementation on legacy resolution pending an inference-growth fix. | -| `nofib/lastpiece`, `nofib/sorting` | Both still exceed the 25-second compilation time limit under new resolution (retried 2026-10-02). `sorting` first needs `int_of_char(c: Str)`; adding it reveals the timeout. Diagnose inference growth before retrying these ports. | - -Array callbacks must accept the arguments supplied by the JS API: three for -`map`/`filter`/`forEach` and four for `reduce`. Use `pass1(f)` or `pass2(f)` when -adapting a function that uses fewer arguments. An unused rest parameter also satisfies -this existing rule (`newres/Arrays.mls`); it does not require relaxing callback -arity checking. Missing host declarations and ordinary parameter annotations do -not require a language-design discussion. Compiler assertions and timeouts need -reproductions and fixes; a design question should name the unresolved semantic -choice, rather than merely name the failing subsystem. - -To reproduce a blocker, temporarily add the language directive to the named -compilation fixture and run `ctest ` or `catest ` as appropriate. -The blocked fixtures retain their existing resolution mode. - -`Runtime`, `Rendering`, `LazyFingerTree`, `Term`, `Extension`, -`Predef`, `Block`, `Shape`, `LazyArray`, `Accounting`, `CSV`, `Parser`, -`ParseRuleVisualizer`, and the parser web demo use new resolution. Nullable host -results use pattern matching where possible; mutable traversal arrays use `mut`. -Parser map interfaces carry their element types through `Option`, so ordinary -`Some(rule)` bindings retain the rule interface. Member `let` bindings contribute -their initializer and assignment shapes, including in the web demo. -The parser unwraps optional continuation rules and resumes module parsing after -leading separators. The virtual `source` fields expand in both resolution modes -(`newres/SourceLocations`); constructor metadata is covered by `newres/ShapeFixture`. - -The runtime describes packed continuation arrays and nullable frame links explicitly; -casts decode the packed slots and initialized tail links. Tuple helpers use a structural -indexable interface so lazy views remain supported without nominal array casts. -Raw indexing preserves undefined elements and existing tuple fusion. Rendering uses typed callback -adapters and host collection interfaces, with dynamic access confined to reflective -properties. `newres/MigratedFixtureInterfaces` covers imported result interfaces and -lazy materialization. `Term` exposes typed printer interfaces and converts nullable -literals with `String`; the same regression checks literal printing and fresh names. -`apps/parsing/ParseRuleTest` checks heterogeneous sequencing. -`Extension` constructs `Choice[Stack[Tree]]` before mapping the accumulated trees to -an application expression. Existing rendering, handler, and parser consumers retain -their results. - -The web demo compiles, but serving its checked-in HTML directly still encounters -Node-only imports (`fs`, `process`, `path`, and `url`) through `Predef` and `Term`. -Browser packaging must separate that dependency before direct startup works. -Parser and visualizer worksheets run under Node and do not exercise this browser -packaging constraint. - -### Shape propagation and capture precision - -- **Generators:** `newres/GeneratorResults.mls` records a call whose result is - treated as the generator body's return value, leaving `.next` unresolved. - Model the iterator result produced by lowering; use `codegen/Generators` as - the worksheet acceptance case. -- **Mutation and control flow:** `newres/MutationFlow.mls` records a reassigned - array checked against its initializer's tuple length; accumulating both shapes - would still reject valid later indexing. `newres/MemberVariables.mls` covers - shape accumulation through local and member-variable assignments. - Handler inference needs separate flows for the receiver, values passed to - resumptions, and abortive results (`newres/HandlerResults.mls` and - `codegen/ScopedBlocksAndHandlers`). -- **Captured activations:** preserve activation identity through captured - functions, constructor aliases, reconstruction of the same class, and partial - construction. Existing regressions include `CtxSens`, `ValCtxSens`, - `Projections`, `RecursiveEnvironment`, `SpreadCalls`, and `InterfaceExposure` - under `newres`. Local modules capturing outer binders are covered by - `newres/InheritedTypeArguments.mls` and `ScopePaths.mls`. Do not turn unknown - shapes into dynamic values or truncate capture paths to make these cases pass. -- **Exposed nominal results:** `newres/InterfaceExposure.mls` records a returned - private class whose callable methods are not checked through its nominal result - annotation. Follow declared member interfaces back to their implementations - without exposing initializer shapes hidden by annotations. -- **Cross-block flow:** remaining cases include parameter flow in - `basics/MiscArrayTests`, unfinished closures in `codegen/FirstClassFunctionTransform`, - and the unresolved receiver in `codegen/ObjectMethodDebinding`. -- **Recursive inference:** `newres/RecursiveEquality.mls` now runs the unannotated - deep-equality function and mutually recursive array callbacks. Pattern refinement - reuses synthesized type arguments for the same test, constructor path, and input, - allowing publisher deduplication to close the cycle. See the - [scope and graph invariants](new-resolution-scopes.md). - -### Patterns and generated references - -Constructor tests in named-pattern definitions refine `as` bindings, including -uses in guards and transformations (`newres/PatternDefinitionBindings`). Compound -pattern outputs remain incomplete: conjunctions can produce pairs, chains feed -outputs into subsequent matches, and concatenations and rebuilt constructors need -appropriate result interfaces. Remaining worksheet blockers include: - -- `ups/examples/Computation`: a numeric range does not yet refine its bound - minute value enough for `toString` and `padStart`. -- `ups/examples/ListPredicates`: the transformation in a higher-order pattern - argument leaves the result of `toString` without a resolved `length` selection. -- `ucs/patterns/where`, `ups/MatchResult`, `ups/SimpleTransform`, and several regex - worksheets: pattern values and direct `.unapply`/`.unapplyStringPrefix` access. -- `ups/examples/{EvaluationContext,EvaluationContext2,HindleyMilner}`: unresolved - member selections in worksheet-local definitions, despite the migration of the - separate `ups/EvaluationContext` compilation fixture. - -Generated matcher code must retain source targets and receiver paths through -lowering. Transfer rules must distinguish matched inputs, bound fields, and -transformed outputs; negation must not export positive bindings. Recursive pattern -flow still needs cycle-aware subscriptions and demonstrated convergence. - -Acceptance coverage should include tuple/record extraction, nested and imported -constructors, aliases, guards, transformed results, repeated references, and -recursive UPS matchers. Preserve both runtime results and matcher structure. - -### Calls, declarations, and diagnostics - -- Implement contextual instance lookup and automatic argument insertion for calls - with trailing `using` lists (`newres/LexerMigration.mls`). This blocks - `parsing/Lexer` and `basics/CompanionModules_Classes`: contextual calls leave - function values where completed results are expected. Passing instances - explicitly would bypass the feature these tests need to exercise. -- Complete constructor-value member lookup (`codegen/ParamClasses`, - `basics/DynamicInstantiation`) and module/call checks. Decide nominal - module-forwarding compatibility for `basics/CyclicModuleForwarders` before - changing its expected outcomes. -- Extend host interfaces where needed, including static descriptions of arbitrary - WebAssembly exports. `Array.reduce` requires an initial value because declared - methods cannot be overloaded by arity; the form without one would need - an accumulator type that also includes the element type. - `Array.concat` currently returns `Array[Any]`; improving precision needs a - declared element constraint on its rest arguments. `Array.splice` must separate - its optional deletion count from inserted elements before those elements can - constrain `T`; `newres/MutableArrays.mls` records the gap. -- Complete quasiquote type-selection and wildcard-reference lowering (`CSP`, - `QuoteExample1`). -- Preserve source origins in synthesized references and diagnostics. In particular, - check the lost undefined-binding use sites in `ups/UpsBugsBacklog` and - `ups/syntax/MixedParameters` without changing the selected symbol. - -Retry legacy worksheets and compilation fixtures against the current compiler and -prelude before diagnosing a blocker. Keep deferred fixtures on legacy resolution. -The remaining legacy WASM worksheet, `wasm/Binaryen`, fails when given the common -language configuration. It exercises JavaScript host tooling alongside backend -directives and needs its configuration and backend diagnostics reviewed separately. - -## Validation and completion gates +| `FingerTreeList` | New resolution exceeds the compilation time limit around recursive trees and tuple spreads. | +| `CSP`, `QuoteExample1` | Quasiquote type selections and wildcard-reference lowering are unsupported. | +| `parsing/Lexer` | Token constructors with trailing `using` parameters need automatic contextual argument insertion. `newres/LexerMigration` reproduces the missing behavior. | +| `parsing/ParseRule` | Recursive rule inference exceeds the compilation time limit when compiling consumers. | +| `nofib/lastpiece`, `nofib/sorting` | New resolution exceeds the compilation time limit. `sorting` also needs an `int_of_char(c: Str)` parameter annotation. | + +## Remaining worksheet constraints + +Legacy worksheets require more than enabling the language configuration. The +following constraints identify the current failing behavior and representative tests. + +- **Contextual calls and module checks:** `basics/CompanionModules_Classes` can + produce a function instead of the completed result because implicit `using` + arguments are not inserted. Several `ctx` and module worksheets also lose + required negative diagnostics. `basics/CyclicModuleForwarders` needs a decision + about nominal module-forwarding compatibility. Passing contextual arguments + explicitly would bypass the feature these tests exercise. +- **Constructor and anonymous-object interfaces:** `codegen/ParamClasses`, + `basics/ValMemberSymbols`, and `lifter/AnonClasses` exercise constructor members, partially + constructed objects, or anonymous refinements that lack the required interfaces. + `lifter/PrivateMutableFields` cannot resolve anonymous `new with` expressions. +- **Mutation and returned shapes:** `codegen/SetStmt` retains an initializer's + tuple length after reassignment (`newres/MutationFlow`). `basics/Records` needs + callable result precision across branches with different parameter lists. + `std/LazyFingerTreeTest` loses the private view interface through slicing helpers; + its general slice result also includes finger trees without `materialize`. + `lifter/Loops` includes an implicit unit result after an unconditional loop return, + preventing the returned closure from being called directly. +- **Generators:** `codegen/Generators` needs the iterator interface produced by + generator lowering. `newres/GeneratorResults` records the unresolved `.next`. +- **Handlers:** handler-generated receiver and member references remain unresolved + in `handlers`, `codegen/ScopedBlocksAndHandlers`, and handler cases in `lifter`. + Receiver flow, resumption arguments, and abortive results need distinct handling + (`newres/HandlerResults`). +- **Recursive definitions and staged functions:** `basics/FunDefs` and recursive + getter cases in `tailrec` can overflow during resolution. `block-staging/Functions` + and `codegen/FirstClassFunctionTransform` encounter unfinished generated symbols. + `objbuf/BasicsObjBuf` exceeds the worksheet time limit under new resolution. +- **Legacy flow analysis:** the `flows` worksheets run a separate flow pass that + does not handle new-resolution selections and can throw on `NewSel` nodes. +- **Runtime instrumentation and privacy:** `codegen/ObjectMethodDebinding` loses + an expected runtime error. `codegen/PrivateMembers` loses privacy diagnostics + and required runtime errors. These worksheets must preserve their checks. +- **Pattern values and transformations:** `ucs/patterns/where`, `ups/MatchResult`, + `ups/SimpleTransform`, `std/RenderingTest`, and regex worksheets use pattern + values or direct `.unapply`/`.unapplyStringPrefix` access. Range and higher-order + transformations lack result interfaces in `ups/examples/Computation` and + `ups/examples/ListPredicates`. Evaluation-context and Hindley–Milner worksheets + additionally need recursive member interfaces; `ups/examples/EvaluationContext` + exceeds the worksheet time limit. +- **Quasiquotes:** `codegen/Quasiquotes` needs type-selection and generated-reference + support. Pattern and quote lowering must preserve source targets and receiver paths. + +Focused coverage for capture identity and nominal interfaces is in `newres/CtxSens`, +`ValCtxSens`, `Projections`, `RecursiveEnvironment`, `SpreadCalls`, `ScopePaths`, +`InheritedTypeArguments`, and `InterfaceExposure`. These tests constrain fixes to +recursive and exposed interfaces; unknown shapes must not silently become dynamic. + +## Migration rules + +- Load the common language configuration before the first blank line. Most suites + use `:.` and a local `.mls` containing `:..`. Lifter worksheets use `:..` for the + language configuration and `:.` for their existing lifting flags. Preserve all + execution flags. `wasm/Binaryen` imports the parent language configuration with + `:..` because it tests JavaScript host tooling rather than the compiler's WASM backend. +- Add ordinary parameter and result interfaces before attributing a failure to + language design. Use shared `Indexable` and `IterableIndexable` aliases from + `decls/Prelude`; avoid invariant `Any` arguments and unnecessary casts. +- Keep host declarations accurate. Array callbacks receive three arguments for + `map`/`filter`/`forEach` and four for `reduce`; use `pass1`, `pass2`, or unused + rest parameters where appropriate. Static callbacks must retain these arities. +- Explicitly open names that shadow builtins, including operators and helpers such + as `equals`, `render`, and `Symbol`. Use dynamic selectors for deliberate host + reflection and undeclared property additions, preserving observable effects. + For tests of checked dynamic access, annotate receivers as `dyn` and keep ordinary + selections: raw `!` selectors omit checks that these tests need to exercise. +- Review values and logs as well as test-runner success. A tolerated error or + an uncalled function can hide a regression. Existing invalid operations may + need compilation-error expectations in addition to runtime-error expectations. +- Keep blocked worksheets on their existing configuration with their source and + goldens intact. Do not add failure suppressions or dynamic escapes to bypass + unresolved language features. + Reproduce compiler failures in focused `.mls` regressions. + +Host-interface precision still needs work for `Array.concat`, `Array.splice`, and +`Array.from`. `Array.reduce` without an initial accumulator also needs a signature +that includes the element type; declared methods cannot overload by arity. +`newres/MutableArrays` covers the missing insertion constraint for `splice`. + +## Validation Follow the [repository test workflow](../README.md#running-the-tests-1): -1. Rebuild compilation fixtures before running their worksheet consumers: `ctest` - for main fixtures, `catest` for applications, `cntest` for Nofib, and `cwtest` - for WASM. In particular, runtime tests need the generated `.mjs` dependencies. -2. Run affected consumers, including negative cases, and review golden changes. - Keep nonmigrated files intact; do not retain trial-generated failures. -3. Run `hkmc2AllTests/test` for each retained batch. Commit intentional golden - updates together with the change, using the agent's identity for agent commits. -4. Remove legacy suite configurations only after all covered files migrate, - without new failure suppressions or lost negative diagnostics. - -Update the counts and remaining work after each retained batch. Test totals -include configurations and regression tests, so they are not migration counts. +1. Run `ctest` before worksheet tests to regenerate main runtime dependencies. + Use `catest`, `cntest`, and `cwtest` for application, Nofib, and WASM fixtures. +2. Run affected worksheets with `dtest`, `adtest`, `ndtest`, or `wdtest`; inspect + all rewritten goldens and retain existing successful results and negative intent. +3. Run `hkmc2AllTests/test` for the retained changes. Commit the final golden output + together with the migration, using the agent's own identity for agent commits. +4. Update this inventory and remove resolved blockers. Remove legacy configurations + only when every covered worksheet can migrate without lost coverage. diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index ee16e618cc..f69008ef42 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -158,30 +158,37 @@ abstract class Parser( protected var indent = 0 private var _cur: Ls[TokLoc] = preprocessTokens(tokens) - private def preprocessTokens(tokens: Ls[TokLoc]): Ls[TokLoc] = tokens match - case (IDENT("new", false), l1) :: (IDENT("!", true), l2) :: rest => - (IDENT("new!", false), l1 ++ l2) :: preprocessTokens(rest) - case (IDENT("yield", false), l1) :: (IDENT("*", true), l2) :: rest => - (IDENT("yield*", false), l1 ++ l2) :: preprocessTokens(rest) - // * Remove empty indented sections - case (BRACKETS(Indent, toks), _) :: rest - if toks.forall: - case (NEWLINE | SPACE, _) => true - case _ => false - => - preprocessTokens(rest) - // * Expands end-of-line suspensions that introduce implied indentation, - // * skipping NOISE tokens between `...` and NEWLINE (eg `... // hello\n body`) - // * Note: using `NEWLINE_COMMA` instead of `NEWLINE` causes misparsing of things like `fun foo(..., ...)` - case (SUSPENSION(true), l0) :: NOISE((NEWLINE, l1) :: rest) => - val outerLoc = l0.left ++ rest.lastOption.map(_._2.right) - val innerLoc = l1.right ++ rest.lastOption.map(_._2.left) - BRACKETS(Indent, preprocessTokens(rest))(innerLoc) -> outerLoc :: Nil - case tl :: rest => - val rest2 = preprocessTokens(rest) - if rest2 is rest then tokens - else tl :: rest2 - case Nil => tokens + private def preprocessTokens(tokens: Ls[TokLoc]): Ls[TokLoc] = + // Flat token streams can exceed the JavaScript stack even without nested syntax. + // Keep nonempty suffixes in reverse order so rebuilding can share unchanged tails. + def rebuild(prefixes: Ls[Ls[TokLoc]], rest: Ls[TokLoc]): Ls[TokLoc] = + prefixes.foldLeft(rest): (rest, original) => + if rest is original.tail then original + else original.head :: rest + @tailrec + def loop(tokens: Ls[TokLoc], prefixes: Ls[Ls[TokLoc]]): Ls[TokLoc] = tokens match + case (IDENT("new", false), l1) :: (IDENT("!", true), l2) :: rest => + loop(rest, List(IDENT("new!", false) -> (l1 ++ l2)) :: prefixes) + case (IDENT("yield", false), l1) :: (IDENT("*", true), l2) :: rest => + loop(rest, List(IDENT("yield*", false) -> (l1 ++ l2)) :: prefixes) + // * Remove empty indented sections + case (BRACKETS(Indent, toks), _) :: rest + if toks.forall: + case (NEWLINE | SPACE, _) => true + case _ => false + => + loop(rest, prefixes) + // * Expands end-of-line suspensions that introduce implied indentation, + // * skipping NOISE tokens between `...` and NEWLINE (eg `... // hello\n body`) + // * Note: using `NEWLINE_COMMA` instead of `NEWLINE` causes misparsing of things like `fun foo(..., ...)` + case (SUSPENSION(true), l0) :: NOISE((NEWLINE, l1) :: rest) => + val outerLoc = l0.left ++ rest.lastOption.map(_._2.right) + val innerLoc = l1.right ++ rest.lastOption.map(_._2.left) + rebuild(prefixes, BRACKETS(Indent, preprocessTokens(rest))(innerLoc) -> outerLoc :: Nil) + case _ :: rest => + loop(rest, tokens :: prefixes) + case Nil => rebuild(prefixes, Nil) + loop(tokens, Nil) private def wrap[R](args: => Any)(using l: Line, n: Name)(mkRes: => R): R = printDbg(s"@ ${n.value}${args match { diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls index 01089ae43e..5157544757 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/Examples.mls @@ -6,7 +6,8 @@ import "../../Predef.mls" open Predef module Examples with... -val examples: MutMap.MutMap[Str, {name: Str, source: Str}] = MutMap.empty +type Example = {name: Str, source: Str} +val examples: MutMap.MutMap[Str, Example] = MutMap.empty examples |> MutMap.insert of "hanoi" diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing-web-demo/.mls b/hkmc2/shared/src/test/mlscript/apps/parsing-web-demo/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/apps/parsing-web-demo/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing-web-demo/ExamplesTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing-web-demo/ExamplesTest.mls index a12b46c0bd..b843a19433 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing-web-demo/ExamplesTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing-web-demo/ExamplesTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../../mlscript-compile/Iter.mls" @@ -7,9 +8,9 @@ import "../../../mlscript-compile/apps/parsing-web-demo/Examples.mls" :silent val examples = (Examples.examples |> MutMap.values).sort of - (a, b) => a.name.localeCompare(b.name) + (a: Examples.Example, b: Examples.Example) => a.name.localeCompare(b.name) -examples Iter.each of case entry then +examples Iter.each of (entry: Examples.Example) => print of "Testing example: " + entry.name Test.example of entry.source //│ > Testing example: Extensible Syntax diff --git a/hkmc2/shared/src/test/mlscript/backlog/.mls b/hkmc2/shared/src/test/mlscript/backlog/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/backlog/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/backlog/DestructuringBindings.mls b/hkmc2/shared/src/test/mlscript/backlog/DestructuringBindings.mls index 7d2d0db58f..04c350cb51 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/DestructuringBindings.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/DestructuringBindings.mls @@ -1,43 +1,44 @@ +:. :js :todo [a, b] => [b, a] //│ ╔══[COMPILATION ERROR] Illegal constraint syntax. -//│ ║ l.5: [a, b] => [b, a] +//│ ║ l.6: [a, b] => [b, a] //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Illegal constraint syntax. -//│ ║ l.5: [a, b] => [b, a] +//│ ║ l.6: [a, b] => [b, a] //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: b -//│ ║ l.5: [a, b] => [b, a] +//│ ║ l.6: [a, b] => [b, a] //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.5: [a, b] => [b, a] +//│ ║ l.6: [a, b] => [b, a] //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (constrained type) -//│ ║ l.5: [a, b] => [b, a] +//│ ║ l.6: [a, b] => [b, a] //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. fun eqTup2([a, b], [c, d]) = a == c and b == d //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found tuple -//│ ║ l.23: fun eqTup2([a, b], [c, d]) = a == c and b == d +//│ ║ l.24: fun eqTup2([a, b], [c, d]) = a == c and b == d //│ ╙── ^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found tuple -//│ ║ l.23: fun eqTup2([a, b], [c, d]) = a == c and b == d +//│ ║ l.24: fun eqTup2([a, b], [c, d]) = a == c and b == d //│ ╙── ^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.23: fun eqTup2([a, b], [c, d]) = a == c and b == d +//│ ║ l.24: fun eqTup2([a, b], [c, d]) = a == c and b == d //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: c -//│ ║ l.23: fun eqTup2([a, b], [c, d]) = a == c and b == d +//│ ║ l.24: fun eqTup2([a, b], [c, d]) = a == c and b == d //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: b -//│ ║ l.23: fun eqTup2([a, b], [c, d]) = a == c and b == d +//│ ║ l.24: fun eqTup2([a, b], [c, d]) = a == c and b == d //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: d -//│ ║ l.23: fun eqTup2([a, b], [c, d]) = a == c and b == d +//│ ║ l.24: fun eqTup2([a, b], [c, d]) = a == c and b == d //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/backlog/ForLLM.mls b/hkmc2/shared/src/test/mlscript/backlog/ForLLM.mls index fbb69ebdc5..cf0c304742 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ForLLM.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ForLLM.mls @@ -1,3 +1,4 @@ +:. :js // Instruction for agent: for each test case you solve, move it to its own test file outside of the `backlog` folder @@ -27,7 +28,7 @@ let x = 1 in x //│ ╔══[COMPILATION ERROR] Illegal position for 'in' modifier. -//│ ║ l.28: in x +//│ ║ l.29: in x //│ ╙── ^^ //│ = 1 //│ x = 1 @@ -109,7 +110,7 @@ if 2 :fixme // * Should be a call of two arguments, not a sequenced expression +(1,2) //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.110: +(1,2) +//│ ║ l.111: +(1,2) //│ ╙── ^ //│ = 2 @@ -120,19 +121,19 @@ pattern ShapeArea = | (:radius) => 0 | x => 1 //│ ╔══[COMPILATION ERROR] Found an inconsistent variable in disjunction patterns. -//│ ║ l.120: | (:radius) => 0 +//│ ║ l.121: | (:radius) => 0 //│ ║ ^^^^^^ //│ ╟── The variable is missing from this sub-pattern. -//│ ║ l.121: | x => 1 +//│ ║ l.122: | x => 1 //│ ╙── ^^^^^^ :fixme pattern P = {:radius} => radius //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found type definition -//│ ║ l.130: pattern P = {:radius} => radius +//│ ║ l.131: pattern P = {:radius} => radius //│ ╙── ^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: radius -//│ ║ l.130: pattern P = {:radius} => radius +//│ ║ l.131: pattern P = {:radius} => radius //│ ╙── ^^^^^^ //│ = fun @@ -143,7 +144,7 @@ pattern P = ((:radius)) => radius :fixme pattern P = (:radius) => radius //│ ╔══[WARNING] Unused pattern binding: radius. -//│ ║ l.144: pattern P = (:radius) => radius +//│ ║ l.145: pattern P = (:radius) => radius //│ ╙── ^^^^^^ // ——— ——— ——— diff --git a/hkmc2/shared/src/test/mlscript/backlog/NonReturningStatements.mls b/hkmc2/shared/src/test/mlscript/backlog/NonReturningStatements.mls index 3557776d76..597e7a0aa7 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/NonReturningStatements.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/NonReturningStatements.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/backlog/OfStatements.mls b/hkmc2/shared/src/test/mlscript/backlog/OfStatements.mls index 5a3f5abb8f..606b8f1ec9 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/OfStatements.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/OfStatements.mls @@ -1,3 +1,4 @@ +:. :js :todo @@ -6,10 +7,10 @@ print of let x = 1 x + 1 //│ ╔══[COMPILATION ERROR] Expected a body for let bindings in expression position -//│ ║ l.6: let x = 1 +//│ ║ l.7: let x = 1 //│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.7: x + 1 +//│ ║ l.8: x + 1 //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -18,13 +19,13 @@ print of let x = 1 x + 1 //│ ╔══[COMPILATION ERROR] Illegal position for 'open' statement. -//│ ║ l.17: open Predef +//│ ║ l.18: open Predef //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a body for let bindings in expression position -//│ ║ l.18: let x = 1 +//│ ║ l.19: let x = 1 //│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.19: x + 1 +//│ ║ l.20: x + 1 //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/backlog/UCS.mls b/hkmc2/shared/src/test/mlscript/backlog/UCS.mls index 9aef71c587..8a5872a2d1 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/UCS.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/UCS.mls @@ -1,3 +1,4 @@ +:. :js :ignore @@ -16,22 +17,22 @@ if 1 + 1 * 2 == 1 then "A" |> id is r then r //│ ╔══[PARSE ERROR] Operator cannot be used inside this operator split -//│ ║ l.15: * 2 == 4 then "X" +//│ ║ l.16: * 2 == 4 then "X" //│ ║ ^^ //│ ╟── as it has lower precedence than the splitting operator here -//│ ║ l.15: * 2 == 4 then "X" +//│ ║ l.16: * 2 == 4 then "X" //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected literal in this operator split inner position -//│ ║ l.15: * 2 == 4 then "X" +//│ ║ l.16: * 2 == 4 then "X" //│ ║ ^ //│ ╟── Note: the operator split starts here -//│ ║ l.15: * 2 == 4 then "X" +//│ ║ l.16: * 2 == 4 then "X" //│ ╙── ^ //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.15: * 2 == 4 then "X" +//│ ║ l.16: * 2 == 4 then "X" //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Unrecognized term split (integer literal) -//│ ║ l.15: * 2 == 4 then "X" +//│ ║ l.16: * 2 == 4 then "X" //│ ╙── ^ //│ ═══[COMPILATION ERROR] Unrecognized term split (reference term) //│ ═══[RUNTIME ERROR] Error: match error @@ -82,18 +83,21 @@ let foo = case Tuple(...elements) then elements //│ ╔══[COMPILATION ERROR] Unrecognized pattern (spread). -//│ ║ l.83: Tuple(...elements) then elements +//│ ║ l.84: Tuple(...elements) then elements //│ ╙── ^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.84: Tuple(...elements) then elements +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Class definition 'Tuple' expected 3 pattern arguments, but got 1 +//│ ║ l.80: data class Tuple(a, b, c) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: elements -//│ ║ l.83: Tuple(...elements) then elements +//│ ║ l.84: Tuple(...elements) then elements //│ ╙── ^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Expected three arguments, but found only one argument. -//│ ║ l.83: Tuple(...elements) then elements -//│ ╙── ^^^^^ -//│ foo = fun +//│ foo = fun foo foo(Tuple(1, 2, 3)) -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Error: match error // ——— ——— ——— @@ -106,21 +110,21 @@ fun foo(x) = if x is Foo. Bar then "Bar" Foo then "Foo" //│ ╔══[PARSE ERROR] Expected end of input; found period instead -//│ ║ l.105: fun foo(x) = if x is Foo. +//│ ║ l.109: fun foo(x) = if x is Foo. //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (identifier). -//│ ║ l.105: fun foo(x) = if x is Foo. +//│ ║ l.109: fun foo(x) = if x is Foo. //│ ╙── ^^^ fun foo(x) = if x is Foo .Bar then "Bar" .Foo then "Foo" //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (operator split). -//│ ║ l.115: fun foo(x) = if x is Foo +//│ ║ l.119: fun foo(x) = if x is Foo //│ ║ ^^^ -//│ ║ l.116: .Bar then "Bar" +//│ ║ l.120: .Bar then "Bar" //│ ║ ^^^^^^^^^^^^^^^^^ -//│ ║ l.117: .Foo then "Foo" +//│ ║ l.121: .Foo then "Foo" //│ ╙── ^^^^^^^^^^^^^^^^^ // ——— ——— ——— diff --git a/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls b/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls index 0f57d28b89..6d2adaca7f 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,30 +7,30 @@ class Foo class Foo //│ ╔══[COMPILATION ERROR] Multiple definitions of symbol 'Foo' //│ ╟── defined here -//│ ║ l.5: class Foo +//│ ║ l.6: class Foo //│ ║ ^^^^^^^^^ //│ ╟── defined here -//│ ║ l.6: class Foo +//│ ║ l.7: class Foo //│ ╙── ^^^^^^^^^ :e class Foo object Foo //│ ╔══[COMPILATION ERROR] Illegal overloading of class 'Foo' -//│ ║ l.16: class Foo +//│ ║ l.17: class Foo //│ ║ ^^^^^^^^^ //│ ╟── with object of the same name -//│ ║ l.17: object Foo +//│ ║ l.18: object Foo //│ ╙── ^^^^^^^^^^ :e object Foo class Foo //│ ╔══[COMPILATION ERROR] Illegal overloading of class 'Foo' -//│ ║ l.27: class Foo +//│ ║ l.28: class Foo //│ ║ ^^^^^^^^^ //│ ╟── with object of the same name -//│ ║ l.26: object Foo +//│ ║ l.27: object Foo //│ ╙── ^^^^^^^^^^ :todo // TODO: silence such internal errors when a compilation error is already reported? @@ -37,10 +38,10 @@ class Foo fun Foo = 1 object Foo //│ ╔══[COMPILATION ERROR] Illegal overloading of function 'Foo' -//│ ║ l.37: fun Foo = 1 +//│ ║ l.38: fun Foo = 1 //│ ║ ^^^^^^^^^^^ //│ ╟── with object of the same name -//│ ║ l.38: object Foo +//│ ║ l.39: object Foo //│ ╙── ^^^^^^^^^^ //│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: Unexpected class storage for Foo: parameters=None, independent=None //│ ╟── The compilation result may be incorrect. @@ -60,57 +61,36 @@ fun Foo = 1 object Foo class Foo //│ ╔══[COMPILATION ERROR] Illegal overloading of function 'Foo' -//│ ║ l.59: fun Foo = 1 +//│ ║ l.60: fun Foo = 1 //│ ║ ^^^^^^^^^^^ //│ ╟── with object of the same name -//│ ║ l.60: object Foo +//│ ║ l.61: object Foo //│ ╙── ^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Foo' -//│ ║ l.59: fun Foo = 1 -//│ ║ ^^^^^^^^^^^ -//│ ╟── with class of the same name -//│ ║ l.61: class Foo -//│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal overloading of class 'Foo' -//│ ║ l.61: class Foo +//│ ║ l.62: class Foo //│ ║ ^^^^^^^^^ //│ ╟── with object of the same name -//│ ║ l.60: object Foo +//│ ║ l.61: object Foo //│ ╙── ^^^^^^^^^^ -:todo // TODO: support -:e +// Class and module companions can have a function with the same name. class Foo fun Foo = 1 -//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Foo' -//│ ║ l.85: fun Foo = 1 -//│ ║ ^^^^^^^^^^^ -//│ ╟── with class of the same name -//│ ║ l.84: class Foo -//│ ╙── ^^^^^^^^^ -// TODO: support -:e fun Foo = 1 module Foo -//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Foo' -//│ ║ l.95: fun Foo = 1 -//│ ║ ^^^^^^^^^^^ -//│ ╟── with module of the same name -//│ ║ l.96: module Foo -//│ ╙── ^^^^^^^^^^ // TODO: support :e val Foo = 1 module Foo //│ ╔══[COMPILATION ERROR] Not yet supported: overloading of value 'Foo' -//│ ║ l.106: val Foo = 1 -//│ ║ ^^^^^^^^^^^ +//│ ║ l.86: val Foo = 1 +//│ ║ ^^^^^^^^^^^ //│ ╟── with module of the same name -//│ ║ l.107: module Foo -//│ ╙── ^^^^^^^^^^ +//│ ║ l.87: module Foo +//│ ╙── ^^^^^^^^^^ //│ Foo = class Foo @@ -118,11 +98,11 @@ module Foo fun (**) f1(a, b) = "[" + a + " * " + b + "]" fun (**) f2(a, b) = "(" + a + " + " + b + ")" //│ ╔══[COMPILATION ERROR] Symbolic name '**' of definition 'f1' is already used -//│ ║ l.118: fun (**) f1(a, b) = "[" + a + " * " + b + "]" -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.98: fun (**) f1(a, b) = "[" + a + " * " + b + "]" +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── by sibling member 'f2' -//│ ║ l.119: fun (**) f2(a, b) = "(" + a + " + " + b + ")" -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.99: fun (**) f2(a, b) = "(" + a + " + " + b + ")" +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1 ** 2 //│ = "(1 + 2)" @@ -132,10 +112,10 @@ fun (**) t() = 0 fun (++) t() = 1 //│ ╔══[COMPILATION ERROR] Multiple definitions of symbol 't' //│ ╟── defined here -//│ ║ l.131: fun (**) t() = 0 +//│ ║ l.111: fun (**) t() = 0 //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── defined here -//│ ║ l.132: fun (++) t() = 1 +//│ ║ l.112: fun (++) t() = 1 //│ ╙── ^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/DynamicFields.mls b/hkmc2/shared/src/test/mlscript/basics/DynamicFields.mls index 9d457f2690..763328b058 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DynamicFields.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DynamicFields.mls @@ -1,3 +1,4 @@ +:. :js @@ -62,6 +63,7 @@ foo.["x"] class DynCtor() with + declare val dynField: Int set this.("dynField") = 42 DynCtor().dynField diff --git a/hkmc2/shared/src/test/mlscript/basics/DynamicInstantiation.mls b/hkmc2/shared/src/test/mlscript/basics/DynamicInstantiation.mls index 3f7c39daf4..f53e966056 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DynamicInstantiation.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DynamicInstantiation.mls @@ -1,3 +1,4 @@ +:. :js @@ -51,19 +52,27 @@ new! (id(C)) :e :re new id(C) -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found selection. -//│ ║ l.53: new id(C) -//│ ║ ^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] TypeError: Predef.id is not a constructor +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.54: new id(C) +//│ ║ ^^^^^^^^^ +//│ ╟── Function 'id' cannot be instantiated with keyword 'new'. +//│ ║ in ../../mlscript-compile/Predef.mls +//│ ║ l.19: fun id(x) = x +//│ ╙── ^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here +//│ ║ l.54: new id(C) +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e new (id(C)) -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found application. -//│ ║ l.61: new (id(C)) -//│ ║ ^^^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ = C +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.68: new (id(C)) +//│ ║ ^^^^^^^^^^^ +//│ ╟── Invalid class expression: application +//│ ║ l.68: new (id(C)) +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. data class C(x, y) @@ -89,7 +98,7 @@ f(1, 2) //│ ═══[RUNTIME ERROR] TypeError: f3 is not a function -new! C.class(1, 2) +new! C!class(1, 2) //│ = C(1, 2) diff --git a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls index 47365b5af1..bdd351ea55 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls @@ -31,7 +31,7 @@ xs.reverse() :re xs.map(x => x * 2) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.193: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in MiscArrayTests.mls:32 diff --git a/hkmc2/shared/src/test/mlscript/basics/MutRcd.mls b/hkmc2/shared/src/test/mlscript/basics/MutRcd.mls index 2334819a1c..a7563af3d8 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MutRcd.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MutRcd.mls @@ -1,7 +1,9 @@ +:. :js -let r = {a: 0} +// Dynamic receivers keep property additions and missing-field checks observable at runtime. +val r: dyn = {a: 0} //│ r = {a: 0} set @@ -17,7 +19,7 @@ r.foo //│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' -let r = mut {a: 1} +val r: dyn = mut {a: 1} //│ r = {a: 1} set @@ -33,7 +35,7 @@ r.foo //│ = 1 -let r = mut {} +val r: dyn = mut {} //│ r = {} set r.foo = 1 @@ -44,7 +46,7 @@ r.foo :sjs -let r = {} +val r: dyn = {} //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let r3; r3 = runtime.Unit; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -61,10 +63,10 @@ r.foo //│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' :re -().foo +(() as dyn).foo //│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' -let s = {} +val s: dyn = {} //│ s = () set s.foo = 2 @@ -77,7 +79,7 @@ r.foo :e mut() //│ ╔══[COMPILATION ERROR] Illegal position for 'mut' modifier. -//│ ║ l.78: mut() +//│ ║ l.80: mut() //│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/New.mls b/hkmc2/shared/src/test/mlscript/basics/New.mls index 68fffaef3f..fd2a085f0b 100644 --- a/hkmc2/shared/src/test/mlscript/basics/New.mls +++ b/hkmc2/shared/src/test/mlscript/basics/New.mls @@ -1,3 +1,4 @@ +:. :js @@ -14,59 +15,73 @@ new //│ = Foo :e -:w new Foo Foo -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found block. -//│ ║ l.20: Foo -//│ ║ ^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ╔══[WARNING] Pure expression in statement position +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.18: new +//│ ║ ^^^ //│ ║ l.19: Foo +//│ ║ ^^^^^ +//│ ║ l.20: Foo +//│ ║ ^^^^^ +//│ ╟── Invalid class expression: block +//│ ║ l.20: Foo //│ ╙── ^^^ -//│ = Foo +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // * Interesting... it tries to call `Foo()` and instantiate the resulting class value... :e :re new Foo() -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found application. -//│ ║ l.34: Foo() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.37: Foo() //│ ║ ^^^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo cannot be invoked without 'new' +//│ ╟── Class 'Foo' must be instantiated with 'new'. +//│ ║ l.5: class Foo +//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.36: new +//│ ║ ^^^ +//│ ║ l.37: Foo() +//│ ║ ^^^^^^^ +//│ ╟── Invalid class expression: application +//│ ║ l.37: Foo() +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e new Foo () //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (unit). -//│ ║ l.44: () +//│ ║ l.57: () //│ ╙── ^^ //│ = Foo :e -:w :re new Foo () -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found block. -//│ ║ l.55: () -//│ ║ ^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.54: Foo -//│ ╙── ^^^ -//│ ═══[RUNTIME ERROR] TypeError: runtime.Unit is not a constructor +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.65: new +//│ ║ ^^^ +//│ ║ l.66: Foo +//│ ║ ^^^^^ +//│ ║ l.67: () +//│ ║ ^^^^ +//│ ╟── Invalid class expression: block +//│ ║ l.67: () +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e new Foo () //│ ╔══[COMPILATION ERROR] Illegal juxtaposition right-hand side (unit). -//│ ║ l.67: () +//│ ║ l.82: () //│ ╙── ^^ //│ = Foo @@ -79,10 +94,24 @@ data class Foo(a,b,c) new Foo(...xs) //│ = Foo(1, 2, 3) +:e new Foo(...[1, 2]) +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.98: new Foo(...[1, 2]) +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Foo' expected 3 arguments, but got 2 +//│ ║ l.92: data class Foo(a,b,c) +//│ ╙── ^^^^^^^^^^^^^^^^ //│ = Foo(1, 2, undefined) +:e new Foo(...[1, 2, 3, 4]) +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.108: new Foo(...[1, 2, 3, 4]) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Foo' expected 3 arguments, but got 4 +//│ ║ l.92: data class Foo(a,b,c) +//│ ╙── ^^^^^^^^^^^^^^^^ //│ = Foo(1, 2, 3) diff --git a/hkmc2/shared/src/test/mlscript/basics/NewMut.mls b/hkmc2/shared/src/test/mlscript/basics/NewMut.mls index dc7e4df11a..05f1775cf3 100644 --- a/hkmc2/shared/src/test/mlscript/basics/NewMut.mls +++ b/hkmc2/shared/src/test/mlscript/basics/NewMut.mls @@ -1,3 +1,4 @@ +:. :js @@ -7,10 +8,11 @@ class Foo with set x += 1 +// Dynamic receivers allow these tests to add fields while retaining runtime checks. :sjs -let f = new Foo +val f: dyn = new Foo //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f; f = globalThis.Object.freeze(new Foo1()); +//│ let f, tmp; tmp = globalThis.Object.freeze(new Foo1()); f = tmp; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ f = Foo { x: 0 } @@ -37,9 +39,9 @@ f.y :sjs // :elt -let f = new mut Foo +val f: dyn = new mut Foo //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f1; f1 = new Foo1(); +//│ let f1, tmp1; tmp1 = new Foo1(); f1 = tmp1; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ f = Foo { x: 0 } @@ -61,21 +63,23 @@ f.y :e let f = mut new Foo //│ ╔══[COMPILATION ERROR] Illegal position for 'mut' modifier. -//│ ║ l.62: let f = mut new Foo +//│ ║ l.64: let f = mut new Foo //│ ╙── ^^^ //│ f = Foo { x: 0 } :e :re new mut Foo.x -//│ ╔══[COMPILATION ERROR] Expected a statically known class; found selection. -//│ ║ l.70: new mut Foo.x +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.72: new mut Foo.x //│ ║ ^^^^^ -//│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. -//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' +//│ ╟── Class value does not contain member 'x' +//│ ║ l.5: class Foo with +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -let f = new mut Foo() +val f: dyn = new mut Foo() //│ f = Foo { x: 0 } set @@ -124,7 +128,7 @@ f.x //│ = 1 -let f = Foo(0) +val f: dyn = Foo(0) //│ f = Foo(0) set @@ -164,7 +168,7 @@ f.y -let f = new Object with +val f: dyn = new Object with val x = 12 //│ f = $anon { x: 12 } @@ -180,7 +184,7 @@ f.y //│ ═══[RUNTIME ERROR] Error: Access to required field 'y' yielded 'undefined' -let f = new mut Object with +val f: dyn = new mut Object with val x = 12 //│ f = $anon { x: 12 } diff --git a/hkmc2/shared/src/test/mlscript/basics/NewlineOps.mls b/hkmc2/shared/src/test/mlscript/basics/NewlineOps.mls index ef1460f583..9b669ecc06 100644 --- a/hkmc2/shared/src/test/mlscript/basics/NewlineOps.mls +++ b/hkmc2/shared/src/test/mlscript/basics/NewlineOps.mls @@ -1,3 +1,4 @@ +:. :js @@ -57,7 +58,7 @@ let xs = [1, 2, 3] let ys = [4, 5, 6] -fun len(xs) = xs.length +fun len(xs: Array) = xs.length //│ xs = [1, 2, 3] //│ ys = [4, 5, 6] @@ -84,7 +85,7 @@ let r = 10 in r //│ rhs = Tup of Ls of //│ IntLit of 1 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.74: let r = 10 in r +//│ ║ l.75: let r = 10 in r //│ ╙── ^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = -1 @@ -160,11 +161,20 @@ let x .a //│ = 2 +:e :re 2 |> (a: _) .a -//│ ═══[RUNTIME ERROR] Error: Access to required field 'a' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.167: |> (a: _) +//│ ║ ^^^^^^ +//│ ║ l.168: .a +//│ ║ ^^^^^^ +//│ ╟── Function literal does not contain member 'a' +//│ ║ l.167: |> (a: _) +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge :re @@ -172,7 +182,7 @@ let x |> (a: _) .a //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (leading dot selection) -//│ ║ l.173: .a +//│ ║ l.183: .a //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -215,7 +225,7 @@ let x :e // Note: this used to parse as a juxtaposition (the curly braces were treated equally to an indented section) 2 { * 1 { + 2 } } //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.216: 2 { * 1 { + 2 } } +//│ ║ l.226: 2 { * 1 { + 2 } } //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -322,10 +332,10 @@ let incr = _ + 1 :w 2, * 3 //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.323: 2, * 3 +//│ ║ l.333: 2, * 3 //│ ╙── ^ //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.323: 2, * 3 +//│ ║ l.333: 2, * 3 //│ ╙── ^ //│ = fun @@ -349,7 +359,7 @@ let incr = _ + 1 //│ = 16 module Arr with - val len = _.length + val len = (xs: Array) => xs.length let xs = [1, 2] @@ -379,9 +389,9 @@ xs |> Arr.len ,+ ys |> Arr.len //│ ╔══[PARSE ERROR] Expected start of expression in this position; found new line instead -//│ ║ l.379: ,+ +//│ ║ l.389: ,+ //│ ║ ^ -//│ ║ l.380: ys |> Arr.len +//│ ║ l.390: ys |> Arr.len //│ ╙── //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -395,9 +405,9 @@ xs |> Arr.len +, ys |> Arr.len //│ ╔══[PARSE ERROR] Expected start of expression in this position; found new line instead -//│ ║ l.395: +, +//│ ║ l.405: +, //│ ║ ^ -//│ ║ l.396: ys |> Arr.len +//│ ║ l.406: ys |> Arr.len //│ ╙── //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -428,10 +438,10 @@ new Foo(1) is Foo :fixme // ? new Foo(1) ,is Foo //│ ╔══[PARSE ERROR] Expected start of expression in this position; found 'is' keyword instead -//│ ║ l.429: new Foo(1) ,is Foo +//│ ║ l.439: new Foo(1) ,is Foo //│ ╙── ^^ //│ ╔══[PARSE ERROR] Expected end of input; found identifier instead -//│ ║ l.429: new Foo(1) ,is Foo +//│ ║ l.439: new Foo(1) ,is Foo //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -439,10 +449,10 @@ new Foo(1) ,is Foo new Foo(1) is Foo //│ ╔══[PARSE ERROR] Expected start of expression in this position; found 'is' keyword instead -//│ ║ l.440: is Foo +//│ ║ l.450: is Foo //│ ╙── ^^ //│ ╔══[PARSE ERROR] Expected end of input; found identifier instead -//│ ║ l.440: is Foo +//│ ║ l.450: is Foo //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/NewlineSels.mls b/hkmc2/shared/src/test/mlscript/basics/NewlineSels.mls index e523778f47..b66f2e5a88 100644 --- a/hkmc2/shared/src/test/mlscript/basics/NewlineSels.mls +++ b/hkmc2/shared/src/test/mlscript/basics/NewlineSels.mls @@ -1,3 +1,4 @@ +:. :js @@ -39,10 +40,10 @@ rcd rcd .a //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.39: rcd +//│ ║ l.40: rcd //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (leading dot selection) -//│ ║ l.40: .a +//│ ║ l.41: .a //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -55,7 +56,7 @@ let rcd = let rcd = a: x => {b: y => c: x + y} //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.56: a: x => {b: y => c: x + y} +//│ ║ l.57: a: x => {b: y => c: x + y} //│ ╙── ^ //│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Unexpected effectful in subterm position: Effectful(InfixApp(Ident(b),Keywrd(keyword ':'),InfixApp(Tup(List(Ident(y))),Keywrd(keyword '=>'),InfixApp(Ident(c),Keywrd(keyword ':'),OpApp(Ident(x),Ident(+),List(Ident(y)))))),Error()) diff --git a/hkmc2/shared/src/test/mlscript/basics/StrTest.mls b/hkmc2/shared/src/test/mlscript/basics/StrTest.mls index 9f7222e215..3aa1f7e180 100644 --- a/hkmc2/shared/src/test/mlscript/basics/StrTest.mls +++ b/hkmc2/shared/src/test/mlscript/basics/StrTest.mls @@ -1,8 +1,10 @@ +:. :js import "../../mlscript-compile/StrOps.mls" open StrOps +open StrOps { concat2, ~ } :sjs @@ -27,23 +29,31 @@ concat of :e (~)("a", "b", "c") -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 3 -//│ ║ l.29: (~)("a", "b", "c") -//│ ╙── ^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.31: (~)("a", "b", "c") +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'concat2' expected 2 arguments, but got 3 +//│ ║ in ../../mlscript-compile/StrOps.mls +//│ ║ l.6: fun (~) concat2(a, b) = a + b +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = "ab" :e (~)("a") -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 1 -//│ ║ l.37: (~)("a") -//│ ╙── ^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.43: (~)("a") +//│ ║ ^^^^^^^^ +//│ ╟── Function 'concat2' expected 2 arguments, but got 1 +//│ ║ in ../../mlscript-compile/StrOps.mls +//│ ║ l.6: fun (~) concat2(a, b) = a + b +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = "aundefined" :ge ~"a" //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (negation type) -//│ ║ l.44: ~"a" +//│ ║ l.54: ~"a" //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -51,7 +61,7 @@ concat of :ge ~("a", "b") //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (negation type) -//│ ║ l.52: ~("a", "b") +//│ ║ l.62: ~("a", "b") //│ ╙── ^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -60,13 +70,13 @@ concat of :ge ~ of "a", "b" //│ ╔══[PARSE ERROR] Expected start of expression in this position; found 'of' keyword instead -//│ ║ l.61: ~ of "a", "b" +//│ ║ l.71: ~ of "a", "b" //│ ╙── ^^ //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.61: ~ of "a", "b" +//│ ║ l.71: ~ of "a", "b" //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (negation type) -//│ ║ l.61: ~ of "a", "b" +//│ ║ l.71: ~ of "a", "b" //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/Underscores.mls b/hkmc2/shared/src/test/mlscript/basics/Underscores.mls index cc1a77315f..b81e0e2625 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Underscores.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Underscores.mls @@ -1,3 +1,4 @@ +:. :js @@ -14,7 +15,7 @@ _ :e y: _ //│ ╔══[COMPILATION ERROR] Illegal position for '_' placeholder. -//│ ║ l.15: y: _ +//│ ║ l.16: y: _ //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ y = undefined @@ -60,17 +61,35 @@ print(_) :sjs -let test = _.f(0, _, 2) +val test: ({f: (Int, Int, Int) -> ()}, Int) -> () = _.f(0, _, 2) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let test, test1; -//│ test1 = function test(_0, _1) { return runtime.safeCall(_0.f(0, _1, 2)) }; -//│ test = test1; +//│ let test, lambda7; +//│ lambda7 = (undefined, function (_0, _1) { +//│ return runtime.safeCall(_0.f(0, _1, 2)) +//│ }); +//│ test = lambda7; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ test = fun test +//│ test = fun +:e :re test(f: print)(1) -//│ ═══[RUNTIME ERROR] Error: Function 'test' expected 2 arguments but got 1 +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.76: test(f: print)(1) +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Function with a declared signature expected 2 arguments, but got 1 +//│ ║ l.64: val test: ({f: (Int, Int, Int) -> ()}, Int) -> () = _.f(0, _, 2) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.76: test(f: print)(1) +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Value with no known interface cannot be called like a function. +//│ ║ l.64: val test: ({f: (Int, Int, Int) -> ()}, Int) -> () = _.f(0, _, 2) +//│ ║ ^^ +//│ ╟── No member interface is known for this type. +//│ ║ l.64: val test: ({f: (Int, Int, Int) -> ()}, Int) -> () = _.f(0, _, 2) +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 1 test((f: print), 1) //│ > 0 1 2 @@ -97,12 +116,12 @@ print(inc(_)) :sjs _ is Int //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let lambda8; -//│ lambda8 = (undefined, function (_0) { +//│ let lambda9; +//│ lambda9 = (undefined, function (_0) { //│ if (globalThis.Number.isInteger(_0)) { return true } //│ return false; //│ }); -//│ return lambda8 +//│ return lambda9 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun @@ -139,7 +158,7 @@ mkObj(1, 2) :re let mkObj = x: _, y: _, z: 3 //│ ╔══[COMPILATION ERROR] Illegal position for '_' placeholder. -//│ ║ l.140: let mkObj = x: _, y: _, z: 3 +//│ ║ l.159: let mkObj = x: _, y: _, z: 3 //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ mkObj = fun mkObj diff --git a/hkmc2/shared/src/test/mlscript/block-staging/.mls b/hkmc2/shared/src/test/mlscript/block-staging/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/block-staging/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/block-staging/Casts.mls b/hkmc2/shared/src/test/mlscript/block-staging/Casts.mls index ff734fd6e3..31b8da7bfc 100644 --- a/hkmc2/shared/src/test/mlscript/block-staging/Casts.mls +++ b/hkmc2/shared/src/test/mlscript/block-staging/Casts.mls @@ -1,3 +1,4 @@ +:. // The staged-module compiler (`ReflectionInstrumenter`) has to keep a `Cast` as data rather than // dropping it: the staged mirror (`Block.mls`) has a `Cast` node, so the coercion survives into the // produced IR instead of being silently discarded on the way in. diff --git a/hkmc2/shared/src/test/mlscript/block-staging/Nested.mls b/hkmc2/shared/src/test/mlscript/block-staging/Nested.mls index dd89573888..3a53c297e1 100644 --- a/hkmc2/shared/src/test/mlscript/block-staging/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/block-staging/Nested.mls @@ -1,3 +1,4 @@ +:. :js :ftc :sir diff --git a/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls b/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls index 621c69fca0..0029728606 100644 --- a/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls +++ b/hkmc2/shared/src/test/mlscript/block-staging/PrintCode.mls @@ -1,3 +1,4 @@ +:. :staging :js @@ -5,6 +6,7 @@ import "../../mlscript-compile/Block.mls" import "../../mlscript-compile/Option.mls" open Block +open Block { Symbol } open Option printCode(FunDefn(Symbol("f"), [[Symbol("x")]], Return(ValueLit(1)))) diff --git a/hkmc2/shared/src/test/mlscript/block-staging/Syntax.mls b/hkmc2/shared/src/test/mlscript/block-staging/Syntax.mls index 767ab21e60..16a7fa830c 100644 --- a/hkmc2/shared/src/test/mlscript/block-staging/Syntax.mls +++ b/hkmc2/shared/src/test/mlscript/block-staging/Syntax.mls @@ -1,3 +1,4 @@ +:. :pt staged module A diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls index 4e9b5d1f24..3ef81332e7 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls @@ -1,3 +1,4 @@ +:. :js @@ -26,8 +27,10 @@ let s = Some(0) if s is Some(x) then x //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (s instanceof Some1.class) { -//│ return s.value +//│ let scrut1; +//│ scrut1 = s; +//│ if (scrut1 instanceof Some1.class) { +//│ return scrut1.value //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -45,10 +48,11 @@ if s is Some(1) then 0 None then 1 //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let arg$Some$0$; -//│ match s⁰ +//│ let scrut, arg$Some$0$; +//│ set scrut = s⁰; +//│ match scrut //│ Some⁰ => -//│ set arg$Some$0$ = s⁰.value⁰; +//│ set arg$Some$0$ = scrut.value⁰; //│ match arg$Some$0$ //│ 1 => //│ return 0 @@ -61,10 +65,11 @@ if s is //│ throw new globalThis⁰.Error⁰("match error") //│ end //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let arg$Some$0$; -//│ match s⁰ +//│ let scrut, arg$Some$0$; +//│ set scrut = s⁰; +//│ match scrut //│ Some⁰ => -//│ set arg$Some$0$ = s⁰.value⁰; +//│ set arg$Some$0$ = scrut.value⁰; //│ match arg$Some$0$ //│ 1 => //│ return 0 @@ -123,7 +128,7 @@ M.d fun f(x) = if x is M.D then 1 else 0 -M.D.class +M.D!class //│ = class D M is M.D @@ -154,11 +159,11 @@ fun f(x) = if x is //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f4; //│ f4 = function f(x2) { -//│ let scrut1, arg$Some$0$1; +//│ let scrut5, arg$Some$0$1; //│ if (x2 instanceof Some1.class) { //│ arg$Some$0$1 = x2.value; -//│ scrut1 = arg$Some$0$1 > 0; -//│ if (scrut1 === true) { +//│ scrut5 = arg$Some$0$1 > 0; +//│ if (scrut5 === true) { //│ return 42 //│ } //│ return Predef.print("oops"); diff --git a/hkmc2/shared/src/test/mlscript/codegen/ConsoleLog.mls b/hkmc2/shared/src/test/mlscript/codegen/ConsoleLog.mls index 1d3ef73438..4fda1fe031 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ConsoleLog.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ConsoleLog.mls @@ -1,3 +1,4 @@ +:. :js :global @@ -5,7 +6,7 @@ :silent -declare val console +declare val console: {log: Function} //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -26,7 +27,7 @@ console.log("b") let l = console.log l(123) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let l; l = globalThis.console.log; return runtime.safeCall(l(123)) +//│ let l; l = globalThis.console.log; return runtime.safeCall(globalThis.console.log(123)) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > 123 //│ l = fun log diff --git a/hkmc2/shared/src/test/mlscript/codegen/CurriedClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/CurriedClasses.mls index bf065ed09e..2ae37d2a6e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CurriedClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CurriedClasses.mls @@ -1,3 +1,4 @@ +:. :js @@ -76,8 +77,8 @@ new A(...[4])(...[5]).y //│ = 5 -// * First-class class value; FIXME: should be forbidden -let a = A.class +// * Reflect the raw JavaScript class behind the curried constructor. +let a = A!class //│ a = class A // * Dynamic instantiation of a raw JS class is not eta-expanded. @@ -106,22 +107,22 @@ h.y class A(x1, x2, ...xs)(y1, ...ys) with fun fields = [x1, x2, xs, y1, ys] //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. -//│ ║ l.106: class A(x1, x2, ...xs)(y1, ...ys) with +//│ ║ l.107: class A(x1, x2, ...xs)(y1, ...ys) with //│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. -//│ ║ l.106: class A(x1, x2, ...xs)(y1, ...ys) with +//│ ║ l.107: class A(x1, x2, ...xs)(y1, ...ys) with //│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'xs' -//│ ║ l.107: fun fields = [x1, x2, xs, y1, ys] +//│ ║ l.108: fun fields = [x1, x2, xs, y1, ys] //│ ║ ^^ //│ ╟── which references the symbol introduced here -//│ ║ l.106: class A(x1, x2, ...xs)(y1, ...ys) with +//│ ║ l.107: class A(x1, x2, ...xs)(y1, ...ys) with //│ ╙── ^^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'ys' -//│ ║ l.107: fun fields = [x1, x2, xs, y1, ys] +//│ ║ l.108: fun fields = [x1, x2, xs, y1, ys] //│ ║ ^^ //│ ╟── which references the symbol introduced here -//│ ║ l.106: class A(x1, x2, ...xs)(y1, ...ys) with +//│ ║ l.107: class A(x1, x2, ...xs)(y1, ...ys) with //│ ╙── ^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let A3; @@ -155,21 +156,37 @@ class A(x1, x2, ...xs)(y1, ...ys) with //│ static [definitionMetadata] = ["class", "A", [null, null]]; //│ }); //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'xs' -//│ ║ l.107: fun fields = [x1, x2, xs, y1, ys] +//│ ║ l.108: fun fields = [x1, x2, xs, y1, ys] //│ ║ ^^ //│ ╟── which references the symbol introduced here -//│ ║ l.106: class A(x1, x2, ...xs)(y1, ...ys) with +//│ ║ l.107: class A(x1, x2, ...xs)(y1, ...ys) with //│ ╙── ^^ //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'ys' -//│ ║ l.107: fun fields = [x1, x2, xs, y1, ys] +//│ ║ l.108: fun fields = [x1, x2, xs, y1, ys] //│ ║ ^^ //│ ╟── which references the symbol introduced here -//│ ║ l.106: class A(x1, x2, ...xs)(y1, ...ys) with +//│ ║ l.107: class A(x1, x2, ...xs)(y1, ...ys) with //│ ╙── ^^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :todo new A(1, 2, 3)(4, 5, 6).fields +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.173: new A(1, 2, 3)(4, 5, 6).fields +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'A' expected 2 arguments, but got 3 +//│ ║ l.107: class A(x1, x2, ...xs)(y1, ...ys) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.108: fun fields = [x1, x2, xs, y1, ys] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.173: new A(1, 2, 3)(4, 5, 6).fields +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'A' expected 1 argument, but got 3 +//│ ║ l.107: class A(x1, x2, ...xs)(y1, ...ys) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.108: fun fields = [x1, x2, xs, y1, ys] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] ReferenceError: xs is not defined diff --git a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls index f7791cbcaa..53c5e4032a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls @@ -1,3 +1,4 @@ +:. :js :noInline @@ -300,7 +301,7 @@ data class Foo(x) with //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -fun foo(f) = +fun foo(f: {oops: Int}) = f.oops @@ -308,7 +309,7 @@ foo(Foo(42)) //│ = 42 -fun foo(f) = +fun foo(f: {oops: Int}) = f.oops data class Foo(x) with @@ -317,8 +318,15 @@ data class Foo(x) with foo(Foo(42)) //│ = 42 +:e :re foo(Foo(42))() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.323: foo(Foo(42))() +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Int' cannot be called like a function. +//│ ║ l.312: fun foo(f: {oops: Int}) = +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] TypeError: baseCall3 is not a function diff --git a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls index 47fe350c4e..25a813dc67 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls @@ -1,3 +1,4 @@ +:. :js :noModuleCheck @@ -54,9 +55,16 @@ val Test = "oops" //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ Test = "oops" +:e :re Test.foo() -//│ ═══[RUNTIME ERROR] TypeError: Test2.foo is not a function +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.60: Test.foo() +//│ ║ ^^^^^^^^ +//│ ╟── String literal does not contain member 'foo' +//│ ║ l.52: val Test = "oops" +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :sjs @@ -78,10 +86,10 @@ module Test with val x = 1 let x = 2 //│ ╔══[COMPILATION ERROR] Name 'x' is already used -//│ ║ l.79: let x = 2 +//│ ║ l.87: let x = 2 //│ ║ ^^^^^ //│ ╟── by a member declared in the same block -//│ ║ l.78: val x = 1 +//│ ║ l.86: val x = 1 //│ ╙── ^^^^^^^^^ //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let Test4; @@ -185,7 +193,7 @@ module Whoops with val w: module Whoops = this fun g() = f() //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'module Whoops' at an unrelated type 'module Whoops.Whoops' -//│ ║ l.182: val v: module Whoops = this +//│ ║ l.190: val v: module Whoops = this //│ ╙── ^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Whoops2; @@ -230,7 +238,7 @@ Whoops.Whoops.g() :e Runtime //│ ╔══[COMPILATION ERROR] Name not found: Runtime -//│ ║ l.231: Runtime +//│ ║ l.239: Runtime //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls index 54cc53a85a..5a6592302f 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportExample.mls @@ -1,3 +1,4 @@ +:. :js @@ -18,10 +19,14 @@ Example.pubFun :e :re Example.privFun -//│ ╔══[COMPILATION ERROR] Module 'Example' does not contain member 'privFun' -//│ ║ l.20: Example.privFun -//│ ╙── ^^^^^^^^ -//│ ═══[RUNTIME ERROR] Error: Access to required field 'privFun' yielded 'undefined' +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.21: Example.privFun +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Module 'Example' does not contain member 'privFun' +//│ ║ in ../../mlscript-compile/Example.mls +//│ ║ l.9: module Example with ... +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :re Example.assertFail() @@ -42,6 +47,7 @@ n / 2 // * Builtin operators can be shadowed/rebound open Example +open Example { funnySlash, / } :sjs inc / 2 diff --git a/hkmc2/shared/src/test/mlscript/codegen/Open.mls b/hkmc2/shared/src/test/mlscript/codegen/Open.mls index 2343238a5d..bab91e2990 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Open.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Open.mls @@ -1,3 +1,4 @@ +:. :js @@ -20,15 +21,18 @@ x + y :e z //│ ╔══[COMPILATION ERROR] Name not found: z -//│ ║ l.21: z +//│ ║ l.22: z //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e open Foo { a } -//│ ╔══[COMPILATION ERROR] Module 'Foo' does not contain member 'a' -//│ ║ l.28: open Foo { a } -//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.29: open Foo { a } +//│ ║ ^ +//│ ╟── Module 'Foo' does not contain member 'a' +//│ ║ l.5: module Foo with +//│ ╙── ^^^ open Bar { y, Baz } @@ -46,12 +50,27 @@ module Bar with val str = "oops" //│ str = "oops" +:e open str { length, oops } +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.54: open str { length, oops } +//│ ║ ^^^^ +//│ ╟── String literal does not contain member 'oops' +//│ ║ l.50: val str = "oops" +//│ ╙── ^^^^^^ length //│ = 4 +:e +:re oops === () -//│ = false +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.67: oops === () +//│ ║ ^^^^ +//│ ╟── String literal does not contain member 'oops' +//│ ║ l.50: val str = "oops" +//│ ╙── ^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls index 6599b1c2dc..fe8d954166 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls @@ -1,3 +1,4 @@ +:. :js @@ -25,7 +26,14 @@ let g = f(_, 3) g(2) //│ = [1, 2, 3] +:e foo(_, _, 3) of 1, 2 +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.30: foo(_, _, 3) of 1, 2 +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Tuple literal cannot be called like a function. +//│ ║ l.5: fun foo(x, y, z) = [x, y, z] +//│ ╙── ^^^^^^^^^ //│ = fun {foo(_, _, 3)} of 1, 2 @@ -87,8 +95,15 @@ let i = _(0, 1, _) i(print, 2) //│ > 0 1 2 +:e :re i(print)(2) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.100: i(print)(2) +//│ ║ ^^^^^^^^ +//│ ╟── Function literal expected 2 arguments, but got 1 +//│ ║ l.89: let i = _(0, 1, _) +//│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'i' expected 2 arguments but got 1 // :re @@ -96,7 +111,20 @@ i((x, y, z) => x + y + z, 2) //│ = 3 // :re +:e i((...args) => [...args], _)(2) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.100: i(print)(2) +//│ ║ ^^^^^^^^^^^ +//│ ╟── Tuple literal cannot be called like a function. +//│ ║ l.89: let i = _(0, 1, _) +//│ ╙── ^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.115: i((...args) => [...args], _)(2) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Tuple literal cannot be called like a function. +//│ ║ l.89: let i = _(0, 1, _) +//│ ╙── ^^^^^^^^^ //│ = fun @@ -110,8 +138,16 @@ i((...args) => [...args], _)(2) 123 |> ((_ + 1) >> (_ * 2)) //│ = 248 +:e :re 123 |> (_ + 1 >> _ * 2) +//│ ╔══[COMPILATION ERROR] Resolution error in function type +//│ ║ l.30: fun (|>) pipeInto[A, B](x: A, f: A -> B): B = f(x) +//│ ║ ^^^^^^ +//│ ╟── Callback parameter list does not match its declared function type. +//│ ║ in PartialApps.mls:143 +//│ ║ l.143: 123 |> (_ + 1 >> _ * 2) +//│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 1 (_ + 1 + _ * 2) of 123, 456 @@ -201,16 +237,20 @@ _ - _ of 1, 2 :pe |> 1 //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.202: |> 1 +//│ ║ l.238: |> 1 //│ ╙── ^ //│ = fun pipeInto :e :re |> (1) -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 1 -//│ ║ l.210: |> (1) -//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.246: |> (1) +//│ ║ ^^^^^^ +//│ ╟── Function 'pipeInto' expected 2 arguments, but got 1 +//│ ║ in ../../mlscript-compile/Predef.mls +//│ ║ l.30: fun (|>) pipeInto[A, B](x: A, f: A -> B): B = f(x) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: f is not a function |> (1, _ - 2) @@ -254,10 +294,10 @@ _ - _ of 1, 2 :w let f = if _ then 1 else 0 //│ ╔══[WARNING] This catch-all clause makes the following branches unreachable. -//│ ║ l.255: let f = if _ then 1 else 0 +//│ ║ l.295: let f = if _ then 1 else 0 //│ ║ ^^^^^^ //│ ╟── This branch is unreachable. -//│ ║ l.255: let f = if _ then 1 else 0 +//│ ║ l.295: let f = if _ then 1 else 0 //│ ╙── ^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f23; f23 = 1; diff --git a/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls b/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls index 74db669543..ed7444afaa 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls @@ -1,13 +1,14 @@ +:. :js :sjs -let folderName1 = process.env.PWD.split("/").pop() +let folderName1 = (process.env!PWD as Str).split("/").pop() in let folderName2 = process.cwd().split("/").pop() in folderName2 === folderName1 || folderName2 === "shared" //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let folderName1, folderName2, tmp, tmp1, tmp2, tmp3; -//│ tmp = runtime.safeCall(globalThis.process.env.PWD.split("/")); +//│ tmp = runtime.safeCall(globalThis.process.env["PWD"].split("/")); //│ folderName1 = runtime.safeCall(tmp.pop()); //│ tmp1 = runtime.safeCall(globalThis.process.cwd()); //│ tmp2 = runtime.safeCall(tmp1.split("/")); diff --git a/hkmc2/shared/src/test/mlscript/codegen/RuntimeUsage.mls b/hkmc2/shared/src/test/mlscript/codegen/RuntimeUsage.mls index 54de37c33e..e2d02d1b69 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/RuntimeUsage.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/RuntimeUsage.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls index d2150de51e..6ca71298ed 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls @@ -1,6 +1,8 @@ +:. :js -// * In this test, we use `id` to wrap functions so that compile-time arity checks get bypassed. +// * Use dynamic values to exercise runtime checks even for statically invalid operations. +fun unchecked(x): dyn = x :ssjs @@ -12,7 +14,7 @@ f(2, 3) //│ runtime.checkArgs("f", 2, true, arguments.length); //│ return x + y //│ }; -//│ block$res1 = 5; +//│ block$res2 = 5; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 5 @@ -27,17 +29,17 @@ f(2, 3) //│ return x + y //│ }; //│ f1 = f2; -//│ block$res2 = 5; +//│ block$res3 = 5; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 5 //│ f = fun f :re -f(2) +unchecked(f)(2) //│ ═══[RUNTIME ERROR] Error: Function 'f' expected 2 arguments but got 1 :re -f(2, 3, 4) +unchecked(f)(2, 3, 4) //│ ═══[RUNTIME ERROR] Error: Function 'f' expected 2 arguments but got 3 @@ -45,18 +47,18 @@ f(2, 3, 4) :expect NaN :noSanityCheck fun f2(x, y) = x + y -id(f2)(2) +unchecked(f2)(2) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let f21; -//│ f21 = function f2(x, y) { return x + y }; block$res5 = runtime.safeCall(f21(2)); +//│ f21 = function f2(x, y) { return x + y }; block$res6 = runtime.safeCall(f21(2)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = NaN :ssjs :re -id(f)(2) +unchecked(f)(2) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res6 = runtime.safeCall(f1(2)); +//│ block$res7 = runtime.safeCall(f1(2)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] Error: Function 'f' expected 2 arguments but got 1 @@ -64,7 +66,7 @@ id(f)(2) :ssjs // :re // Argument numbers are no longer checked for lambdas, including extra parameter lists fun f(x)(y, z) = x + y + z -id(f)(3)(4) +unchecked(f)(3)(4) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let f3; //│ f3 = function f(x) { @@ -75,7 +77,7 @@ id(f)(3)(4) //│ return tmp + z //│ } //│ }; -//│ block$res7 = runtime.safeCall(f3(3)(4)); +//│ block$res8 = runtime.safeCall(f3(3)(4)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = NaN @@ -83,13 +85,13 @@ id(f)(3)(4) // :ssjs :expect NaN :noSanityCheck -let f = (x, y) => x + y in f(2) +let f = (x, y) => x + y in unchecked(f)(2) //│ = NaN // :ssjs :re let f = (x, y) => x + y -f(2) +unchecked(f)(2) //│ ═══[RUNTIME ERROR] Error: Function 'f' expected 2 arguments but got 1 //│ f = fun f @@ -99,14 +101,14 @@ f(2) :noSanityCheck data class Cls(x, y) with fun f(z, p) = x + y + z + p -id(Cls(1, 2)).f(3) +unchecked(Cls(1, 2)).f(3) //│ = NaN // :ssjs :re data class Cls(x, y) with fun f(z, p) = x + y + z + p -id(Cls(1, 2)).f(3) +unchecked(Cls(1, 2)).f(3) //│ ═══[RUNTIME ERROR] Error: Function 'f' expected 2 arguments but got 1 @@ -115,7 +117,7 @@ id(Cls(1, 2)).f(3) // :re // Argument numbers are no longer checked for lambdas, including extra parameter lists data class Cls(x, y) with fun f(z, p)(q, s) = x + y + z + p + q + s -id(Cls(1, 2)).f(3, 4)(5) +unchecked(Cls(1, 2)).f(3, 4)(5) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let Cls5, tmp2, callPrefix; //│ Cls5 = function Cls(x, y) { @@ -146,7 +148,7 @@ id(Cls(1, 2)).f(3, 4)(5) //│ }); //│ tmp2 = runtime.checkCall(Cls5(1, 2)); //│ callPrefix = runtime.safeCall(tmp2.f(3, 4)); -//│ block$res12 = runtime.safeCall(callPrefix(5)); +//│ block$res13 = runtime.safeCall(callPrefix(5)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = NaN @@ -176,7 +178,7 @@ module M with :ssjs :re -if M.A(1).y is +if unchecked(M.A(1)).y is x and x == 1 then x else 0 //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— @@ -184,7 +186,7 @@ if M.A(1).y is //│ tmp3 = runtime.checkCall(M1.A(1)); //│ scrut = runtime.checkSelect(tmp3.y, "y", tmp3); //│ scrut1 = runtime.checkCall(Predef.equals(scrut, 1)); -//│ if (scrut1 === true) { block$res17 = scrut; } else { block$res17 = 0; } +//│ if (scrut1 === true) { block$res18 = scrut; } else { block$res18 = 0; } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] Error: Access to required field 'y' yielded 'undefined' @@ -192,7 +194,7 @@ if M.A(1).y is // :ssjs :re -M.A(1).y +unchecked(M.A(1)).y console.log() //│ ═══[RUNTIME ERROR] Error: Access to required field 'y' yielded 'undefined' @@ -200,22 +202,22 @@ M.A(1).y // :ssjs :noSanityCheck -M.A(1).y +unchecked(M.A(1)).y console.log() //│ > undefined :noSanityCheck :expect false -M.A(2).y > 1 +unchecked(M.A(2)).y > 1 //│ = false :re -M.A(2).y > 1 +unchecked(M.A(2)).y > 1 //│ ═══[RUNTIME ERROR] Error: Access to required field 'y' yielded 'undefined' // :ssjs :re -M.A(1).g(0) +unchecked(M.A(1)).g(0) //│ ═══[RUNTIME ERROR] TypeError: tmp8.g is not a function @@ -229,7 +231,7 @@ M.A(1).f(0) :ssjs M.g(1, 2) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res24 = 3; +//│ block$res25 = 3; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index 5294b2c49f..20659f149a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -1,3 +1,4 @@ +:. :js @@ -16,7 +17,7 @@ while false then () () => while true then 0 //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.17: while true then 0 +//│ ║ l.18: while true then 0 //│ ╙── ^ //│ = fun @@ -48,10 +49,12 @@ fun f = while x else return 42 // TODO: support `break` here f //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f, inlinedVal; +//│ let f; //│ f = function f() { //│ lbl4: while (true) { -//│ if (x2 === true) { +//│ let scrut; +//│ scrut = x2; +//│ if (scrut === true) { //│ Predef.print("Hello World"); //│ x2 = false; //│ continue lbl4 @@ -59,26 +62,22 @@ f //│ return 42; //│ } //│ }; -//│ inlinedLbl: { -//│ lbl4: while (true) { -//│ if (x2 === true) { -//│ Predef.print("Hello World"); -//│ x2 = false; -//│ continue lbl4 -//│ } -//│ inlinedVal = 42; -//│ break inlinedLbl; -//│ } -//│ } -//│ return inlinedVal +//│ return f() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > Hello World //│ = 42 +:e fun f = while (print("Hello World"), false) then 0(0) else return 1 f +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.72: then 0(0) +//│ ║ ^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.72: then 0(0) +//│ ╙── ^ //│ > Hello World //│ = 1 @@ -164,8 +163,10 @@ if x else 42 //│ Normalized: //│ > ‹if|while› -//│ > x is true and -//│ > y is true then 0 +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is true and +//│ > let tmp:scrut = y⁰ +//│ > tmp:scrut is true then 0 //│ > else 1 //│ > else 42 //│ = 42 @@ -178,8 +179,10 @@ if x else 42 //│ Normalized: //│ > ‹if|while› -//│ > x is true and -//│ > y is true then 0 +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is true and +//│ > let tmp:scrut = y⁰ +//│ > tmp:scrut is true then 0 //│ > else 1 //│ > else 42 //│ = 42 @@ -201,14 +204,14 @@ fun f(ls) = //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f6; //│ f6 = function f(ls) { -//│ lbl9: while (true) { +//│ lbl8: while (true) { //│ let arg$Cons$0$, arg$Cons$1$; //│ if (ls instanceof Cons1.class) { //│ arg$Cons$0$ = ls.hd; //│ arg$Cons$1$ = ls.tl; //│ ls = arg$Cons$1$; //│ Predef.print(arg$Cons$0$); -//│ continue lbl9 +//│ continue lbl8 //│ } //│ break; //│ } @@ -251,8 +254,10 @@ let x = 1 :sjs while x is {} do() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ lbl11: while (true) { -//│ if (x3 instanceof globalThis.Object) { continue lbl11 } +//│ lbl10: while (true) { +//│ let scrut4; +//│ scrut4 = x3; +//│ if (scrut4 instanceof globalThis.Object) { continue lbl10 } //│ break; //│ } //│ return runtime.Unit @@ -274,7 +279,7 @@ fun f() = savedLambda = () => local set cnt -= 1 // Calls the saved lambda - savedLambda() + if savedLambda is ~null as run then run() f() //│ = 2 @@ -327,10 +332,10 @@ while print("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.327: then 0(0) +//│ ║ l.332: then 0(0) //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized term split (false literal) -//│ ║ l.326: while print("Hello World"); false +//│ ║ l.331: while print("Hello World"); false //│ ╙── ^^^^^ //│ > Hello World @@ -339,12 +344,12 @@ while { print("Hello World"), false } then 0(0) else 1 //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.338: while { print("Hello World"), false } +//│ ║ l.343: while { print("Hello World"), false } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.339: then 0(0) +//│ ║ l.344: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.340: else 1 +//│ ║ l.345: else 1 //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -355,14 +360,14 @@ while then 0(0) else 1 //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.353: print("Hello World") +//│ ║ l.358: print("Hello World") //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.354: false +//│ ║ l.359: false //│ ║ ^^^^^^^^^ -//│ ║ l.355: then 0(0) +//│ ║ l.360: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.356: else 1 +//│ ║ l.361: else 1 //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ctx/.mls b/hkmc2/shared/src/test/mlscript/ctx/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ctx/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions1.mls b/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions1.mls index 443f5c7d07..d33abab462 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions1.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions1.mls @@ -1,3 +1,4 @@ +:. fun f: Any -> Any @@ -5,7 +6,7 @@ fun f: Any -> Any :e g(5) //│ ╔══[COMPILATION ERROR] Name not found: g -//│ ║ l.6: g(5) +//│ ║ l.7: g(5) //│ ╙── ^ :js @@ -13,10 +14,10 @@ g(5) :re f(1) //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'f' -//│ ║ l.14: f(1) +//│ ║ l.15: f(1) //│ ║ ^ //│ ╟── which references the symbol introduced here -//│ ║ l.3: fun f: Any -> Any +//│ ║ l.4: fun f: Any -> Any //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] ReferenceError: f is not defined @@ -30,10 +31,10 @@ fun g(x) = f(x) :re g(0) //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'f' -//│ ║ l.26: fun g(x) = f(x) +//│ ║ l.27: fun g(x) = f(x) //│ ║ ^ //│ ╟── which references the symbol introduced here -//│ ║ l.3: fun f: Any -> Any +//│ ║ l.4: fun f: Any -> Any //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] ReferenceError: f is not defined @@ -41,10 +42,10 @@ g(0) :ge fun g(x) = f(x) //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'f' -//│ ║ l.42: fun g(x) = f(x) +//│ ║ l.43: fun g(x) = f(x) //│ ║ ^ //│ ╟── which references the symbol introduced here -//│ ║ l.3: fun f: Any -> Any +//│ ║ l.4: fun f: Any -> Any //│ ╙── ^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions2.mls b/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions2.mls index d53861141c..33a32f0970 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions2.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions2.mls @@ -1,3 +1,4 @@ +:. :js @@ -5,7 +6,7 @@ :fixme let x: Int //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.6: let x: Int +//│ ║ l.7: let x: Int //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls b/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls index 642b7d4441..65c95d56e8 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls @@ -1,3 +1,4 @@ +:. class Foo with val foo: Foo = new Foo @@ -6,10 +7,8 @@ class Foo with new Foo //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = New{typ=class:Foo⁰}: -//│ cls = Resolved{sym=class:Foo⁰}: -//│ t = Ref{sym=member:Foo¹} of member:Foo¹ -//│ sym = class:Foo⁰ +//│ res = New: +//│ cls = MemberRef of member:Foo⁰ // * TODO: dynamic new-expression should also be resolvable for its type :rt @@ -17,9 +16,7 @@ new! Foo //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: //│ res = DynNew: -//│ cls = Resolved{sym=class:Foo⁰}: -//│ t = Ref{sym=member:Foo¹} of member:Foo¹ -//│ sym = class:Foo⁰ +//│ cls = MemberRef of member:Foo⁰ val foo: Foo = new Foo @@ -27,21 +24,15 @@ val foo: Foo = new Foo foo //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=term:foo⁰,typ=class:Foo⁰}: -//│ t = Ref{sym=member:foo¹} of member:foo¹ -//│ sym = term:foo⁰ +//│ res = MemberRef of member:foo⁰ :rt foo.foo //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=term:Foo⁰/foo²,typ=class:Foo⁰}: -//│ t = Sel{sym=member:foo³}: -//│ prefix = Resolved{sym=term:foo⁰,typ=class:Foo⁰}: -//│ t = Ref{sym=member:foo¹} of member:foo¹ -//│ sym = term:foo⁰ -//│ nme = Ident of "foo" -//│ sym = term:Foo⁰/foo² +//│ res = NewSel: +//│ prefix = MemberRef of member:foo⁰ +//│ id = Ident of "foo" class Foo2[A, B] with val foo: Foo2[Int, Str] = new Foo2 @@ -50,10 +41,8 @@ class Foo2[A, B] with new Foo2 //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = New{typ=class:Foo2⁰}: -//│ cls = Resolved{sym=class:Foo2⁰}: -//│ t = Ref{sym=member:Foo2¹} of member:Foo2¹ -//│ sym = class:Foo2⁰ +//│ res = New: +//│ cls = MemberRef of member:Foo2⁰ // * TODO: dynamic new-expression should also be resolvable for its type :rt @@ -61,9 +50,7 @@ new! Foo2 //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: //│ res = DynNew: -//│ cls = Resolved{sym=class:Foo2⁰}: -//│ t = Ref{sym=member:Foo2¹} of member:Foo2¹ -//│ sym = class:Foo2⁰ +//│ cls = MemberRef of member:Foo2⁰ val foo: Foo2[Str, Int] = new Foo2 @@ -71,21 +58,15 @@ val foo: Foo2[Str, Int] = new Foo2 foo //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=term:foo⁴,typ=class:Foo2⁰[class:Str⁰, class:Int⁰]}: -//│ t = Ref{sym=member:foo⁵} of member:foo⁵ -//│ sym = term:foo⁴ +//│ res = MemberRef of member:foo¹ :rt foo.foo //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=term:Foo2⁰/foo⁶,typ=class:Foo2⁰[class:Int⁰, class:Str⁰]}: -//│ t = Sel{sym=member:foo⁷}: -//│ prefix = Resolved{sym=term:foo⁴,typ=class:Foo2⁰[class:Str⁰, class:Int⁰]}: -//│ t = Ref{sym=member:foo⁵} of member:foo⁵ -//│ sym = term:foo⁴ -//│ nme = Ident of "foo" -//│ sym = term:Foo2⁰/foo⁶ +//│ res = NewSel: +//│ prefix = MemberRef of member:foo¹ +//│ id = Ident of "foo" :rt fun f(x: Foo) = x @@ -102,12 +83,10 @@ fun f(x: Foo) = x //│ Param: //│ sym = x⁰ //│ sign = S of Capture: -//│ base = Resolved{sym=class:Foo⁰}: -//│ t = Ref{sym=member:Foo¹} of member:Foo¹ -//│ sym = class:Foo⁰ +//│ base = MemberRef of member:Foo⁰ //│ thru = term:f¹ //│ modulefulness = Modulefulness of N -//│ body = S of Ref{sym=x⁰,typ=class:Foo⁰} of x⁰ +//│ body = S of SimpleRef of x⁰ //│ modulefulness = Modulefulness of N //│ res = Lit of UnitLit of false @@ -118,17 +97,13 @@ object O M //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=module:M⁰,typ=module:M⁰}: -//│ t = Ref{sym=member:M¹} of member:M¹ -//│ sym = module:M⁰ +//│ res = MemberRef of member:M⁰ :rt O //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=object:O⁰,typ=object:O⁰}: -//│ t = Ref{sym=member:O¹} of member:O¹ -//│ sym = object:O⁰ +//│ res = MemberRef of member:O⁰ val m: module M = M val o: O = O @@ -137,17 +112,13 @@ val o: O = O m //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=term:m⁰,typ=module:M⁰}: -//│ t = Ref{sym=member:m¹} of member:m¹ -//│ sym = term:m⁰ +//│ res = MemberRef of member:m⁰ :rt o //│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— //│ Blk: -//│ res = Resolved{sym=term:o⁰,typ=object:O⁰}: -//│ t = Ref{sym=member:o¹} of member:o¹ -//│ sym = term:o⁰ +//│ res = MemberRef of member:o⁰ :rt fun f(module m: M): module M = m @@ -162,21 +133,17 @@ fun f(module m: M): module M = m //│ ParamList: //│ params = Ls of //│ Param: -//│ sym = m² +//│ sym = m¹ //│ sign = S of Capture: -//│ base = Resolved{sym=module:M⁰,typ=module:M⁰}: -//│ t = Ref{sym=member:M¹} of member:M¹ -//│ sym = module:M⁰ +//│ base = MemberRef of member:M⁰ //│ thru = term:f³ -//│ modulefulness = Modulefulness of S of module:M⁰ +//│ modulefulness = Modulefulness of N //│ sign = S of Capture: -//│ base = Resolved{sym=module:M⁰,typ=module:M⁰}: -//│ t = Ref{sym=member:M¹} of member:M¹ -//│ sym = module:M⁰ +//│ base = MemberRef of member:M⁰ //│ thru = term:f³ -//│ body = S of Ref{sym=m²,typ=module:M⁰} of m² +//│ body = S of SimpleRef of m¹ //│ flags = (result annotation) -//│ modulefulness = Modulefulness of S of module:M⁰ +//│ modulefulness = Modulefulness of N //│ res = Lit of UnitLit of false :rt @@ -192,14 +159,12 @@ fun f(o: O) = o //│ ParamList: //│ params = Ls of //│ Param: -//│ sym = o² +//│ sym = o¹ //│ sign = S of Capture: -//│ base = Resolved{sym=object:O⁰,typ=object:O⁰}: -//│ t = Ref{sym=member:O¹} of member:O¹ -//│ sym = object:O⁰ +//│ base = MemberRef of member:O⁰ //│ thru = term:f⁵ //│ modulefulness = Modulefulness of N -//│ body = S of Ref{sym=o²,typ=object:O⁰} of o² +//│ body = S of SimpleRef of o¹ //│ modulefulness = Modulefulness of N //│ res = Lit of UnitLit of false @@ -213,22 +178,20 @@ class Foo with //│ stats = Ls of //│ Plain: //│ kind = Cls -//│ sym = class:Foo² -//│ bsym = member:Foo³ +//│ sym = class:Foo¹ +//│ bsym = member:Foo² //│ body = ObjBody of Blk: //│ stats = Ls of //│ TermDefinition: //│ k = ImmutVal -//│ sym = member:foo⁸ -//│ tsym = term:Foo²/foo⁹ +//│ sym = member:foo² +//│ tsym = term:Foo¹/foo³ //│ body = S of Lit of IntLit of 42 //│ flags = (method) //│ modulefulness = Modulefulness of N -//│ res = Resolved{sym=term:Foo²/foo⁹}: -//│ t = SynthSel{sym=member:foo⁸}: -//│ prefix = Ref{sym=class:Foo²,typ=class:Foo²} of class:Foo² -//│ nme = Ident of "foo" -//│ sym = term:Foo²/foo⁹ +//│ res = NewSel: +//│ prefix = SelfRef of class:Foo¹ +//│ id = Ident of "foo" //│ res = Lit of UnitLit of false // The explicit `this` (class:Foo) in the Sel(class:Foo, "foo") should be resolved correctly. @@ -241,20 +204,18 @@ class Foo with //│ stats = Ls of //│ Plain: //│ kind = Cls -//│ sym = class:Foo⁴ -//│ bsym = member:Foo⁵ +//│ sym = class:Foo³ +//│ bsym = member:Foo⁴ //│ body = ObjBody of Blk: //│ stats = Ls of //│ TermDefinition: //│ k = ImmutVal -//│ sym = member:foo¹⁰ -//│ tsym = term:Foo⁴/foo¹¹ +//│ sym = member:foo⁴ +//│ tsym = term:Foo³/foo⁵ //│ body = S of Lit of IntLit of 42 //│ flags = (method) //│ modulefulness = Modulefulness of N -//│ res = Resolved{sym=term:Foo⁴/foo¹¹}: -//│ t = Sel{sym=member:foo¹⁰}: -//│ prefix = Ref{sym=class:Foo⁴,typ=class:Foo⁴} of class:Foo⁴ -//│ nme = Ident of "foo" -//│ sym = term:Foo⁴/foo¹¹ +//│ res = NewSel: +//│ prefix = SelfRef of class:Foo³ +//│ id = Ident of "foo" //│ res = Lit of UnitLit of false diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/basic.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/basic.mls index e844c803e4..5d6a98b552 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/basic.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/basic.mls @@ -1,3 +1,4 @@ +:. :js :noInline :flowBasedOpt debugDpe @@ -145,13 +146,14 @@ foo.bar(99) //│ define Foo⁰ as class Foo² { //│ private val x⁰; //│ constructor Foo¹(x) { -//│ set Foo².this.x⁰ = x; +//│ @private +//│ define x¹ as val x⁰ = x; //│ end //│ } //│ method bar⁰ = fun bar¹(a) { return Foo².this.x⁰ } //│ }; //│ set foo⁰ = Foo¹(1); -//│ return foo⁰.bar﹖(99) +//│ return foo⁰.bar¹(99) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 //│ foo = Foo(_) @@ -298,7 +300,7 @@ fun f(c) = Runtime.Tuple.get(c, 1) + 1 //│ set tmp = Runtime⁰.Tuple⁰.get⁰(c, 1); //│ return +⁰(tmp, 1) //│ else -//│ return runtime⁰.unwind﹖(f²⁴, -1, "basic.mls:288:1", null, null, 1, 1, c, 0) +//│ return runtime⁰.unwind﹖(f²⁴, -1, "basic.mls:290:1", null, null, 1, 1, c, 0) //│ end //│ }; //│ end diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/clash.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/clash.mls index 34a35fd8ba..8783864528 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/clash.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/clash.mls @@ -1,3 +1,4 @@ +:. :js :noInline :flowBasedOpt debugDpe @@ -92,7 +93,7 @@ m() //│ let tmp, lambda; //│ set tmp = [1, 2, 3]; //│ define lambda as fun lambda⁰(x, i, a) { return +⁰(x, 1) }; -//│ return tmp.map﹖(lambda⁰) +//│ return tmp.map⁰((lambda⁰ as! Function⁰)) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = [2, 3, 4] diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/class-in-fun.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/class-in-fun.mls index 7f4fc3e29d..e6d8fdd2f8 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/class-in-fun.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/class-in-fun.mls @@ -1,3 +1,4 @@ +:. :js :flowBasedOpt debugDpe @@ -31,7 +32,7 @@ f(1, 2).get() //│ @private //│ define f⁰ as fun f¹(used) { return new C⁰(used) }; //│ set tmp = f¹(1); -//│ return tmp.get﹖() +//│ return tmp.get¹() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/config-flags.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/config-flags.mls index 39402d6648..c2fff6d502 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/config-flags.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/config-flags.mls @@ -1,3 +1,4 @@ +:. :js :noInline diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/dead-ref.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/dead-ref.mls index d6438e77c9..08c15c9a88 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/dead-ref.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/dead-ref.mls @@ -1,3 +1,4 @@ +:. :js :noInline :flowBasedOpt debugDpe diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/inherited-field.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/inherited-field.mls index 67fed4776b..c1f322ed27 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/inherited-field.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/inherited-field.mls @@ -1,3 +1,4 @@ +:. :.. :js diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls index 26067d7480..7327256321 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls @@ -1,3 +1,4 @@ +:. :js :flowBasedOpt debugDpe diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/module.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/module.mls index 42af9db3bc..0ee4a103d4 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/module.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/module.mls @@ -1,3 +1,4 @@ +:. :js :flowBasedOpt debugDpe :noInline diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/multiArgLists.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/multiArgLists.mls index 55cf60139d..504a6a0eb3 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/multiArgLists.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/multiArgLists.mls @@ -1,3 +1,4 @@ +:. :js :noInline diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/purity.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/purity.mls index 59d82a33d6..2e7811896c 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/purity.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/purity.mls @@ -1,13 +1,15 @@ +:. :js :noInline :flowBasedOpt debugDpe +// Dynamic receivers keep the invalid null accesses in the generated code. // an eliminated arg with effects is still evaluated :re :soir private fun f(a) = 0 -fun g(o) = f(o.x) +fun g(o: dyn) = f(o.x) g(null) //│ dead-param-elim > >>> dead-param-elim results >>> //│ dead-param-elim > prodfun f#0 @ -> eliminable: {0} @@ -24,7 +26,7 @@ g(null) :re :soir private fun f(a, b) = a -fun g(o, p) = f(o.x, p.y) +fun g(o: dyn, p: dyn) = f(o.x, p.y) g(null, null) //│ dead-param-elim > >>> dead-param-elim results >>> //│ dead-param-elim > prodfun f#0 @ -> eliminable: {1} @@ -64,12 +66,12 @@ g(new A) //│ define g⁴ as fun g⁵(o) { //│ let arg; //│ set arg = n⁰; -//│ do o.bump﹖; +//│ do o.bump⁰; //│ return f⁵(arg) //│ }; //│ set n⁰ = 0; //│ define A⁰ as class A¹ { -//│ method bump⁰ = fun bump¹ { +//│ method bump¹ = fun bump⁰ { //│ set n⁰ = 1; //│ return runtime⁰.Unit⁰ //│ } diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls index 1addda984e..3f49a129df 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls @@ -1,3 +1,4 @@ +:. :js :flowBasedOpt debugDpe :noInline diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls index 3eab808fd3..d9c59e5fd5 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls @@ -1,3 +1,4 @@ +:. :js :flowBasedOpt debugDpe poly :noInline @@ -90,7 +91,7 @@ wrapper(42, 0) //│ }; //│ set tmp = new A⁰(); //│ set tmp1 = tmp.makeB¹(); -//│ return tmp1.getVal﹖() +//│ return tmp1.getVal¹() //│ }; //│ define wrapper⁰ as fun wrapper¹(val1, dead) { //│ let A, tmp, tmp1; @@ -107,7 +108,7 @@ wrapper(42, 0) //│ }; //│ set tmp = new A¹(); //│ set tmp1 = tmp.makeB³(); -//│ return tmp1.getVal﹖() +//│ return tmp1.getVal³() //│ }; //│ return wrapper_3$wrapper⁰(42) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/todos.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/todos.mls index 099abf5c9c..cfebb10b79 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/todos.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/todos.mls @@ -1,3 +1,4 @@ +:. :js :deforest :flowBasedOpt debugEta debugDpe @@ -32,9 +33,9 @@ fun c(x) = c(p(true)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: x⁰ +//│ deforest > match: scrut⁰ //│ deforest > B⁰ -> -//│ deforest > match: x⁰ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > p_7$p: [1, 1] @@ -90,18 +91,18 @@ fun sumPlus(ls) = sumPlus(enum(3)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁰ -//│ deforest > Cons⁰(x¹, tmp⁰) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > Cons⁰(x⁰, tmp⁰) -> +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > enum_14$enum: [1, 9] //│ eta-expansion > <<< eta-expansion targets shapes <<< //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun sumPlus_16$ls_Cons#1 @ -> eliminable: {0, 5} -//│ dead-param-elim > prodfun sumPlus_16$ls_Nil#1 @ -> eliminable: {0, 5} +//│ dead-param-elim > prodfun sumPlus_16$scrut_Cons#1 @ -> eliminable: {0, 5} +//│ dead-param-elim > prodfun sumPlus_16$scrut_Nil#1 @ -> eliminable: {0, 5} //│ dead-param-elim > <<< dead-param-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ let enum_14$enum$worker; @@ -172,9 +173,9 @@ fun c(x) = if x is c(p(true)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: x² +//│ deforest > match: scrut² //│ deforest > B⁰ -> -//│ deforest > match: x² +//│ deforest > match: scrut² //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > p_7$p: [1, 0] diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/ups.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/ups.mls index b5f4a948d3..e1ab796efa 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/ups.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/ups.mls @@ -1,10 +1,11 @@ +:. :js :flowBasedOpt debugDpe :ucs ups pattern Middle = ([x] => x) | ([x, ...(Middle as m), y] => m) -//│ elaborated pattern body: [x‹x⁰›] => x¹ ∨ [x‹x²›, ...Middle as m‹m⁰›, y‹y⁰›] => m¹ +//│ elaborated pattern body: [x‹x⁰›] => x¹ ∨ [x‹x²›, ...member:Middle⁰ as m‹m⁰›, y‹y⁰›] => m¹ //│ dead-param-elim > >>> dead-param-elim results >>> //│ dead-param-elim > prodfun lambda#0 @ -> eliminable: {0, 1} //│ dead-param-elim > <<< dead-param-elim results <<< diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index e35c8747bc..7fc3765116 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -6,6 +6,7 @@ declare type Nothing declare class Object with fun toString(): Str +declare fun Object(...args): Object declare module Object with fun entries(target): Array[[Str, Any]] fun getOwnPropertyDescriptor(target, key): Object | undefined @@ -23,6 +24,7 @@ declare module JSON with fun stringify declare class Number +declare fun Number(...args): Num declare module Number with val MIN_VALUE @@ -33,6 +35,7 @@ declare module Number with POSITIVE_INFINITY declare class BigInt with fun toString(...radix): Str +declare fun BigInt(value): BigInt declare module BigInt declare class Function with val name: Str @@ -157,6 +160,7 @@ declare class Str with fun repeat(count: Int): Str fun toUpperCase(): Str fun toLowerCase(): Str + fun localeCompare(other: Str, ...localesAndOptions): Int fun trim(): Str declare class Class @@ -221,6 +225,7 @@ declare module Array with declare class Symbol with val description: Str | undefined +declare fun Symbol(...description): Symbol declare module Symbol with // The `TermDef` needs `rhs` to be defined to be recognized as `isMLsFun`. // Otherwise, it would be wrapped in `runtime.safeCall`, which accesses the @@ -327,7 +332,8 @@ declare val Infinity // Wasm support declare object WebAssembly with - declare class Instance + declare class Instance with + val exports: dyn declare class Memory declare class Module declare module Module with diff --git a/hkmc2/shared/src/test/mlscript/deforest/.mls b/hkmc2/shared/src/test/mlscript/deforest/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/deforest/append.mls b/hkmc2/shared/src/test/mlscript/deforest/append.mls index 207c27b4c0..d177b62c52 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/append.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/append.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -63,25 +64,25 @@ appendThree of id(5 :: 6 :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(h⁰, tmp⁰) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h¹ -//│ deforest > fields: xs⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h¹ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ +//│ deforest > match: scrut⁰ //│ deforest > Cons⁰(h², tmp¹) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h¹ -//│ deforest > fields: xs⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h¹ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > Cons⁰(h⁰, tmp⁰) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h¹ -//│ deforest > fields: xs⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h¹ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ +//│ deforest > match: scrut⁰ //│ deforest > Cons⁰(h², tmp¹) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h¹ -//│ deforest > fields: xs⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h¹ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) diff --git a/hkmc2/shared/src/test/mlscript/deforest/basic.mls b/hkmc2/shared/src/test/mlscript/deforest/basic.mls index a5e28468c6..a9345d749e 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/basic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/basic.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -19,14 +20,14 @@ let lsit = [2, 1] in let useless = if lsit is [a, b] then 0 //│ deforest > >>> fusing >>> //│ deforest > [2, 1] -> -//│ deforest > match: lsit⁰ -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(lsit⁰, 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(lsit⁰, 1) +//│ deforest > match: scrut⁰ +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁰, 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁰, 1) //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let lsit, useless, a, b, element1$, element0$, tmp, $lsit_tup2, $lsit_rest, tup2_0, tup2_1; +//│ let lsit, useless, scrut, a, b, element1$, element0$, tmp, $scrut_tup2, $scrut_rest, tup2_0, tup2_1; //│ @affine(1) @private -//│ define $lsit_tup2 as fun $lsit_tup2⁰(tup2_01, tup2_11)() { +//│ define $scrut_tup2 as fun $scrut_tup2⁰(tup2_01, tup2_11)() { //│ set element0$ = tup2_01; //│ set element1$ = tup2_11; //│ set b = element1$; @@ -36,13 +37,14 @@ let lsit = [2, 1] in //│ return runtime⁰.Unit⁰ //│ }; //│ @private -//│ define $lsit_rest as fun $lsit_rest⁰() { +//│ define $scrut_rest as fun $scrut_rest⁰() { //│ return null //│ }; //│ set tup2_0 = 2; //│ set tup2_1 = 1; -//│ set lsit = $lsit_tup2⁰(tup2_0, tup2_1); -//│ return lsit() +//│ set lsit = $scrut_tup2⁰(tup2_0, tup2_1); +//│ set scrut = lsit; +//│ return scrut() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :expect O @@ -52,8 +54,8 @@ fun c(x) = if x is c(p()) //│ deforest > >>> fusing >>> //│ deforest > X⁰(O⁰) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.a⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.a⁰ //│ deforest > <<< fusing <<< //│ = O @@ -66,29 +68,30 @@ fun f(x) = if x is f(p()) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp⁰) -> -//│ deforest > match: x¹ -//│ deforest > fields: x¹.a⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².a⁰ //│ deforest > Y⁰(O⁰) -> //│ deforest > match: arg$X$0$⁰ //│ deforest > fields: arg$X$0$⁰.b⁰ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp, p_8$p, f_10$f, f_10$x_X, f_10$x_rest, f_10$arg$X$0$_Y, f_10$arg$X$0$_rest; +//│ let tmp, p_8$p, f_10$f, f_10$scrut_X, f_10$scrut_rest, f_10$arg$X$0$_Y, f_10$arg$X$0$_rest; //│ @private //│ define p_8$p as fun p_8$p⁰() { //│ let tmp1, Y_b, X_a; //│ set Y_b = O⁰; //│ set tmp1 = f_10$arg$X$0$_Y⁰(Y_b); //│ set X_a = tmp1; -//│ return f_10$x_X⁰(X_a) +//│ return f_10$scrut_X⁰(X_a) //│ }; //│ @private //│ define f_10$f as fun f_10$f⁰(x) { -//│ let y, arg$X$0$, arg$Y$0$; -//│ return x(y, arg$X$0$, arg$Y$0$) +//│ let scrut, y, arg$X$0$, arg$Y$0$; +//│ set scrut = x; +//│ return scrut(y, arg$X$0$, arg$Y$0$) //│ }; //│ @affine(1) @private -//│ define f_10$x_X as fun f_10$x_X⁰(X_a)(fv_y, fv_arg$X$0$, fv_arg$Y$0$) { +//│ define f_10$scrut_X as fun f_10$scrut_X⁰(X_a)(fv_y, fv_arg$X$0$, fv_arg$Y$0$) { //│ set fv_arg$X$0$ = X_a; //│ return fv_arg$X$0$(fv_y, fv_arg$Y$0$) //│ }; @@ -99,12 +102,12 @@ f(p()) //│ return fv_y //│ }; //│ @private -//│ define f_10$x_rest as fun f_10$x_rest⁰() { +//│ define f_10$scrut_rest as fun f_10$scrut_rest⁰() { //│ return null //│ }; //│ @private //│ define f_10$arg$X$0$_rest as fun f_10$arg$X$0$_rest⁰() { -//│ return f_10$x_rest⁰() +//│ return f_10$scrut_rest⁰() //│ }; //│ define p⁰ as fun p¹() { //│ let tmp1; @@ -112,10 +115,11 @@ f(p()) //│ return X⁰(tmp1) //│ }; //│ define f⁰ as fun f¹(x) { -//│ let y, arg$X$0$, arg$Y$0$; -//│ match x +//│ let scrut, y, arg$X$0$, arg$Y$0$; +//│ set scrut = x; +//│ match scrut //│ X¹ => -//│ set arg$X$0$ = x.a⁰; +//│ set arg$X$0$ = scrut.a⁰; //│ match arg$X$0$ //│ Y¹ => //│ set arg$Y$0$ = arg$X$0$.b⁰; @@ -144,11 +148,11 @@ fun f(x, res) = if x is f(p(), 2) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp¹) -> -//│ deforest > match: x² -//│ deforest > fields: x².a⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.a⁰ //│ deforest > Y⁰(O⁰) -> -//│ deforest > match: a¹ -//│ deforest > fields: a¹.b⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.b⁰ //│ deforest > <<< fusing <<< //│ = 14 @@ -165,8 +169,8 @@ fun f(x, y, res) = if x is f(X(1), Y(2), 3) //│ deforest > >>> fusing >>> //│ deforest > X⁰(1) -> -//│ deforest > match: x³ -//│ deforest > fields: x³.a⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.a⁰ //│ deforest > <<< fusing <<< //│ = 16 @@ -176,18 +180,18 @@ fun c(x, d) = if x is X(a) then c(X(a), d) () => c(X(3), 2) //│ deforest > >>> fusing >>> -//│ deforest > X⁰(a²) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.a⁰ +//│ deforest > X⁰(a¹) -> +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.a⁰ //│ deforest > X⁰(3) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.a⁰ -//│ deforest > X⁰(a²) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.a⁰ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.a⁰ +//│ deforest > X⁰(a¹) -> +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.a⁰ //│ deforest > X⁰(3) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.a⁰ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.a⁰ //│ deforest > <<< fusing <<< //│ = fun lambda_7$lambda @@ -201,7 +205,7 @@ fun p(x) = if x is p(c()) //│ deforest > >>> fusing >>> //│ deforest > O⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut⁷ //│ deforest > <<< fusing <<< //│ = 2 @@ -212,14 +216,14 @@ if [1, 2] is [a, b] then a + b //│ deforest > >>> fusing >>> //│ deforest > [1, 2] -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁰, 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁰, 1) +//│ deforest > match: scrut⁸ +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁸, 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁸, 1) //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let scrut, a, b, element1$, element0$, $scrut_tup2, $scrut_rest, tup2_0, tup2_1; //│ @affine(1) @private -//│ define $scrut_tup2 as fun $scrut_tup2⁰(tup2_01, tup2_11)() { +//│ define $scrut_tup2 as fun $scrut_tup2¹(tup2_01, tup2_11)() { //│ set element0$ = tup2_01; //│ set element1$ = tup2_11; //│ set b = element1$; @@ -227,12 +231,12 @@ if [1, 2] is //│ return +⁰(a, b) //│ }; //│ @private -//│ define $scrut_rest as fun $scrut_rest⁰() { +//│ define $scrut_rest as fun $scrut_rest¹() { //│ return null //│ }; //│ set tup2_0 = 1; //│ set tup2_1 = 2; -//│ set scrut = $scrut_tup2⁰(tup2_0, tup2_1); +//│ set scrut = $scrut_tup2¹(tup2_0, tup2_1); //│ return scrut() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 3 @@ -244,9 +248,9 @@ fun f(x) = if x is f([1,2]) //│ deforest > >>> fusing >>> //│ deforest > [1, 2] -> -//│ deforest > match: x⁶ -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(x⁶, 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(x⁶, 1) +//│ deforest > match: scrut⁹ +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁹, 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁹, 1) //│ deforest > <<< fusing <<< //│ = 3 @@ -278,8 +282,8 @@ fun f(x) = if x is f(A(1)) //│ deforest > >>> fusing >>> //│ deforest > A⁰(1) -> -//│ deforest > match: x⁷ -//│ deforest > fields: x⁷.x⁸ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.x⁰ //│ deforest > <<< fusing <<< //│ = 1 @@ -289,10 +293,6 @@ fun f(x) = if x is A(a) then x.A#x + a f(A(1)) //│ deforest > >>> fusing >>> -//│ deforest > A⁰(1) -> -//│ deforest > match: x⁹ -//│ deforest > fields: x⁹.x⁸ -//│ deforest > fields: x⁹.x⁸ //│ deforest > <<< fusing <<< //│ = 2 @@ -303,11 +303,11 @@ fun f(x) = if x is f(A(2)) + f(A(3)) //│ deforest > >>> fusing >>> //│ deforest > A⁰(3) -> -//│ deforest > match: x¹⁰ -//│ deforest > fields: x¹⁰.x⁸ +//│ deforest > match: scrut¹¹ +//│ deforest > fields: scrut¹¹.x⁰ //│ deforest > A⁰(2) -> -//│ deforest > match: x¹⁰ -//│ deforest > fields: x¹⁰.x⁸ +//│ deforest > match: scrut¹¹ +//│ deforest > fields: scrut¹¹.x⁰ //│ deforest > <<< fusing <<< //│ = 4 @@ -319,18 +319,18 @@ if localFuse(3) is B(a) then a localFuse(4) //│ deforest > >>> fusing >>> -//│ deforest > A⁰(x¹¹) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.x⁸ -//│ deforest > A⁰(x¹¹) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.x⁸ -//│ deforest > A⁰(x¹¹) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.x⁸ +//│ deforest > A⁰(x¹) -> +//│ deforest > match: scrut¹² +//│ deforest > fields: scrut¹².x⁰ +//│ deforest > A⁰(x¹) -> +//│ deforest > match: scrut¹² +//│ deforest > fields: scrut¹².x⁰ +//│ deforest > A⁰(x¹) -> +//│ deforest > match: scrut¹² +//│ deforest > fields: scrut¹².x⁰ //│ deforest > B⁰(y⁰) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².x¹² +//│ deforest > match: scrut¹³ +//│ deforest > fields: scrut¹³.x² //│ deforest > <<< fusing <<< //│ = B(4) @@ -356,7 +356,7 @@ let f() = x f() //│ deforest > >>> fusing >>> //│ deforest > A⁰(0) -> -//│ deforest > match: scrut³ +//│ deforest > match: scrut¹⁴ //│ deforest > <<< fusing <<< //│ = 1 //│ f = undefined @@ -377,12 +377,9 @@ fun dtor(x) = if x is dtor(A(B(0))) //│ deforest > >>> fusing >>> //│ deforest > B⁰(4) -> -//│ deforest > match: y¹ +//│ deforest > match: scrut¹⁵ //│ deforest > B⁰(4) -> -//│ deforest > match: y¹ -//│ deforest > A⁰(tmp²) -> -//│ deforest > match: x¹³ -//│ deforest > fields: x¹³.x⁸ +//│ deforest > match: scrut¹⁵ //│ deforest > <<< fusing <<< //│ = B(0) diff --git a/hkmc2/shared/src/test/mlscript/deforest/clashes.mls b/hkmc2/shared/src/test/mlscript/deforest/clashes.mls index cee48bd145..bf90d3a3e3 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/clashes.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/clashes.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -42,8 +43,8 @@ fun f2(p) = if p is A(b) then b + 2 f1(one) + f2(one) + f2(two) //│ deforest > >>> fusing >>> //│ deforest > A⁰(x⁰) -> -//│ deforest > match: p⁰ -//│ deforest > fields: p⁰.x¹ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x¹ //│ deforest > <<< fusing <<< //│ = 9 diff --git a/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls b/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls index 70d783d6c9..ef3f4da831 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls @@ -1,3 +1,4 @@ +:. :js data class A(x) @@ -43,11 +44,11 @@ fun f(a) = if a is (x => 0)(f(f(C))) //│ deforest > >>> fusing >>> //│ deforest > C⁰ -> -//│ deforest > match: a⁰ +//│ deforest > match: scrut⁰ //│ deforest > C⁰ -> -//│ deforest > match: a⁰ +//│ deforest > match: scrut⁰ //│ deforest > D⁰ -> -//│ deforest > match: a⁰ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = 0 @@ -77,11 +78,11 @@ fun modSome(x, m) = if x is modSome(Some(17), 3) //│ deforest > >>> fusing >>> //│ deforest > Some⁰(tmp⁰) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.x¹ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.x⁰ //│ deforest > Some⁰(17) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.x¹ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.x⁰ //│ deforest > <<< fusing <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ let Some_x, fv_m, fv_scrut, fv_tmp, inlinedVal; @@ -90,7 +91,7 @@ modSome(Some(17), 3) //│ let scrut, arg$Some$0$, tmp, tmp1; //│ match x //│ Some¹ => -//│ set arg$Some$0$ = x.x¹; +//│ set arg$Some$0$ = x.x⁰; //│ set scrut = >=⁰(arg$Some$0$, m); //│ match scrut //│ true => @@ -150,12 +151,12 @@ fun zipWith(xsys, f) = if xsys is zipWith([id(Nil), id(Nil)], (x, y) => x) //│ deforest > >>> fusing >>> //│ deforest > [tx⁰, ty⁰] -> -//│ deforest > match: xsys⁰ -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(xsys⁰, 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(xsys⁰, 1) +//│ deforest > match: scrut² +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut², 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut², 1) //│ deforest > [tmp¹, tmp²] -> -//│ deforest > match: xsys⁰ -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(xsys⁰, 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(xsys⁰, 1) +//│ deforest > match: scrut² +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut², 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut², 1) //│ deforest > <<< fusing <<< //│ = Nil diff --git a/hkmc2/shared/src/test/mlscript/deforest/determinism.mls b/hkmc2/shared/src/test/mlscript/deforest/determinism.mls index f4f7c96cec..74ef2dbed0 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/determinism.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/determinism.mls @@ -1,3 +1,4 @@ +:. :js data class A(a) @@ -56,8 +57,8 @@ fun c2(x) = if x is c2 of A(A(A(A(A(A(1)))))) //│ deforest > >>> fusing >>> //│ deforest > A⁰(tmp⁰) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.a¹ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.a¹ //│ deforest > A⁰(tmp¹) -> //│ deforest > match: arg$A$0$⁰ //│ deforest > fields: arg$A$0$⁰.a¹ @@ -75,14 +76,15 @@ c2 of A(A(A(A(A(A(1)))))) //│ deforest > fields: arg$A$0$⁴.a¹ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, c2_19$c2, c2_19$x_A, c2_19$x_rest, c2_19$arg$A$0$_A, c2_19$arg$A$0$_rest, c2_19$arg$A$0$_A1, c2_19$arg$A$0$_rest1, c2_19$arg$A$0$_A2, c2_19$arg$A$0$_rest2, c2_19$arg$A$0$_A3, c2_19$arg$A$0$_rest3, c2_19$arg$A$0$_A4, c2_19$arg$A$0$_rest4, A_a, A_a1, A_a2, A_a3, A_a4, A_a5; +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, c2_19$c2, c2_19$scrut_A, c2_19$scrut_rest, c2_19$arg$A$0$_A, c2_19$arg$A$0$_rest, c2_19$arg$A$0$_A1, c2_19$arg$A$0$_rest1, c2_19$arg$A$0$_A2, c2_19$arg$A$0$_rest2, c2_19$arg$A$0$_A3, c2_19$arg$A$0$_rest3, c2_19$arg$A$0$_A4, c2_19$arg$A$0$_rest4, A_a, A_a1, A_a2, A_a3, A_a4, A_a5; //│ @private //│ define c2_19$c2 as fun c2_19$c2⁰(x) { -//│ let a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5; -//│ return x(a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5) +//│ let scrut, a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5; +//│ set scrut = x; +//│ return scrut(a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5) //│ }; //│ @affine(1) @private -//│ define c2_19$x_A as fun c2_19$x_A⁰(A_a6)(fv_a, fv_arg$A$0$, fv_arg$A$0$1, fv_arg$A$0$2, fv_arg$A$0$3, fv_arg$A$0$4, fv_arg$A$0$5) { +//│ define c2_19$scrut_A as fun c2_19$scrut_A⁰(A_a6)(fv_a, fv_arg$A$0$, fv_arg$A$0$1, fv_arg$A$0$2, fv_arg$A$0$3, fv_arg$A$0$4, fv_arg$A$0$5) { //│ set fv_arg$A$0$ = A_a6; //│ return fv_arg$A$0$(fv_a, fv_arg$A$0$1, fv_arg$A$0$2, fv_arg$A$0$3, fv_arg$A$0$4, fv_arg$A$0$5) //│ }; @@ -113,12 +115,12 @@ c2 of A(A(A(A(A(A(1)))))) //│ return fv_a //│ }; //│ @private -//│ define c2_19$x_rest as fun c2_19$x_rest⁰() { +//│ define c2_19$scrut_rest as fun c2_19$scrut_rest⁰() { //│ return null //│ }; //│ @private //│ define c2_19$arg$A$0$_rest as fun c2_19$arg$A$0$_rest⁰() { -//│ return c2_19$x_rest⁰() +//│ return c2_19$scrut_rest⁰() //│ }; //│ @private //│ define c2_19$arg$A$0$_rest1 as fun c2_19$arg$A$0$_rest¹() { @@ -137,10 +139,11 @@ c2 of A(A(A(A(A(A(1)))))) //│ return c2_19$arg$A$0$_rest³() //│ }; //│ define c2⁰ as fun c2¹(x) { -//│ let a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5; -//│ match x +//│ let scrut, a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5; +//│ set scrut = x; +//│ match scrut //│ A¹ => -//│ set arg$A$0$ = x.a¹; +//│ set arg$A$0$ = scrut.a¹; //│ match arg$A$0$ //│ A¹ => //│ set arg$A$0$1 = arg$A$0$.a¹; @@ -188,7 +191,7 @@ c2 of A(A(A(A(A(A(1)))))) //│ set A_a4 = tmp3; //│ set tmp4 = c2_19$arg$A$0$_A⁰(A_a4); //│ set A_a5 = tmp4; -//│ set tmp5 = c2_19$x_A⁰(A_a5); +//│ set tmp5 = c2_19$scrut_A⁰(A_a5); //│ return c2_19$c2⁰(tmp5) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/deforest/dropLast.mls b/hkmc2/shared/src/test/mlscript/deforest/dropLast.mls index 37041a8b7f..1e752e6d6c 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/dropLast.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/dropLast.mls @@ -1,3 +1,4 @@ +:. :js data class (::) Cons(h, t) diff --git a/hkmc2/shared/src/test/mlscript/deforest/erasedTypes.mls b/hkmc2/shared/src/test/mlscript/deforest/erasedTypes.mls index d68375096b..7c47695f5f 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/erasedTypes.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/erasedTypes.mls @@ -1,3 +1,4 @@ +:. :global :sir :siret @@ -47,26 +48,27 @@ fun selected(n: Int, k: Str) = Box(x) then [x, k] //│ deforest > >>> fusing >>> //│ deforest > Box¹(n⁰) -> -//│ deforest > match: b² -//│ deforest > fields: b².v¹ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.v¹ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let selected_4$b_Box, selected_4$b_rest; +//│ let selected_4$scrut_Box, selected_4$scrut_rest; //│ @affine(1) @private -//│ define selected_4$b_Box as fun selected_4$b_Box⁰(Box_v: Int⁰)(fv_k: Str⁰, fv_x: ?, fv_arg$Box$0$: ?): ? { +//│ define selected_4$scrut_Box as fun selected_4$scrut_Box⁰(Box_v: Int⁰)(fv_k: Str⁰, fv_x: ?, fv_arg$Box$0$: ?): ? { //│ set fv_arg$Box$0$ = Box_v; //│ set fv_x = fv_arg$Box$0$; //│ return [fv_x, fv_k] //│ }; //│ @private -//│ define selected_4$b_rest as fun selected_4$b_rest⁰(): ? { +//│ define selected_4$scrut_rest as fun selected_4$scrut_rest⁰(): ? { //│ return null //│ }; //│ define selected⁰ as fun selected¹(n: Int⁰, k: Str⁰): ? { -//│ let b: ?, x: ?, arg$Box$0$: ?, Box_v: Int⁰; +//│ let b: ?, scrut: ?, x: ?, arg$Box$0$: ?, Box_v: Int⁰; //│ set Box_v = n; -//│ set b = selected_4$b_Box⁰(Box_v); -//│ return b(k, x, arg$Box$0$) +//│ set b = selected_4$scrut_Box⁰(Box_v); +//│ set scrut = b; +//│ return scrut(k, x, arg$Box$0$) //│ }; //│ end @@ -81,28 +83,29 @@ fun withRest(n: Int, k: Str) = [r, k] //│ deforest > >>> fusing >>> //│ deforest > Box¹(n¹) -> -//│ deforest > match: b³ -//│ deforest > fields: b³.v¹ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.v¹ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let withRest_4$b_Box, withRest_4$b_rest; +//│ let withRest_4$scrut_Box, withRest_4$scrut_rest; //│ @affine(1) @private -//│ define withRest_4$b_Box as fun withRest_4$b_Box⁰(Box_v: Int⁰)(fv_x: ?, fv_arg$Box$0$: ?, fv_tmp: ?, fv_k: Str⁰, fv_r: ?, fv_tmp1: ?): ? { +//│ define withRest_4$scrut_Box as fun withRest_4$scrut_Box⁰(Box_v: Int⁰)(fv_x: ?, fv_arg$Box$0$: ?, fv_tmp: ?, fv_k: Str⁰, fv_r: ?, fv_tmp1: ?): ? { //│ set fv_arg$Box$0$ = Box_v; //│ set fv_x = fv_arg$Box$0$; //│ set fv_tmp1 = fv_x; -//│ return withRest_4$b_rest⁰(fv_k, fv_r, fv_tmp1) +//│ return withRest_4$scrut_rest⁰(fv_k, fv_r, fv_tmp1) //│ }; //│ @private -//│ define withRest_4$b_rest as fun withRest_4$b_rest⁰(fv_k: Str⁰, fv_r: ?, fv_tmp: ?): ? { +//│ define withRest_4$scrut_rest as fun withRest_4$scrut_rest⁰(fv_k: Str⁰, fv_r: ?, fv_tmp: ?): ? { //│ set fv_r = fv_tmp; //│ return [fv_r, fv_k] //│ }; //│ define withRest⁰ as fun withRest¹(n: Int⁰, k: Str⁰): ? { -//│ let b: ?, r: ?, x: ?, arg$Box$0$: ?, tmp: ?, Box_v: Int⁰; +//│ let b: ?, r: ?, scrut: ?, x: ?, arg$Box$0$: ?, tmp: ?, Box_v: Int⁰; //│ set Box_v = n; -//│ set b = withRest_4$b_Box⁰(Box_v); -//│ return b(x, arg$Box$0$, tmp, k, r, tmp) +//│ set b = withRest_4$scrut_Box⁰(Box_v); +//│ set scrut = b; +//│ return scrut(x, arg$Box$0$, tmp, k, r, tmp) //│ }; //│ end @@ -115,23 +118,24 @@ fun noFields(n: Int, s: Str) = Pair then 1 //│ deforest > >>> fusing >>> //│ deforest > Pair¹(n², s⁰) -> -//│ deforest > match: p⁰ +//│ deforest > match: scrut² //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let noFields_2$p_Pair, noFields_2$p_rest; +//│ let noFields_2$scrut_Pair, noFields_2$scrut_rest; //│ @affine(1) @private -//│ define noFields_2$p_Pair as fun noFields_2$p_Pair⁰(_a: Int⁰, _b: Str⁰)(): ? { +//│ define noFields_2$scrut_Pair as fun noFields_2$scrut_Pair⁰(_a: Int⁰, _b: Str⁰)(): ? { //│ return 1 //│ }; //│ @private -//│ define noFields_2$p_rest as fun noFields_2$p_rest⁰(): ? { +//│ define noFields_2$scrut_rest as fun noFields_2$scrut_rest⁰(): ? { //│ return null //│ }; //│ define noFields⁰ as fun noFields¹(n: Int⁰, s: Str⁰): ? { -//│ let p: ?, Pair_a: Int⁰, Pair_b: Str⁰; +//│ let p: ?, scrut: ?, Pair_a: Int⁰, Pair_b: Str⁰; //│ set Pair_a = n; //│ set Pair_b = s; -//│ set p = noFields_2$p_Pair⁰(Pair_a, Pair_b); -//│ return p() +//│ set p = noFields_2$scrut_Pair⁰(Pair_a, Pair_b); +//│ set scrut = p; +//│ return scrut() //│ }; //│ end diff --git a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/.mls b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls index 1f706778a4..f192b1ad5d 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls @@ -1,3 +1,4 @@ +:. :js :deforest :flowBasedOpt debugEta @@ -23,8 +24,8 @@ fun c(x) = if x is c(p(3)) //│ deforest > >>> fusing >>> //│ deforest > X⁰(x⁰) -> -//│ deforest > match: x¹ -//│ deforest > fields: x¹.a⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.a⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > p_4$p: [1, 2] @@ -42,9 +43,9 @@ fun c(x) = if x is c(p(true)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: x² +//│ deforest > match: scrut¹ //│ deforest > B⁰ -> -//│ deforest > match: x² +//│ deforest > match: scrut¹ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > p_7$p: [1, 0] @@ -61,16 +62,16 @@ fun g(y) = if y is g(f(p())) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp⁰) -> -//│ deforest > match: x³ -//│ deforest > fields: x³.a⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².a⁰ //│ deforest > Y⁰(O⁰) -> -//│ deforest > match: y⁰ -//│ deforest > fields: y⁰.b⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.b⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > f_10$f: [1, 2] //│ eta-expansion > p_8$p: [0, 2, 2] -//│ eta-expansion > f_10$x_X: [1, 2, 2] +//│ eta-expansion > f_10$scrut_X: [1, 2, 2] //│ eta-expansion > <<< eta-expansion targets shapes <<< //│ = O @@ -84,14 +85,14 @@ fun c(x) = if x is c(p(true)) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp¹) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.a⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.a⁰ //│ deforest > Y⁰(22) -> //│ deforest > match: arg$X$0$⁰ //│ deforest > fields: arg$X$0$⁰.b⁰ //│ deforest > X⁰(tmp²) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.a⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.a⁰ //│ deforest > Y⁰(33) -> //│ deforest > match: arg$X$0$⁰ //│ deforest > fields: arg$X$0$⁰.b⁰ @@ -111,14 +112,14 @@ fun main(x) = main(3) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp³) -> -//│ deforest > match: x⁵ -//│ deforest > fields: x⁵.a⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.a⁰ //│ deforest > Y⁰(tmp⁴) -> //│ deforest > match: arg$X$0$¹ //│ deforest > fields: arg$X$0$¹.b⁰ //│ deforest > X⁰(tmp³) -> -//│ deforest > match: x⁵ -//│ deforest > fields: x⁵.a⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.a⁰ //│ deforest > Y⁰(tmp⁴) -> //│ deforest > match: arg$X$0$¹ //│ deforest > fields: arg$X$0$¹.b⁰ @@ -137,8 +138,8 @@ fun c(x) = if x is X(a) then a c(p(3)) //│ deforest > >>> fusing >>> -//│ deforest > X⁰(x⁶) -> -//│ deforest > match: x⁷ -//│ deforest > fields: x⁷.a⁰ +//│ deforest > X⁰(x¹) -> +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.a⁰ //│ deforest > <<< fusing <<< //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls index 431e565fd5..330d5ea907 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls @@ -1,3 +1,4 @@ +:. :js :deforest :flowBasedOpt debugEta @@ -21,11 +22,11 @@ fun sum(ls, acc) = if ls is sum(enum(5), 0) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut⁰ //│ deforest > Cons⁰(x⁰, tmp⁰) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h⁰ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > enum_13$enum: [1, 7] @@ -104,11 +105,11 @@ fun len(ls) = if ls is len(flatten(id((1 :: 2 :: Nil) :: (3 :: Nil) :: Nil))) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹ +//│ deforest > match: scrut¹ //│ deforest > Cons⁰(h¹, tmp¹) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h⁰ -//│ deforest > fields: ls¹.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > flatten_31_append_16$append: [2, 6] diff --git a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls index d8a620b088..f95772c3a3 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls @@ -1,3 +1,4 @@ +:. :js :deforest logNonAffine @@ -32,8 +33,8 @@ c(AA(3)) //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(3) -> -//│ deforest > match: p⁰ -//│ deforest > fields: p⁰.x⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -42,13 +43,11 @@ c(AA(3)) fun c(x) = if x is AA(_) then x.AA#x + x.AA#x c(AA(7)) //│ deforest > >>> non-affine syms >>> +//│ deforest > tmp@1 +//│ deforest > x_for_c@13 +//│ deforest > x_for_c@24 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(7) -> -//│ deforest > match: x¹ -//│ deforest > fields: x¹.x⁰ -//│ deforest > fields: x¹.x⁰ -//│ deforest > fields: x¹.x⁰ //│ deforest > <<< fusing <<< //│ = 14 @@ -58,14 +57,14 @@ fun c(p) = cl() + cl() c(AA(7)) //│ deforest > >>> non-affine syms >>> -//│ deforest > cap_ub_(term:lambda,0)_for_c@30 -//│ deforest > cap_ub_(term:lambda,0)_for_c@47 -//│ deforest > cl_for_c@19 -//│ deforest > cl_for_c@36 -//│ deforest > lambda_for_c@28 -//│ deforest > lambda_for_c@45 -//│ deforest > p_for_c@20 -//│ deforest > p_for_c@37 +//│ deforest > cap_ub_(term:lambda,0)_for_c@31 +//│ deforest > cap_ub_(term:lambda,0)_for_c@49 +//│ deforest > cl_for_c@20 +//│ deforest > cl_for_c@38 +//│ deforest > lambda_for_c@29 +//│ deforest > lambda_for_c@47 +//│ deforest > p_for_c@21 +//│ deforest > p_for_c@39 //│ deforest > tmp@1 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> @@ -83,11 +82,11 @@ affine_branched(AA(0), AA(1), (y) => if y is AA(x) then x + 2) //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(1) -> -//│ deforest > match: y⁰ -//│ deforest > fields: y⁰.x⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.x⁰ //│ deforest > AA⁰(0) -> -//│ deforest > match: x² -//│ deforest > fields: x².x⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².x⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -108,13 +107,13 @@ fun c(p) = if p is AA(v) then v fun apply(f, x) = f(x) + f(x) apply(c, AA(3)) //│ deforest > >>> non-affine syms >>> -//│ deforest > c_for_c@29 -//│ deforest > cap_ub_(term:c,0)_for_c@32 -//│ deforest > f_for_apply@20 -//│ deforest > f_for_apply@45 +//│ deforest > c_for_c@30 +//│ deforest > cap_ub_(term:c,0)_for_c@33 +//│ deforest > f_for_apply@22 +//│ deforest > f_for_apply@49 //│ deforest > tmp@1 //│ deforest > x_for_apply@21 -//│ deforest > x_for_apply@46 +//│ deforest > x_for_apply@48 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -126,13 +125,13 @@ fun c(k, x) = if k is AA(_) then x.AA#x + x.AA#x + x.AA#x c(AA(0), AA(3)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@2 -//│ deforest > x_for_c@17 -//│ deforest > x_for_c@31 +//│ deforest > x_for_c@18 +//│ deforest > x_for_c@33 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(0) -> -//│ deforest > match: k⁰ -//│ deforest > fields: k⁰.x⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.x⁰ //│ deforest > <<< fusing <<< //│ = 9 @@ -147,8 +146,8 @@ fun c(y) = if y is AA(_) then c(AA(1)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@1 -//│ deforest > y_for_c@13 -//│ deforest > y_for_c@24 +//│ deforest > y_for_c@14 +//│ deforest > y_for_c@26 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -161,9 +160,9 @@ fun c2(x) = if x is AA(AA(_)) then 1 else 0 let p = AA(AA(AA(10))) in c1(p) + c2(p) //│ deforest > >>> non-affine syms >>> -//│ deforest > p@2 +//│ deforest > p@5 //│ deforest > tmp@1 -//│ deforest > tmp@3 +//│ deforest > tmp@2 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -178,8 +177,8 @@ fun c2(p) = if p is Many(Once(v)) then v fun apply(f, g, p) = f(p) + g(p) apply(c1, c2, Many(Once(10))) //│ deforest > >>> non-affine syms >>> -//│ deforest > p_for_apply@30 -//│ deforest > p_for_apply@72 +//│ deforest > p_for_apply@32 +//│ deforest > p_for_apply@78 //│ deforest > tmp@1 //│ deforest > tmp@2 //│ deforest > <<< non-affine syms <<< @@ -195,23 +194,23 @@ fun nest_consume(f, p) = if p is AA(i) then f(i) + f(i) fun nest_pickB(p) = if p is BB(b) then b + 1 nest_consume(nest_pickB, nest_prod(10)) //│ deforest > >>> non-affine syms >>> -//│ deforest > arg$AA$0$_for_nest_consume@45 -//│ deforest > arg$AA$0$_for_nest_consume@72 -//│ deforest > cap_ub_(term:nest_pickB,0)_for_nest_pickB@50 -//│ deforest > f_for_nest_consume@35 -//│ deforest > f_for_nest_consume@62 -//│ deforest > i_for_nest_consume@36 -//│ deforest > i_for_nest_consume@63 -//│ deforest > nest_pickB_for_nest_pickB@47 -//│ deforest > sel_res_for_nest_consume@46 -//│ deforest > sel_res_for_nest_consume@73 -//│ deforest > tmp_for_nest_prod@32 -//│ deforest > x_for_nest_prod@29 +//│ deforest > arg$AA$0$_for_nest_consume@47 +//│ deforest > arg$AA$0$_for_nest_consume@76 +//│ deforest > cap_ub_(term:nest_pickB,0)_for_nest_pickB@53 +//│ deforest > f_for_nest_consume@38 +//│ deforest > f_for_nest_consume@67 +//│ deforest > i_for_nest_consume@37 +//│ deforest > i_for_nest_consume@66 +//│ deforest > nest_pickB_for_nest_pickB@50 +//│ deforest > sel_res_for_nest_consume@48 +//│ deforest > sel_res_for_nest_consume@77 +//│ deforest > tmp_for_nest_prod@34 +//│ deforest > x_for_nest_prod@31 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: p¹ -//│ deforest > fields: p¹.x⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.x⁰ //│ deforest > <<< fusing <<< //│ = 22 @@ -224,16 +223,16 @@ fun length(ls) = if ls is fun from(n, acc) = if n == 0 then acc else from(n - 1, n :: acc) length(from(5, Nil)) //│ deforest > >>> non-affine syms >>> -//│ deforest > call_res_for_from@34 -//│ deforest > call_res_for_from@73 -//│ deforest > n_for_from@27 -//│ deforest > n_for_from@66 -//│ deforest > tmp_for_from@32 -//│ deforest > tmp_for_from@71 +//│ deforest > call_res_for_from@35 +//│ deforest > call_res_for_from@76 +//│ deforest > n_for_from@28 +//│ deforest > n_for_from@69 +//│ deforest > tmp_for_from@33 +//│ deforest > tmp_for_from@74 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> -//│ deforest > acc_for_from@28 -//│ deforest > acc_for_from@67 +//│ deforest > acc_for_from@29 +//│ deforest > acc_for_from@70 //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -254,15 +253,15 @@ length(append(id(1 :: Nil), 2 :: Nil)) //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(h⁰, tmp¹) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h¹ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.h¹ +//│ deforest > fields: scrut⁵.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h¹ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.h¹ +//│ deforest > fields: scrut⁵.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut⁵ //│ deforest > <<< fusing <<< //│ = 2 @@ -285,15 +284,15 @@ length(ping(id(1 :: 2 :: Nil), 3 :: 4 :: Nil)) //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(3, tmp²) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h¹ -//│ deforest > fields: ls¹.t⁰ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.h¹ +//│ deforest > fields: scrut⁶.t⁰ //│ deforest > Cons⁰(4, Nil⁰) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h¹ -//│ deforest > fields: ls¹.t⁰ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.h¹ +//│ deforest > fields: scrut⁶.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹ +//│ deforest > match: scrut⁶ //│ deforest > <<< fusing <<< //│ = 2 @@ -315,11 +314,11 @@ sum(append(id(Nil), Nil), 0) //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(h², tmp³) -> -//│ deforest > match: ls² -//│ deforest > fields: ls².h¹ -//│ deforest > fields: ls².t⁰ +//│ deforest > match: scrut⁷ +//│ deforest > fields: scrut⁷.h¹ +//│ deforest > fields: scrut⁷.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls² +//│ deforest > match: scrut⁷ //│ deforest > <<< fusing <<< //│ = 0 @@ -356,11 +355,11 @@ length(flatten(id((1 :: Nil) :: Nil))) //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(h³, tmp⁴) -> -//│ deforest > match: ls³ -//│ deforest > fields: ls³.h¹ -//│ deforest > fields: ls³.t⁰ +//│ deforest > match: scrut⁸ +//│ deforest > fields: scrut⁸.h¹ +//│ deforest > fields: scrut⁸.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls³ +//│ deforest > match: scrut⁸ //│ deforest > <<< fusing <<< //│ = 1 @@ -373,8 +372,8 @@ fun alias_then_patmat(x) = alias_then_patmat(Cons(12, 13)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@1 -//│ deforest > x_for_alias_then_patmat@18 -//│ deforest > x_for_alias_then_patmat@34 +//│ deforest > x_for_alias_then_patmat@19 +//│ deforest > x_for_alias_then_patmat@36 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< @@ -391,14 +390,15 @@ fun foo(a) = AA then a.AA#x() + a.AA#x() foo of AA of () => 1 //│ deforest > >>> non-affine syms >>> +//│ deforest > a_for_foo@21 +//│ deforest > a_for_foo@34 +//│ deforest > cap_ub_(term:lambda,0)_for_lambda@19 +//│ deforest > lambda_for_lambda@17 +//│ deforest > tmp@1 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(lambda⁰) -> -//│ deforest > match: a⁰ -//│ deforest > fields: a⁰.x⁰ -//│ deforest > fields: a⁰.x⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -410,19 +410,19 @@ fun foo(a) = f() + f() foo of AA of () => 1 //│ deforest > >>> non-affine syms >>> -//│ deforest > cap_ub_(term:lambda,0)_for_lambda@18 -//│ deforest > f_for_foo@20 -//│ deforest > f_for_foo@32 -//│ deforest > lambda_for_lambda@16 -//│ deforest > sel_res_for_foo@29 -//│ deforest > sel_res_for_foo@41 +//│ deforest > a_for_foo@21 +//│ deforest > a_for_foo@34 +//│ deforest > cap_ub_(term:lambda,0)_for_lambda@19 +//│ deforest > f_for_foo@22 +//│ deforest > f_for_foo@35 +//│ deforest > lambda_for_lambda@17 +//│ deforest > sel_res_for_foo@30 +//│ deforest > sel_res_for_foo@43 +//│ deforest > tmp@1 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(lambda¹) -> -//│ deforest > match: a¹ -//│ deforest > fields: a¹.x⁰ //│ deforest > <<< fusing <<< //│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/deforest/imperative.mls b/hkmc2/shared/src/test/mlscript/deforest/imperative.mls index b5d22bdb24..661bbee616 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/imperative.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/imperative.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -45,11 +46,11 @@ fun bar(v) = if v is foo(bar, 123) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: v⁰ +//│ deforest > match: scrut⁰ //│ deforest > A⁰ -> -//│ deforest > match: v⁰ +//│ deforest > match: scrut⁰ //│ deforest > B⁰ -> -//│ deforest > match: v⁰ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls index 254b119b14..7fd35953e8 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -28,65 +29,65 @@ fun lscomp1(ls) = if ls is lscomp1(zip(enumFromTo(1, 3), enumFromTo(2, 4))) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ +//│ deforest > match: scrut⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: xs_zip⁰ -//│ deforest > fields: xs_zip⁰.h⁰ -//│ deforest > fields: xs_zip⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h⁰ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ +//│ deforest > match: scrut¹ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: ys_zip⁰ -//│ deforest > fields: ys_zip⁰.h⁰ -//│ deforest > fields: ys_zip⁰.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls2⁰ +//│ deforest > match: scrut² //│ deforest > Nil⁰ -> -//│ deforest > match: ls2⁰ +//│ deforest > match: scrut² //│ deforest > Cons⁰(tmp¹, tmp²) -> -//│ deforest > match: ls2⁰ -//│ deforest > fields: ls2⁰.h⁰ -//│ deforest > fields: ls2⁰.t⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².h⁰ +//│ deforest > fields: scrut².t⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a¹ //│ deforest > fields: arg$Cons$0$⁰.b⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ +//│ deforest > match: scrut⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: xs_zip⁰ -//│ deforest > fields: xs_zip⁰.h⁰ -//│ deforest > fields: xs_zip⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h⁰ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ +//│ deforest > match: scrut¹ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: ys_zip⁰ -//│ deforest > fields: ys_zip⁰.h⁰ -//│ deforest > fields: ys_zip⁰.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls2⁰ +//│ deforest > match: scrut² //│ deforest > Nil⁰ -> -//│ deforest > match: ls2⁰ +//│ deforest > match: scrut² //│ deforest > Cons⁰(tmp¹, tmp²) -> -//│ deforest > match: ls2⁰ -//│ deforest > fields: ls2⁰.h⁰ -//│ deforest > fields: ls2⁰.t⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².h⁰ +//│ deforest > fields: scrut².t⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a¹ //│ deforest > fields: arg$Cons$0$⁰.b⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: ys_zip⁰ -//│ deforest > fields: ys_zip⁰.h⁰ -//│ deforest > fields: ys_zip⁰.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ +//│ deforest > match: scrut¹ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: xs_zip⁰ -//│ deforest > fields: xs_zip⁰.h⁰ -//│ deforest > fields: xs_zip⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h⁰ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = Cons(Pair(1, 3), Cons(Pair(2, 4), Cons(Pair(3, 5), Nil))) @@ -106,91 +107,91 @@ fun f1(a1) = if a1 is concatMap(f1, Pair(5, 10) :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(tmp³, Nil⁰) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h⁰ -//│ deforest > fields: xs⁰.t⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.h⁰ +//│ deforest > fields: scrut³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ +//│ deforest > match: scrut³ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ +//│ deforest > match: scrut³ //│ deforest > Cons⁰(tmp⁴, tmp⁵) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > Cons⁰(tmp⁶, tmp⁷) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > Cons⁰(tmp⁸, Nil⁰) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut⁴ //│ deforest > Pair⁰(a², b¹) -> -//│ deforest > match: a2⁰ -//│ deforest > fields: a2⁰.a¹ -//│ deforest > fields: a2⁰.b⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.a¹ +//│ deforest > fields: scrut⁵.b⁰ //│ deforest > Pair⁰(2, 3) -> -//│ deforest > match: a2⁰ -//│ deforest > fields: a2⁰.a¹ -//│ deforest > fields: a2⁰.b⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.a¹ +//│ deforest > fields: scrut⁵.b⁰ //│ deforest > Pair⁰(1, 3) -> -//│ deforest > match: a2⁰ -//│ deforest > fields: a2⁰.a¹ -//│ deforest > fields: a2⁰.b⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.a¹ +//│ deforest > fields: scrut⁵.b⁰ //│ deforest > Cons⁰(tmp³, Nil⁰) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h⁰ -//│ deforest > fields: xs⁰.t⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.h⁰ +//│ deforest > fields: scrut³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ +//│ deforest > match: scrut³ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ +//│ deforest > match: scrut³ //│ deforest > Cons⁰(tmp⁴, tmp⁵) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > Cons⁰(tmp⁶, tmp⁷) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > Cons⁰(tmp⁸, Nil⁰) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut⁴ //│ deforest > Pair⁰(a², b¹) -> -//│ deforest > match: a2⁰ -//│ deforest > fields: a2⁰.a¹ -//│ deforest > fields: a2⁰.b⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.a¹ +//│ deforest > fields: scrut⁵.b⁰ //│ deforest > Pair⁰(2, 3) -> -//│ deforest > match: a2⁰ -//│ deforest > fields: a2⁰.a¹ -//│ deforest > fields: a2⁰.b⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.a¹ +//│ deforest > fields: scrut⁵.b⁰ //│ deforest > Pair⁰(1, 3) -> -//│ deforest > match: a2⁰ -//│ deforest > fields: a2⁰.a¹ -//│ deforest > fields: a2⁰.b⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.a¹ +//│ deforest > fields: scrut⁵.b⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ +//│ deforest > match: scrut³ //│ deforest > Cons⁰(h¹, tmp⁹) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h⁰ -//│ deforest > fields: xs⁰.t⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.h⁰ +//│ deforest > fields: scrut³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ +//│ deforest > match: scrut³ //│ deforest > Cons⁰(tmp¹⁰, Nil⁰) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut⁴ //│ deforest > Pair⁰(5, 10) -> -//│ deforest > match: a1⁰ -//│ deforest > fields: a1⁰.a¹ -//│ deforest > fields: a1⁰.b⁰ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.a¹ +//│ deforest > fields: scrut⁶.b⁰ //│ deforest > <<< fusing <<< //│ = Cons(Pair(5, 1), Cons(Pair(5, 2), Cons(Pair(5, 5), Nil))) @@ -206,15 +207,15 @@ fun lscomp2(ls2, h1, t1) = if ls2 is lscomp1(1 :: 2 :: Nil, 3 :: 4 :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, tmp¹¹) -> -//│ deforest > match: ls1⁰ -//│ deforest > fields: ls1⁰.h⁰ -//│ deforest > fields: ls1⁰.t⁰ +//│ deforest > match: scrut⁷ +//│ deforest > fields: scrut⁷.h⁰ +//│ deforest > fields: scrut⁷.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: ls1⁰ -//│ deforest > fields: ls1⁰.h⁰ -//│ deforest > fields: ls1⁰.t⁰ +//│ deforest > match: scrut⁷ +//│ deforest > fields: scrut⁷.h⁰ +//│ deforest > fields: scrut⁷.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls1⁰ +//│ deforest > match: scrut⁷ //│ deforest > <<< fusing <<< //│ = Cons(Pair(1, 3), Cons(Pair(1, 4), Nil)) @@ -228,21 +229,21 @@ if A is B then Nil //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, tmp¹²) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h⁰ -//│ deforest > fields: ls¹.t⁰ +//│ deforest > match: scrut⁸ +//│ deforest > fields: scrut⁸.h⁰ +//│ deforest > fields: scrut⁸.t⁰ //│ deforest > Cons⁰(2, tmp¹³) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h⁰ -//│ deforest > fields: ls¹.t⁰ +//│ deforest > match: scrut⁸ +//│ deforest > fields: scrut⁸.h⁰ +//│ deforest > fields: scrut⁸.t⁰ //│ deforest > Cons⁰(3, Nil⁰) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h⁰ -//│ deforest > fields: ls¹.t⁰ +//│ deforest > match: scrut⁸ +//│ deforest > fields: scrut⁸.h⁰ +//│ deforest > fields: scrut⁸.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹ +//│ deforest > match: scrut⁸ //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut⁹ //│ deforest > <<< fusing <<< //│ = Cons(2, Cons(3, Cons(4, Nil))) @@ -260,33 +261,33 @@ fun h() = h() //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, tmp¹⁴) -> -//│ deforest > match: ls² -//│ deforest > fields: ls².h⁰ -//│ deforest > fields: ls².t⁰ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.h⁰ +//│ deforest > fields: scrut¹⁰.t⁰ //│ deforest > Cons⁰(2, tmp¹⁵) -> -//│ deforest > match: ls² -//│ deforest > fields: ls².h⁰ -//│ deforest > fields: ls².t⁰ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.h⁰ +//│ deforest > fields: scrut¹⁰.t⁰ //│ deforest > Cons⁰(3, Nil⁰) -> -//│ deforest > match: ls² -//│ deforest > fields: ls².h⁰ -//│ deforest > fields: ls².t⁰ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.h⁰ +//│ deforest > fields: scrut¹⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls² +//│ deforest > match: scrut¹⁰ //│ deforest > Cons⁰(1, tmp¹⁴) -> -//│ deforest > match: ls² -//│ deforest > fields: ls².h⁰ -//│ deforest > fields: ls².t⁰ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.h⁰ +//│ deforest > fields: scrut¹⁰.t⁰ //│ deforest > Cons⁰(2, tmp¹⁵) -> -//│ deforest > match: ls² -//│ deforest > fields: ls².h⁰ -//│ deforest > fields: ls².t⁰ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.h⁰ +//│ deforest > fields: scrut¹⁰.t⁰ //│ deforest > Cons⁰(3, Nil⁰) -> -//│ deforest > match: ls² -//│ deforest > fields: ls².h⁰ -//│ deforest > fields: ls².t⁰ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.h⁰ +//│ deforest > fields: scrut¹⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls² +//│ deforest > match: scrut¹⁰ //│ deforest > <<< fusing <<< //│ = Cons( //│ [1, 1], diff --git a/hkmc2/shared/src/test/mlscript/deforest/module.mls b/hkmc2/shared/src/test/mlscript/deforest/module.mls index 319c8b6fd8..565917aec2 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/module.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/module.mls @@ -1,3 +1,4 @@ +:. :js @@ -15,8 +16,8 @@ module M with M.f(M.p()) //│ deforest > >>> fusing >>> //│ deforest > M⁰.this.XX⁰(tmp⁰) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.a⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.a⁰ //│ deforest > M⁰.this.YY⁰(M⁰.this.OO⁰) -> //│ deforest > match: arg$XX$0$⁰ //│ deforest > fields: arg$XX$0$⁰.b⁰ @@ -42,23 +43,23 @@ module DeforestList with DeforestList.res + DeforestList.main() //│ deforest > >>> fusing >>> //│ deforest > DeforestList⁰.this.Nil⁰ -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut¹ //│ deforest > DeforestList⁰.this.Cons⁰(n⁰, tmp¹) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > DeforestList⁰.this.Nil⁰ -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut¹ //│ deforest > DeforestList⁰.this.Cons⁰(n⁰, tmp¹) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > DeforestList⁰.this.Cons⁰(n⁰, tmp¹) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > DeforestList⁰.this.Nil⁰ -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut¹ //│ deforest > <<< fusing <<< //│ = 5056 @@ -82,23 +83,23 @@ module DeforestList with DeforestList.res + DeforestList.main() //│ deforest > >>> fusing >>> //│ deforest > DeforestList¹.this.Nil¹ -> -//│ deforest > match: ls¹ +//│ deforest > match: scrut² //│ deforest > DeforestList¹.this.Cons¹(n¹, tmp²) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h¹ -//│ deforest > fields: ls¹.t¹ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².h¹ +//│ deforest > fields: scrut².t¹ //│ deforest > DeforestList¹.this.Nil¹ -> -//│ deforest > match: ls¹ +//│ deforest > match: scrut² //│ deforest > DeforestList¹.this.Cons¹(n¹, tmp²) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h¹ -//│ deforest > fields: ls¹.t¹ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².h¹ +//│ deforest > fields: scrut².t¹ //│ deforest > DeforestList¹.this.Cons¹(n¹, tmp²) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h¹ -//│ deforest > fields: ls¹.t¹ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².h¹ +//│ deforest > fields: scrut².t¹ //│ deforest > DeforestList¹.this.Nil¹ -> -//│ deforest > match: ls¹ +//│ deforest > match: scrut² //│ deforest > <<< fusing <<< //│ = 5056 @@ -115,11 +116,11 @@ module MMM with MMM.f() //│ deforest > >>> fusing >>> //│ deforest > MMM⁰.this.A⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: scrut³ //│ deforest > MMM⁰.this.A⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: scrut³ //│ deforest > MMM⁰.this.A⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: scrut³ //│ deforest > <<< fusing <<< //│ = 3 @@ -139,7 +140,7 @@ module M with M.f(M.X) //│ deforest > >>> fusing >>> //│ deforest > M¹.X⁰ -> -//│ deforest > match: k⁰ +//│ deforest > match: scrut⁴ //│ deforest > <<< fusing <<< //│ = X @@ -166,25 +167,25 @@ module M with [M.f(3), M.g(true)] //│ deforest > >>> fusing >>> //│ deforest > B⁰(0) -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁵ //│ deforest > D⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁵ //│ deforest > A¹ -> -//│ deforest > match: scrut² +//│ deforest > match: scrut⁶ //│ deforest > A¹ -> -//│ deforest > match: scrut² +//│ deforest > match: scrut⁶ //│ deforest > C⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: scrut⁶ //│ deforest > B⁰(0) -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁵ //│ deforest > D⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁵ //│ deforest > A¹ -> -//│ deforest > match: scrut² +//│ deforest > match: scrut⁶ //│ deforest > A¹ -> -//│ deforest > match: scrut² +//│ deforest > match: scrut⁶ //│ deforest > C⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: scrut⁶ //│ deforest > <<< fusing <<< //│ = [D, A] @@ -205,8 +206,8 @@ private fun g(x, module I: O.I) = if x is g(f(O.I), O.I) //│ deforest > >>> fusing >>> //│ deforest > I⁰.C¹(3) -> -//│ deforest > match: x¹ -//│ deforest > fields: x¹.x² +//│ deforest > match: scrut⁷ +//│ deforest > fields: scrut⁷.x⁰ //│ deforest > <<< fusing <<< //│ = 5 @@ -219,7 +220,7 @@ private fun g(x, module O: O) = if x is g(f(O), O) //│ deforest > >>> fusing >>> //│ deforest > O⁰.I¹.C¹(3) -> -//│ deforest > match: x³ -//│ deforest > fields: x³.x² +//│ deforest > match: scrut⁸ +//│ deforest > fields: scrut⁸.x⁰ //│ deforest > <<< fusing <<< //│ = 5 diff --git a/hkmc2/shared/src/test/mlscript/deforest/multiArgLists.mls b/hkmc2/shared/src/test/mlscript/deforest/multiArgLists.mls index c99a124325..576ffa7e56 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/multiArgLists.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/multiArgLists.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -12,9 +13,9 @@ fun test(a)(b) = test(Pair(1, 2))(3) //│ deforest > >>> fusing >>> //│ deforest > Pair⁰(1, 2) -> -//│ deforest > match: a⁰ -//│ deforest > fields: a⁰.x⁰ -//│ deforest > fields: a⁰.y⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x⁰ +//│ deforest > fields: scrut⁰.y⁰ //│ deforest > <<< fusing <<< //│ = 6 @@ -28,8 +29,8 @@ fun unbox(b)(offset) = unbox(Box(10))(5) //│ deforest > >>> fusing >>> //│ deforest > Box⁰(10) -> -//│ deforest > match: b⁰ -//│ deforest > fields: b⁰.v⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.v⁰ //│ deforest > <<< fusing <<< //│ = 15 diff --git a/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls b/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls index 1deb2c2326..ce21a3f8ff 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -36,14 +37,14 @@ let a = AA(AA(3)) f(a, 2) + g(a) + f(BB(3), 2) + f(AA(AA(4)), 5) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.x¹ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x⁰ //│ deforest > AA⁰(4) -> -//│ deforest > match: a⁰ -//│ deforest > fields: a⁰.x¹ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.x⁰ //│ deforest > BB⁰(3) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.x² +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x¹ //│ deforest > <<< fusing <<< //│ = 37 //│ a = AA(AA(3)) @@ -60,17 +61,17 @@ fun f(x, y) = f(AA(AA(3)), 9) + f(AA(AA(4)), 10) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp¹) -> -//│ deforest > match: x³ -//│ deforest > fields: x³.x¹ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².x⁰ //│ deforest > AA⁰(4) -> -//│ deforest > match: a¹ -//│ deforest > fields: a¹.x¹ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.x⁰ //│ deforest > AA⁰(tmp²) -> -//│ deforest > match: x³ -//│ deforest > fields: x³.x¹ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².x⁰ //│ deforest > AA⁰(3) -> -//│ deforest > match: a¹ -//│ deforest > fields: a¹.x¹ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.x⁰ //│ deforest > <<< fusing <<< //│ = 30 @@ -86,11 +87,11 @@ fun f(x) = f(AA(AA(3))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp³) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.x¹ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.x⁰ //│ deforest > AA⁰(3) -> -//│ deforest > match: a² -//│ deforest > fields: a².x¹ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.x⁰ //│ deforest > <<< fusing <<< //│ = 5 @@ -104,17 +105,17 @@ fun f(x) = f(AA(AA(A))) + f(AA(AA(A))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁴) -> -//│ deforest > match: x⁵ -//│ deforest > fields: x⁵.x¹ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.x⁰ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x¹ +//│ deforest > fields: arg$AA$0$⁰.x⁰ //│ deforest > AA⁰(tmp⁵) -> -//│ deforest > match: x⁵ -//│ deforest > fields: x⁵.x¹ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.x⁰ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x¹ +//│ deforest > fields: arg$AA$0$⁰.x⁰ //│ deforest > <<< fusing <<< //│ = "A3A3" @@ -126,11 +127,11 @@ fun c2(x) = if x is c2(AA(AA(0))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁶) -> -//│ deforest > match: x⁶ -//│ deforest > fields: x⁶.x¹ +//│ deforest > match: scrut⁷ +//│ deforest > fields: scrut⁷.x⁰ //│ deforest > AA⁰(0) -> //│ deforest > match: arg$AA$0$¹ -//│ deforest > fields: arg$AA$0$¹.x¹ +//│ deforest > fields: arg$AA$0$¹.x⁰ //│ deforest > <<< fusing <<< //│ = 0 @@ -142,11 +143,11 @@ fun f(a) = if a is f(AA(BB(B))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁷) -> -//│ deforest > match: a³ -//│ deforest > fields: a³.x¹ +//│ deforest > match: scrut⁸ +//│ deforest > fields: scrut⁸.x⁰ //│ deforest > BB⁰(B⁰) -> //│ deforest > match: arg$AA$0$² -//│ deforest > fields: arg$AA$0$².x² +//│ deforest > fields: arg$AA$0$².x¹ //│ deforest > B⁰ -> //│ deforest > match: arg$BB$0$⁰ //│ deforest > <<< fusing <<< @@ -164,23 +165,23 @@ fun test(x, y, z, i) = if x is test(AA(1), AA(2), BB(3), 4) + test(AA(1), AA(2), BB(3), 4) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(3) -> -//│ deforest > match: z⁰ -//│ deforest > fields: z⁰.x² +//│ deforest > match: scrut⁹ +//│ deforest > fields: scrut⁹.x¹ //│ deforest > AA⁰(2) -> -//│ deforest > match: y⁰ -//│ deforest > fields: y⁰.x¹ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.x⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: x⁷ -//│ deforest > fields: x⁷.x¹ +//│ deforest > match: scrut¹¹ +//│ deforest > fields: scrut¹¹.x⁰ //│ deforest > BB⁰(3) -> -//│ deforest > match: z⁰ -//│ deforest > fields: z⁰.x² +//│ deforest > match: scrut⁹ +//│ deforest > fields: scrut⁹.x¹ //│ deforest > AA⁰(2) -> -//│ deforest > match: y⁰ -//│ deforest > fields: y⁰.x¹ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.x⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: x⁷ -//│ deforest > fields: x⁷.x¹ +//│ deforest > match: scrut¹¹ +//│ deforest > fields: scrut¹¹.x⁰ //│ deforest > <<< fusing <<< //│ = 10 @@ -199,14 +200,14 @@ fun aa(x) = AA(x) let cc = CC("cc") f1(aa(BB(cc))) + f2(aa(BB(CC(0)))) + f3(cc) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x⁸) -> -//│ deforest > match: x⁹ -//│ deforest > AA⁰(x⁸) -> -//│ deforest > match: x¹⁰ -//│ deforest > fields: x¹⁰.x¹ +//│ deforest > AA⁰(x²) -> +//│ deforest > match: scrut¹² +//│ deforest > AA⁰(x²) -> +//│ deforest > match: scrut¹³ +//│ deforest > fields: scrut¹³.x⁰ //│ deforest > BB⁰(cc⁰) -> -//│ deforest > match: x1⁰ -//│ deforest > fields: x1⁰.x² +//│ deforest > match: scrut¹⁴ +//│ deforest > fields: scrut¹⁴.x¹ //│ deforest > <<< fusing <<< //│ = "ccf2f3cc" //│ cc = CC("cc") @@ -222,11 +223,8 @@ fun c(x, y) = if x is let y = A in c(AA(2), y) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(2) -> -//│ deforest > match: x¹¹ -//│ deforest > fields: x¹¹.x¹ //│ deforest > A⁰ -> -//│ deforest > match: y¹ +//│ deforest > match: scrut¹⁵ //│ deforest > <<< fusing <<< //│ = 4 @@ -241,10 +239,6 @@ fun c2(y) = if y is A then 3 let y = A in c(AA(2), y) + c2(y) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(2) -> -//│ deforest > match: x¹² -//│ deforest > fields: x¹².x¹ -//│ deforest > fields: x¹².x¹ //│ deforest > <<< fusing <<< //│ = 9 @@ -262,8 +256,8 @@ let p = AA(AA(AA("10"))) test(p) + f(p) + test(AA(A)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(A⁰) -> -//│ deforest > match: x¹³ -//│ deforest > fields: x¹³.x¹ +//│ deforest > match: scrut¹⁶ +//│ deforest > fields: scrut¹⁶.x⁰ //│ deforest > A⁰ -> //│ deforest > match: arg$AA$0$³ //│ deforest > <<< fusing <<< @@ -287,30 +281,21 @@ fun f(x, y) = let aa = AA(3) f(aa, 3) + f(aa, 4) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(tmp⁸) -> -//│ deforest > match: tmp⁹ -//│ deforest > fields: tmp⁹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x¹ +//│ deforest > match: scrut¹⁷ +//│ deforest > fields: scrut¹⁷.x⁰ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut¹ -//│ deforest > BB⁰(tmp⁸) -> -//│ deforest > match: tmp⁹ -//│ deforest > fields: tmp⁹.x² +//│ deforest > match: scrut¹⁸ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x¹ +//│ deforest > match: scrut¹⁷ +//│ deforest > fields: scrut¹⁷.x⁰ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut¹ -//│ deforest > BB⁰(tmp⁸) -> -//│ deforest > match: tmp⁹ -//│ deforest > fields: tmp⁹.x² +//│ deforest > match: scrut¹⁸ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x¹ +//│ deforest > match: scrut¹⁷ +//│ deforest > fields: scrut¹⁷.x⁰ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut¹⁸ //│ deforest > <<< fusing <<< //│ = 13 //│ aa = AA(3) @@ -329,23 +314,23 @@ fun test(n, p, q) = test(3, AA(2), AA(3)) + test(3, AA(2), AA(3)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut² +//│ deforest > match: scrut¹⁹ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut² +//│ deforest > match: scrut¹⁹ //│ deforest > AA⁰(3) -> -//│ deforest > match: q⁰ -//│ deforest > fields: q⁰.x¹ +//│ deforest > match: scrut²⁰ +//│ deforest > fields: scrut²⁰.x⁰ //│ deforest > AA⁰(2) -> -//│ deforest > match: p⁰ -//│ deforest > fields: p⁰.x¹ +//│ deforest > match: scrut²¹ +//│ deforest > fields: scrut²¹.x⁰ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut² +//│ deforest > match: scrut¹⁹ //│ deforest > AA⁰(3) -> -//│ deforest > match: q⁰ -//│ deforest > fields: q⁰.x¹ +//│ deforest > match: scrut²⁰ +//│ deforest > fields: scrut²⁰.x⁰ //│ deforest > AA⁰(2) -> -//│ deforest > match: p⁰ -//│ deforest > fields: p⁰.x¹ +//│ deforest > match: scrut²¹ +//│ deforest > fields: scrut²¹.x⁰ //│ deforest > <<< fusing <<< //│ = 16 @@ -369,17 +354,17 @@ fun c(a) = if a is A then 0 test(1, AA(2), AA(3), a) + test(2, AA(2), AA(3), a) + c(a) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(3) -> -//│ deforest > match: q¹ -//│ deforest > fields: q¹.x¹ +//│ deforest > match: scrut²² +//│ deforest > fields: scrut²².x⁰ //│ deforest > AA⁰(2) -> -//│ deforest > match: p¹ -//│ deforest > fields: p¹.x¹ +//│ deforest > match: scrut²³ +//│ deforest > fields: scrut²³.x⁰ //│ deforest > AA⁰(3) -> -//│ deforest > match: q¹ -//│ deforest > fields: q¹.x¹ +//│ deforest > match: scrut²² +//│ deforest > fields: scrut²².x⁰ //│ deforest > AA⁰(2) -> -//│ deforest > match: p¹ -//│ deforest > fields: p¹.x¹ +//│ deforest > match: scrut²³ +//│ deforest > fields: scrut²³.x⁰ //│ deforest > <<< fusing <<< //│ = 13 //│ a = A @@ -399,23 +384,23 @@ fun test(x, y, z, i) = test(AA(1), AA(2), BB(3), 4) + test(AA(1), AA(2), BB(3), 4) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(3) -> -//│ deforest > match: z¹ -//│ deforest > fields: z¹.x² +//│ deforest > match: scrut²⁴ +//│ deforest > fields: scrut²⁴.x¹ //│ deforest > AA⁰(2) -> -//│ deforest > match: y² -//│ deforest > fields: y².x¹ +//│ deforest > match: scrut²⁵ +//│ deforest > fields: scrut²⁵.x⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: x¹⁴ -//│ deforest > fields: x¹⁴.x¹ +//│ deforest > match: scrut²⁶ +//│ deforest > fields: scrut²⁶.x⁰ //│ deforest > BB⁰(3) -> -//│ deforest > match: z¹ -//│ deforest > fields: z¹.x² +//│ deforest > match: scrut²⁴ +//│ deforest > fields: scrut²⁴.x¹ //│ deforest > AA⁰(2) -> -//│ deforest > match: y² -//│ deforest > fields: y².x¹ +//│ deforest > match: scrut²⁵ +//│ deforest > fields: scrut²⁵.x⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: x¹⁴ -//│ deforest > fields: x¹⁴.x¹ +//│ deforest > match: scrut²⁶ +//│ deforest > fields: scrut²⁶.x⁰ //│ deforest > <<< fusing <<< //│ = 18 @@ -434,23 +419,23 @@ fun test(x, y, z, i) = test(AA(1), AA(2), BB(3), 4) + test(AA(1), BB(2), BB(3), 4) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(3) -> -//│ deforest > match: z² -//│ deforest > fields: z².x² +//│ deforest > match: scrut²⁷ +//│ deforest > fields: scrut²⁷.x¹ //│ deforest > BB⁰(2) -> -//│ deforest > match: y³ -//│ deforest > fields: y³.x² +//│ deforest > match: scrut²⁸ +//│ deforest > fields: scrut²⁸.x¹ //│ deforest > AA⁰(1) -> -//│ deforest > match: x¹⁵ -//│ deforest > fields: x¹⁵.x¹ +//│ deforest > match: scrut²⁹ +//│ deforest > fields: scrut²⁹.x⁰ //│ deforest > BB⁰(3) -> -//│ deforest > match: z² -//│ deforest > fields: z².x² +//│ deforest > match: scrut²⁷ +//│ deforest > fields: scrut²⁷.x¹ //│ deforest > AA⁰(2) -> -//│ deforest > match: y³ -//│ deforest > fields: y³.x¹ +//│ deforest > match: scrut²⁸ +//│ deforest > fields: scrut²⁸.x⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: x¹⁵ -//│ deforest > fields: x¹⁵.x¹ +//│ deforest > match: scrut²⁹ +//│ deforest > fields: scrut²⁹.x⁰ //│ deforest > <<< fusing <<< //│ = 10 @@ -466,20 +451,20 @@ fun c(x) = if x is AA then 0 fun p(x) = AA(x) test(p(BB(3))) + test(p(CC(3))) + c(p(A)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x¹⁶) -> -//│ deforest > match: x¹⁷ -//│ deforest > AA⁰(x¹⁶) -> -//│ deforest > match: x¹⁸ -//│ deforest > fields: x¹⁸.x¹ +//│ deforest > AA⁰(x³) -> +//│ deforest > match: scrut³⁰ +//│ deforest > AA⁰(x³) -> +//│ deforest > match: scrut³¹ +//│ deforest > fields: scrut³¹.x⁰ //│ deforest > CC⁰(3) -> //│ deforest > match: arg$AA$0$⁴ -//│ deforest > fields: arg$AA$0$⁴.x¹⁹ -//│ deforest > AA⁰(x¹⁶) -> -//│ deforest > match: x¹⁸ -//│ deforest > fields: x¹⁸.x¹ +//│ deforest > fields: arg$AA$0$⁴.x⁴ +//│ deforest > AA⁰(x³) -> +//│ deforest > match: scrut³¹ +//│ deforest > fields: scrut³¹.x⁰ //│ deforest > BB⁰(3) -> //│ deforest > match: arg$AA$0$⁴ -//│ deforest > fields: arg$AA$0$⁴.x² +//│ deforest > fields: arg$AA$0$⁴.x¹ //│ deforest > <<< fusing <<< //│ = 12 @@ -501,43 +486,25 @@ fun f(x, y, z, k) = AA then m + k f(AA(3), 3, true, 10) + f(AA(5), 4, false, 20) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(tmp¹⁰) -> -//│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² -//│ deforest > BB⁰(tmp¹²) -> -//│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x¹ +//│ deforest > match: scrut³² +//│ deforest > fields: scrut³².x⁰ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut⁴ -//│ deforest > BB⁰(tmp¹⁰) -> -//│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² -//│ deforest > BB⁰(tmp¹²) -> -//│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² +//│ deforest > match: scrut³³ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x¹ +//│ deforest > match: scrut³² +//│ deforest > fields: scrut³².x⁰ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut⁴ +//│ deforest > match: scrut³³ //│ deforest > AA⁰(5) -> -//│ deforest > match: x²⁰ -//│ deforest > BB⁰(tmp¹⁰) -> -//│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² -//│ deforest > BB⁰(tmp¹²) -> -//│ deforest > match: tmp¹¹ -//│ deforest > fields: tmp¹¹.x² +//│ deforest > match: scrut³⁴ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x¹ +//│ deforest > match: scrut³² +//│ deforest > fields: scrut³².x⁰ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut⁴ +//│ deforest > match: scrut³³ //│ deforest > AA⁰(3) -> -//│ deforest > match: x²⁰ +//│ deforest > match: scrut³⁴ //│ deforest > <<< fusing <<< //│ = 41 @@ -573,13 +540,13 @@ fun test(a, b) = test(B, B) + test(A, A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: a⁴ +//│ deforest > match: scrut³⁵ //│ deforest > A⁰ -> -//│ deforest > match: b⁰ +//│ deforest > match: scrut³⁶ //│ deforest > B⁰ -> -//│ deforest > match: a⁴ +//│ deforest > match: scrut³⁵ //│ deforest > B⁰ -> -//│ deforest > match: b⁰ +//│ deforest > match: scrut³⁶ //│ deforest > <<< fusing <<< //│ > B //│ > 1 @@ -600,15 +567,15 @@ fun sum(ls) = if ls is sum(prodDouble(4)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁰ -//│ deforest > Cons⁰(n⁰, tmp¹³) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ -//│ deforest > Cons⁰(n⁰, tmp¹⁴) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut³⁷ +//│ deforest > Cons⁰(n⁰, tmp⁸) -> +//│ deforest > match: scrut³⁷ +//│ deforest > fields: scrut³⁷.h⁰ +//│ deforest > fields: scrut³⁷.t⁰ +//│ deforest > Cons⁰(n⁰, tmp⁹) -> +//│ deforest > match: scrut³⁷ +//│ deforest > fields: scrut³⁷.h⁰ +//│ deforest > fields: scrut³⁷.t⁰ //│ deforest > <<< fusing <<< //│ = 20 diff --git a/hkmc2/shared/src/test/mlscript/deforest/recursive.mls b/hkmc2/shared/src/test/mlscript/deforest/recursive.mls index c8595266f4..deef93c69d 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/recursive.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/recursive.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -24,11 +25,11 @@ let c = Cons(1, Nil) in map(x => x + 1, c) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, Nil⁰) -> -//│ deforest > match: ls_map⁰ -//│ deforest > fields: ls_map⁰.h⁰ -//│ deforest > fields: ls_map⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h⁰ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_map⁰ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = Cons(2, Nil) @@ -40,11 +41,11 @@ fun c(a) = Cons(a, Nil) map(x => x + 1, c(1)) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(a⁰, Nil⁰) -> -//│ deforest > match: ls_map¹ -//│ deforest > fields: ls_map¹.h⁰ -//│ deforest > fields: ls_map¹.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_map¹ +//│ deforest > match: scrut¹ //│ deforest > <<< fusing <<< //│ = Cons(2, Nil) @@ -56,15 +57,15 @@ fun map(ls, f) = if ls is map(1 :: 2 :: Nil, x => x + 1) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, tmp⁰) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².h⁰ +//│ deforest > fields: scrut².t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: ls⁰ -//│ deforest > fields: ls⁰.h⁰ -//│ deforest > fields: ls⁰.t⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².h⁰ +//│ deforest > fields: scrut².t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut² //│ deforest > <<< fusing <<< //│ = Cons(2, Cons(3, Nil)) @@ -76,11 +77,11 @@ fun enum(n) = if n > 0 then n :: enum(n - 1) else Nil sum(enum(3)) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(n⁰, tmp¹) -> -//│ deforest > match: ls¹ -//│ deforest > fields: ls¹.h⁰ -//│ deforest > fields: ls¹.t⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.h⁰ +//│ deforest > fields: scrut³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹ +//│ deforest > match: scrut³ //│ deforest > <<< fusing <<< //│ = 6 @@ -92,11 +93,11 @@ fun map(ls, f) = if ls is map(map(id(1 :: 2 :: Nil), x => x + 1), x => x * 2) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls² +//│ deforest > match: scrut⁴ //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: ls² -//│ deforest > fields: ls².h⁰ -//│ deforest > fields: ls².t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(4, Cons(6, Nil)) @@ -108,11 +109,11 @@ fun enum(a) = if a < 0 then Nil else a :: enum(a - 1) map(x => x + 1, map(x => x * 2, id(enum(3)))) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls³ +//│ deforest > match: scrut⁵ //│ deforest > Cons⁰(tmp⁴, tmp⁵) -> -//│ deforest > match: ls³ -//│ deforest > fields: ls³.h⁰ -//│ deforest > fields: ls³.t⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.h⁰ +//│ deforest > fields: scrut⁵.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(7, Cons(5, Cons(3, Cons(1, Nil)))) @@ -131,29 +132,29 @@ fun cons1(a1) = if a1 is cons(f(0)) + cons1(f(1)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: a1⁰ +//│ deforest > match: scrut⁶ //│ deforest > Cons⁰(x⁰, tmp⁶) -> -//│ deforest > match: a1⁰ -//│ deforest > fields: a1⁰.h⁰ -//│ deforest > fields: a1⁰.t⁰ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.h⁰ +//│ deforest > fields: scrut⁶.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: a1⁰ +//│ deforest > match: scrut⁶ //│ deforest > Cons⁰(y⁰, tmp⁷) -> -//│ deforest > match: a1⁰ -//│ deforest > fields: a1⁰.h⁰ -//│ deforest > fields: a1⁰.t⁰ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.h⁰ +//│ deforest > fields: scrut⁶.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: a¹ +//│ deforest > match: scrut⁷ //│ deforest > Cons⁰(x⁰, tmp⁶) -> -//│ deforest > match: a¹ -//│ deforest > fields: a¹.h⁰ -//│ deforest > fields: a¹.t⁰ +//│ deforest > match: scrut⁷ +//│ deforest > fields: scrut⁷.h⁰ +//│ deforest > fields: scrut⁷.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: a¹ +//│ deforest > match: scrut⁷ //│ deforest > Cons⁰(y⁰, tmp⁷) -> -//│ deforest > match: a¹ -//│ deforest > fields: a¹.h⁰ -//│ deforest > fields: a¹.t⁰ +//│ deforest > match: scrut⁷ +//│ deforest > fields: scrut⁷.h⁰ +//│ deforest > fields: scrut⁷.t⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -172,11 +173,11 @@ fun f1(ls1) = if ls1 is f1(to(4)) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(n¹, tmp⁸) -> -//│ deforest > match: ls1⁰ -//│ deforest > fields: ls1⁰.h⁰ -//│ deforest > fields: ls1⁰.t⁰ +//│ deforest > match: scrut⁸ +//│ deforest > fields: scrut⁸.h⁰ +//│ deforest > fields: scrut⁸.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls1⁰ +//│ deforest > match: scrut⁸ //│ deforest > <<< fusing <<< //│ = 12 @@ -189,21 +190,21 @@ fun double(x) = x * 2 map(map(1 :: 2 :: Nil, succ), double) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁴ +//│ deforest > match: scrut⁹ //│ deforest > Cons⁰(tmp⁹, tmp¹⁰) -> -//│ deforest > match: ls⁴ -//│ deforest > fields: ls⁴.h⁰ -//│ deforest > fields: ls⁴.t⁰ +//│ deforest > match: scrut⁹ +//│ deforest > fields: scrut⁹.h⁰ +//│ deforest > fields: scrut⁹.t⁰ //│ deforest > Cons⁰(1, tmp¹¹) -> -//│ deforest > match: ls⁴ -//│ deforest > fields: ls⁴.h⁰ -//│ deforest > fields: ls⁴.t⁰ +//│ deforest > match: scrut⁹ +//│ deforest > fields: scrut⁹.h⁰ +//│ deforest > fields: scrut⁹.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: ls⁴ -//│ deforest > fields: ls⁴.h⁰ -//│ deforest > fields: ls⁴.t⁰ +//│ deforest > match: scrut⁹ +//│ deforest > fields: scrut⁹.h⁰ +//│ deforest > fields: scrut⁹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁴ +//│ deforest > match: scrut⁹ //│ deforest > <<< fusing <<< //│ = Cons(4, Cons(6, Nil)) @@ -219,17 +220,17 @@ fun wrap(n) = to(n) f1(wrap(4)) + f2(wrap(5)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁵ +//│ deforest > match: scrut¹⁰ //│ deforest > Cons⁰(n², tmp¹²) -> -//│ deforest > match: ls⁵ -//│ deforest > fields: ls⁵.h⁰ -//│ deforest > fields: ls⁵.t⁰ +//│ deforest > match: scrut¹⁰ +//│ deforest > fields: scrut¹⁰.h⁰ +//│ deforest > fields: scrut¹⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁶ +//│ deforest > match: scrut¹¹ //│ deforest > Cons⁰(n², tmp¹²) -> -//│ deforest > match: ls⁶ -//│ deforest > fields: ls⁶.h⁰ -//│ deforest > fields: ls⁶.t⁰ +//│ deforest > match: scrut¹¹ +//│ deforest > fields: scrut¹¹.h⁰ +//│ deforest > fields: scrut¹¹.t⁰ //│ deforest > <<< fusing <<< //│ = 10 @@ -244,23 +245,23 @@ fun f2(ls, acc) = if ls is f1(to(4, Nil), 0) + f2(to(5, Nil), 0) + f1(to(6, Nil), 0) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁷ +//│ deforest > match: scrut¹² //│ deforest > Cons⁰(n³, acc⁰) -> -//│ deforest > match: ls⁷ -//│ deforest > fields: ls⁷.h⁰ -//│ deforest > fields: ls⁷.t⁰ +//│ deforest > match: scrut¹² +//│ deforest > fields: scrut¹².h⁰ +//│ deforest > fields: scrut¹².t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁸ +//│ deforest > match: scrut¹³ //│ deforest > Cons⁰(n³, acc⁰) -> -//│ deforest > match: ls⁸ -//│ deforest > fields: ls⁸.h⁰ -//│ deforest > fields: ls⁸.t⁰ +//│ deforest > match: scrut¹³ +//│ deforest > fields: scrut¹³.h⁰ +//│ deforest > fields: scrut¹³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁷ +//│ deforest > match: scrut¹² //│ deforest > Cons⁰(n³, acc⁰) -> -//│ deforest > match: ls⁷ -//│ deforest > fields: ls⁷.h⁰ -//│ deforest > fields: ls⁷.t⁰ +//│ deforest > match: scrut¹² +//│ deforest > fields: scrut¹².h⁰ +//│ deforest > fields: scrut¹².t⁰ //│ deforest > <<< fusing <<< //│ = 51 @@ -274,27 +275,27 @@ fun triple(x) = x * 3 map(map(map(1 :: 2 :: Nil, succ), double), triple) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁹ +//│ deforest > match: scrut¹⁴ //│ deforest > Cons⁰(tmp¹³, tmp¹⁴) -> -//│ deforest > match: ls⁹ -//│ deforest > fields: ls⁹.h⁰ -//│ deforest > fields: ls⁹.t⁰ +//│ deforest > match: scrut¹⁴ +//│ deforest > fields: scrut¹⁴.h⁰ +//│ deforest > fields: scrut¹⁴.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁹ +//│ deforest > match: scrut¹⁴ //│ deforest > Cons⁰(tmp¹³, tmp¹⁴) -> -//│ deforest > match: ls⁹ -//│ deforest > fields: ls⁹.h⁰ -//│ deforest > fields: ls⁹.t⁰ +//│ deforest > match: scrut¹⁴ +//│ deforest > fields: scrut¹⁴.h⁰ +//│ deforest > fields: scrut¹⁴.t⁰ //│ deforest > Cons⁰(1, tmp¹⁵) -> -//│ deforest > match: ls⁹ -//│ deforest > fields: ls⁹.h⁰ -//│ deforest > fields: ls⁹.t⁰ +//│ deforest > match: scrut¹⁴ +//│ deforest > fields: scrut¹⁴.h⁰ +//│ deforest > fields: scrut¹⁴.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: ls⁹ -//│ deforest > fields: ls⁹.h⁰ -//│ deforest > fields: ls⁹.t⁰ +//│ deforest > match: scrut¹⁴ +//│ deforest > fields: scrut¹⁴.h⁰ +//│ deforest > fields: scrut¹⁴.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls⁹ +//│ deforest > match: scrut¹⁴ //│ deforest > <<< fusing <<< //│ = Cons(12, Cons(18, Nil)) @@ -314,11 +315,11 @@ fun double(x) = x * 2 map1(incr, map(double, id(mk(5)))) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(tmp¹⁶, tmp¹⁷) -> -//│ deforest > match: ls_map1⁰ -//│ deforest > fields: ls_map1⁰.h⁰ -//│ deforest > fields: ls_map1⁰.t⁰ +//│ deforest > match: scrut¹⁵ +//│ deforest > fields: scrut¹⁵.h⁰ +//│ deforest > fields: scrut¹⁵.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_map1⁰ +//│ deforest > match: scrut¹⁵ //│ deforest > <<< fusing <<< //│ = Cons(11, Cons(9, Cons(7, Cons(5, Cons(3, Cons(1, Nil)))))) @@ -331,12 +332,12 @@ fun map(ls) = if ls is h :: t then (h + 4) :: map(t) map(enumFromTo(1, 4)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a², tmp¹⁸) -> -//│ deforest > match: ls¹⁰ -//│ deforest > fields: ls¹⁰.h⁰ -//│ deforest > fields: ls¹⁰.t⁰ +//│ deforest > Cons⁰(a¹, tmp¹⁸) -> +//│ deforest > match: scrut¹⁶ +//│ deforest > fields: scrut¹⁶.h⁰ +//│ deforest > fields: scrut¹⁶.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹⁰ +//│ deforest > match: scrut¹⁶ //│ deforest > <<< fusing <<< //│ = Cons(5, Cons(6, Cons(7, Nil))) @@ -347,12 +348,12 @@ fun sum(ls) = if ls is h :: t then h + sum(t) sum(enumFromTo(1,10)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a³, tmp¹⁹) -> -//│ deforest > match: ls¹¹ -//│ deforest > fields: ls¹¹.h⁰ -//│ deforest > fields: ls¹¹.t⁰ +//│ deforest > Cons⁰(a², tmp¹⁹) -> +//│ deforest > match: scrut¹⁷ +//│ deforest > fields: scrut¹⁷.h⁰ +//│ deforest > fields: scrut¹⁷.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹¹ +//│ deforest > match: scrut¹⁷ //│ deforest > <<< fusing <<< //│ = 45 @@ -364,12 +365,12 @@ fun map(f, ls) = h :: t then f(h) :: map(f, t) map(x => x + 4, enumFromTo(1, 4)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a⁴, tmp²⁰) -> -//│ deforest > match: ls¹² -//│ deforest > fields: ls¹².h⁰ -//│ deforest > fields: ls¹².t⁰ +//│ deforest > Cons⁰(a³, tmp²⁰) -> +//│ deforest > match: scrut¹⁸ +//│ deforest > fields: scrut¹⁸.h⁰ +//│ deforest > fields: scrut¹⁸.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹² +//│ deforest > match: scrut¹⁸ //│ deforest > <<< fusing <<< //│ = Cons(5, Cons(6, Cons(7, Nil))) @@ -382,12 +383,12 @@ fun map(f, ls) = )(f) map(x => x + 4, enumFromTo(1, 4)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a⁵, tmp²¹) -> -//│ deforest > match: ls¹³ -//│ deforest > fields: ls¹³.h⁰ -//│ deforest > fields: ls¹³.t⁰ +//│ deforest > Cons⁰(a⁴, tmp²¹) -> +//│ deforest > match: scrut¹⁹ +//│ deforest > fields: scrut¹⁹.h⁰ +//│ deforest > fields: scrut¹⁹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹³ +//│ deforest > match: scrut¹⁹ //│ deforest > <<< fusing <<< //│ = Cons(5, Cons(6, Cons(7, Nil))) @@ -400,12 +401,12 @@ fun sum(ls, a) = if ls is h :: t then sum(t, h + a) sum(enumFromTo(1, 10), 0) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a⁶, tmp²²) -> -//│ deforest > match: ls¹⁴ -//│ deforest > fields: ls¹⁴.h⁰ -//│ deforest > fields: ls¹⁴.t⁰ +//│ deforest > Cons⁰(a⁵, tmp²²) -> +//│ deforest > match: scrut²⁰ +//│ deforest > fields: scrut²⁰.h⁰ +//│ deforest > fields: scrut²⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls¹⁴ +//│ deforest > match: scrut²⁰ //│ deforest > <<< fusing <<< //│ = 45 @@ -423,11 +424,11 @@ fun rev(xs_rev, acc) = if xs_rev is map(incr, rev(id(mk(5)), Nil)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: xs_map⁰ +//│ deforest > match: scrut²¹ //│ deforest > Cons⁰(x¹, acc¹) -> -//│ deforest > match: xs_map⁰ -//│ deforest > fields: xs_map⁰.h⁰ -//│ deforest > fields: xs_map⁰.t⁰ +//│ deforest > match: scrut²¹ +//│ deforest > fields: scrut²¹.h⁰ +//│ deforest > fields: scrut²¹.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) @@ -445,11 +446,11 @@ fun rev(xs_rev, acc) = if xs_rev is rev(map(incr, id(mk(3))), Nil) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: xs_rev⁰ +//│ deforest > match: scrut²² //│ deforest > Cons⁰(tmp²³, tmp²⁴) -> -//│ deforest > match: xs_rev⁰ -//│ deforest > fields: xs_rev⁰.h⁰ -//│ deforest > fields: xs_rev⁰.t⁰ +//│ deforest > match: scrut²² +//│ deforest > fields: scrut²².h⁰ +//│ deforest > fields: scrut²².t⁰ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(2, Cons(3, Cons(4, Nil)))) @@ -499,11 +500,11 @@ fun flatten(ls_flatten) = if ls_flatten is sum(flatten(id(mk2d(4)))) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls_sum⁰ +//│ deforest > match: scrut²³ //│ deforest > Cons⁰(x², tmp²⁵) -> -//│ deforest > match: ls_sum⁰ -//│ deforest > fields: ls_sum⁰.h⁰ -//│ deforest > fields: ls_sum⁰.t⁰ +//│ deforest > match: scrut²³ +//│ deforest > fields: scrut²³.h⁰ +//│ deforest > fields: scrut²³.t⁰ //│ deforest > <<< fusing <<< //│ = 20 @@ -518,21 +519,21 @@ fun rev(a, ys) = if ys is count(0, rev(Nil, 1 :: 2 :: Nil)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: xs⁰ -//│ deforest > Cons⁰(h¹, a⁷) -> -//│ deforest > match: xs⁰ -//│ deforest > fields: xs⁰.h⁰ -//│ deforest > fields: xs⁰.t⁰ +//│ deforest > match: scrut²⁴ +//│ deforest > Cons⁰(h¹, a⁶) -> +//│ deforest > match: scrut²⁴ +//│ deforest > fields: scrut²⁴.h⁰ +//│ deforest > fields: scrut²⁴.t⁰ //│ deforest > Cons⁰(1, tmp²⁶) -> -//│ deforest > match: ys⁰ -//│ deforest > fields: ys⁰.h⁰ -//│ deforest > fields: ys⁰.t⁰ +//│ deforest > match: scrut²⁵ +//│ deforest > fields: scrut²⁵.h⁰ +//│ deforest > fields: scrut²⁵.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: ys⁰ -//│ deforest > fields: ys⁰.h⁰ -//│ deforest > fields: ys⁰.t⁰ +//│ deforest > match: scrut²⁵ +//│ deforest > fields: scrut²⁵.h⁰ +//│ deforest > fields: scrut²⁵.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys⁰ +//│ deforest > match: scrut²⁵ //│ deforest > <<< fusing <<< //│ = 2 @@ -559,19 +560,19 @@ fun map(f, xs_map) = if xs_map is map(x => if x is A then 1 else 0, A :: B :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(A⁰, tmp²⁷) -> -//│ deforest > match: xs_map¹ -//│ deforest > fields: xs_map¹.h⁰ -//│ deforest > fields: xs_map¹.t⁰ +//│ deforest > match: scrut²⁶ +//│ deforest > fields: scrut²⁶.h⁰ +//│ deforest > fields: scrut²⁶.t⁰ //│ deforest > A⁰ -> -//│ deforest > match: x³ +//│ deforest > match: scrut²⁷ //│ deforest > Cons⁰(B⁰, Nil⁰) -> -//│ deforest > match: xs_map¹ -//│ deforest > fields: xs_map¹.h⁰ -//│ deforest > fields: xs_map¹.t⁰ +//│ deforest > match: scrut²⁶ +//│ deforest > fields: scrut²⁶.h⁰ +//│ deforest > fields: scrut²⁶.t⁰ //│ deforest > B⁰ -> -//│ deforest > match: x³ +//│ deforest > match: scrut²⁷ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_map¹ +//│ deforest > match: scrut²⁶ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(0, Nil)) @@ -588,34 +589,34 @@ fun c(x) = if x is c(T(A, T(B, L, L), T(A, L, L))) //│ deforest > >>> fusing >>> //│ deforest > T⁰(A⁰, tmp²⁸, tmp²⁹) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.n⁴ -//│ deforest > fields: x⁴.l⁰ -//│ deforest > fields: x⁴.r⁰ +//│ deforest > match: scrut²⁸ +//│ deforest > fields: scrut²⁸.n⁴ +//│ deforest > fields: scrut²⁸.l⁰ +//│ deforest > fields: scrut²⁸.r⁰ //│ deforest > A⁰ -> -//│ deforest > match: n⁵ +//│ deforest > match: scrut²⁹ //│ deforest > T⁰(A⁰, L⁰, L⁰) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.n⁴ -//│ deforest > fields: x⁴.l⁰ -//│ deforest > fields: x⁴.r⁰ +//│ deforest > match: scrut²⁸ +//│ deforest > fields: scrut²⁸.n⁴ +//│ deforest > fields: scrut²⁸.l⁰ +//│ deforest > fields: scrut²⁸.r⁰ //│ deforest > A⁰ -> -//│ deforest > match: n⁵ +//│ deforest > match: scrut²⁹ //│ deforest > L⁰ -> -//│ deforest > match: x⁴ +//│ deforest > match: scrut²⁸ //│ deforest > L⁰ -> -//│ deforest > match: x⁴ +//│ deforest > match: scrut²⁸ //│ deforest > T⁰(B⁰, L⁰, L⁰) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.n⁴ -//│ deforest > fields: x⁴.l⁰ -//│ deforest > fields: x⁴.r⁰ +//│ deforest > match: scrut²⁸ +//│ deforest > fields: scrut²⁸.n⁴ +//│ deforest > fields: scrut²⁸.l⁰ +//│ deforest > fields: scrut²⁸.r⁰ //│ deforest > B⁰ -> -//│ deforest > match: n⁵ +//│ deforest > match: scrut²⁹ //│ deforest > L⁰ -> -//│ deforest > match: x⁴ +//│ deforest > match: scrut²⁸ //│ deforest > L⁰ -> -//│ deforest > match: x⁴ +//│ deforest > match: scrut²⁸ //│ deforest > <<< fusing <<< //│ = T(0, T(1, L, L), T(0, L, L)) @@ -629,27 +630,27 @@ fun max(ms, m) = if ms is max(3 :: 2 :: 4 :: 1 :: 0 :: Nil, 0) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(3, tmp³⁰) -> -//│ deforest > match: ms⁰ -//│ deforest > fields: ms⁰.h⁰ -//│ deforest > fields: ms⁰.t⁰ +//│ deforest > match: scrut³⁰ +//│ deforest > fields: scrut³⁰.h⁰ +//│ deforest > fields: scrut³⁰.t⁰ //│ deforest > Cons⁰(2, tmp³¹) -> -//│ deforest > match: ms⁰ -//│ deforest > fields: ms⁰.h⁰ -//│ deforest > fields: ms⁰.t⁰ +//│ deforest > match: scrut³⁰ +//│ deforest > fields: scrut³⁰.h⁰ +//│ deforest > fields: scrut³⁰.t⁰ //│ deforest > Cons⁰(4, tmp³²) -> -//│ deforest > match: ms⁰ -//│ deforest > fields: ms⁰.h⁰ -//│ deforest > fields: ms⁰.t⁰ +//│ deforest > match: scrut³⁰ +//│ deforest > fields: scrut³⁰.h⁰ +//│ deforest > fields: scrut³⁰.t⁰ //│ deforest > Cons⁰(1, tmp³³) -> -//│ deforest > match: ms⁰ -//│ deforest > fields: ms⁰.h⁰ -//│ deforest > fields: ms⁰.t⁰ +//│ deforest > match: scrut³⁰ +//│ deforest > fields: scrut³⁰.h⁰ +//│ deforest > fields: scrut³⁰.t⁰ //│ deforest > Cons⁰(0, Nil⁰) -> -//│ deforest > match: ms⁰ -//│ deforest > fields: ms⁰.h⁰ -//│ deforest > fields: ms⁰.t⁰ +//│ deforest > match: scrut³⁰ +//│ deforest > fields: scrut³⁰.h⁰ +//│ deforest > fields: scrut³⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ms⁰ +//│ deforest > match: scrut³⁰ //│ deforest > <<< fusing <<< //│ = 4 @@ -742,19 +743,19 @@ fun g2() = f() f //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: B⁰ +//│ deforest > match: scrut³¹ //│ deforest > B⁰ -> -//│ deforest > match: B⁰ +//│ deforest > match: scrut³¹ //│ deforest > B⁰ -> -//│ deforest > match: B⁰ +//│ deforest > match: scrut³¹ //│ deforest > B⁰ -> -//│ deforest > match: B⁰ +//│ deforest > match: scrut³¹ //│ deforest > B⁰ -> -//│ deforest > match: B⁰ +//│ deforest > match: scrut³¹ //│ deforest > B⁰ -> -//│ deforest > match: B⁰ +//│ deforest > match: scrut³¹ //│ deforest > <<< fusing <<< -//│ = fun f_9$f +//│ = fun f_10$f :deforest @@ -799,65 +800,65 @@ fun f() = f //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: x⁵ +//│ deforest > match: scrut³² //│ deforest > <<< fusing <<< //│ = fun f_18$f @@ -873,21 +874,21 @@ fun f() = f //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut³³ //│ deforest > B⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut³³ //│ deforest > B⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut³³ //│ deforest > B⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut³³ //│ deforest > B⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut³³ //│ deforest > B⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut³³ //│ deforest > B⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut³³ //│ deforest > B⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut³³ //│ deforest > <<< fusing <<< //│ = fun f_10$f @@ -904,24 +905,24 @@ fun f() = f //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > B⁰ -> -//│ deforest > match: x⁷ +//│ deforest > match: scrut³⁴ //│ deforest > <<< fusing <<< //│ = fun f_14$f diff --git a/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls b/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls index 3c5c9ec64a..b0e67c4186 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls @@ -1,3 +1,4 @@ +:. :js @@ -44,7 +45,7 @@ store.foo(AA(4)) //│ deforest > fields: scrut¹.x¹ //│ deforest > <<< fusing <<< //│ ═══[INTERNAL ERROR] `this` not in scope: class:Store -//│ > let AA3, Store3, store1, tmp2, tmp3, $scrut_AA;try { const n$accessorSymbol = globalThis.Symbol("n"); $scrut_AA = function $scrut_AA(AA_x) { runtime.checkArgs("$scrut_AA", 1, true, arguments.length); return (fv_x, fv_arg$AA$0$) => { return AA_x + ‹MISSING_THIS›[n$accessorSymbol] } }; AA3 = function AA(x) { return globalThis.Object.freeze(new AA.class(x)); }; globalThis.Object.freeze(class AA2 { static { AA3.class = this } constructor(x) { this.x = x; } toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "AA", ["x"]]; }); Store3 = function Store(n) { return globalThis.Object.freeze(new Store.class(n)); }; globalThis.Object.freeze(class Store2 { static { Store3.class = this } constructor(n) { this.#n = n; } #n; get [n$accessorSymbol]() { return this.#n; } set [n$accessorSymbol](value) { this.#n = value; } get get$__checkNotMethod() { runtime.deboundMethod("get", "Store"); } get foo$__checkNotMethod() { runtime.deboundMethod("foo", "Store"); } get() { runtime.checkArgs("get", 0, true, arguments.length); let AA_x; AA_x = this.#n; return runtime.checkCall($scrut_AA(AA_x)(undefined, undefined)) } foo(x) { runtime.checkArgs("foo", 1, true, arguments.length); let arg$AA$0$; if (x instanceof AA3.class) { arg$AA$0$ = x.x; return arg$AA$0$ + this.#n } throw globalThis.Object.freeze(new globalThis.Error("match error")); } toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "Store", [null]]; }); tmp2 = runtime.checkCall(Store3(1)); store1 = tmp2; tmp3 = runtime.checkCall(AA3(4)); block$res2 = runtime.safeCall(store1.foo(tmp3));; undefined } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > let AA3, Store3, store1, tmp2, tmp3, $scrut_AA;try { const n$accessorSymbol = globalThis.Symbol("n"); $scrut_AA = function $scrut_AA(AA_x) { runtime.checkArgs("$scrut_AA", 1, true, arguments.length); return (fv_x, fv_arg$AA$0$) => { return AA_x + ‹MISSING_THIS›[n$accessorSymbol] } }; AA3 = function AA(x) { return globalThis.Object.freeze(new AA.class(x)); }; globalThis.Object.freeze(class AA2 { static { AA3.class = this } constructor(x) { this.x = x; } toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "AA", ["x"]]; }); Store3 = function Store(n) { return globalThis.Object.freeze(new Store.class(n)); }; globalThis.Object.freeze(class Store2 { static { Store3.class = this } constructor(n) { this.#n = n; } #n; get [n$accessorSymbol]() { return this.#n; } set [n$accessorSymbol](value) { this.#n = value; } get get$__checkNotMethod() { runtime.deboundMethod("get", "Store"); } get foo$__checkNotMethod() { runtime.deboundMethod("foo", "Store"); } get() { runtime.checkArgs("get", 0, true, arguments.length); let AA_x; AA_x = this.#n; return runtime.checkCall($scrut_AA(AA_x)(undefined, undefined)) } foo(x) { runtime.checkArgs("foo", 1, true, arguments.length); let arg$AA$0$; if (x instanceof AA3.class) { arg$AA$0$ = x.x; return arg$AA$0$ + this.#n } throw globalThis.Object.freeze(new globalThis.Error("match error")); } toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "Store", [null]]; }); tmp2 = runtime.checkCall(Store3(1)); store1 = tmp2; tmp3 = runtime.checkCall(AA3(4)); block$res2 = runtime.checkCall(store1.foo(tmp3));; undefined } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Invalid or unexpected token //│ ═══[RUNTIME ERROR] Expected: '5', got: 'undefined' //│ ═══[RUNTIME ERROR] ReferenceError: store1 is not defined diff --git a/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls b/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls index 8a47163fbc..e575cb31ac 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -30,12 +31,9 @@ fun c(x) = if x is c(AA(2)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut⁰ //│ deforest > A⁰ -> -//│ deforest > match: A⁰ -//│ deforest > AA⁰(2) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.x¹ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -48,10 +46,7 @@ fun c(x, y) = if x is c(AA(2), A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: y⁰ -//│ deforest > AA⁰(2) -> -//│ deforest > match: x² -//│ deforest > fields: x².x¹ +//│ deforest > match: scrut¹ //│ deforest > <<< fusing <<< //│ = 2 @@ -66,10 +61,7 @@ fun p() = AA(2) c(p(), A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: y¹ -//│ deforest > AA⁰(2) -> -//│ deforest > match: x³ -//│ deforest > fields: x³.x¹ +//│ deforest > match: scrut² //│ deforest > <<< fusing <<< //│ = 2 @@ -86,10 +78,7 @@ fun p() = AA(2) c(p(), A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: y² -//│ deforest > AA⁰(2) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.x¹ +//│ deforest > match: scrut³ //│ deforest > <<< fusing <<< //│ = 3 @@ -105,22 +94,22 @@ fun g(x) = if x is f(AA(AA(AA(AA(AA(A)))))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: x⁵ -//│ deforest > fields: x⁵.x¹ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.x⁰ //│ deforest > AA⁰(tmp¹) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x¹ +//│ deforest > fields: arg$AA$0$⁰.x⁰ //│ deforest > AA⁰(tmp²) -> -//│ deforest > match: x⁶ -//│ deforest > fields: x⁶.x¹ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.x⁰ //│ deforest > AA⁰(tmp³) -> -//│ deforest > match: x⁵ -//│ deforest > fields: x⁵.x¹ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.x⁰ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x¹ +//│ deforest > fields: arg$AA$0$⁰.x⁰ //│ deforest > A⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut⁵ //│ deforest > <<< fusing <<< //│ = 42 @@ -130,11 +119,11 @@ fun f(x) = if x is f(AA(AA(A))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁴) -> -//│ deforest > match: x⁷ -//│ deforest > fields: x⁷.x¹ +//│ deforest > match: scrut⁶ +//│ deforest > fields: scrut⁶.x⁰ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$¹ -//│ deforest > fields: arg$AA$0$¹.x¹ +//│ deforest > fields: arg$AA$0$¹.x⁰ //│ deforest > <<< fusing <<< //│ = A @@ -145,11 +134,11 @@ fun p() = AA(AA(A)) f(p()) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁵) -> -//│ deforest > match: x⁸ -//│ deforest > fields: x⁸.x¹ +//│ deforest > match: scrut⁷ +//│ deforest > fields: scrut⁷.x⁰ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$² -//│ deforest > fields: arg$AA$0$².x¹ +//│ deforest > fields: arg$AA$0$².x⁰ //│ deforest > <<< fusing <<< //│ = A @@ -165,11 +154,8 @@ fun c(x, y) = c(AA(2), 10) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut⁸ //│ deforest > A⁰ -> -//│ deforest > match: A⁰ -//│ deforest > AA⁰(2) -> -//│ deforest > match: x⁹ -//│ deforest > fields: x⁹.x¹ +//│ deforest > match: scrut⁸ //│ deforest > <<< fusing <<< //│ = 12 diff --git a/hkmc2/shared/src/test/mlscript/deforest/simple.mls b/hkmc2/shared/src/test/mlscript/deforest/simple.mls index 86036ad615..c6411df5a6 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/simple.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/simple.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -32,8 +33,8 @@ fun test(x) = test(AA(AA(10))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.aa⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.aa⁰ //│ deforest > AA⁰(10) -> //│ deforest > match: arg$AA$0$⁰ //│ deforest > fields: arg$AA$0$⁰.aa⁰ @@ -49,9 +50,9 @@ if x is B then 2 //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: x¹ +//│ deforest > match: scrut¹ //│ deforest > A⁰ -> -//│ deforest > match: x¹ +//│ deforest > match: scrut¹ //│ deforest > <<< fusing <<< //│ = 1 @@ -62,11 +63,11 @@ if x is BB(x) then x //│ deforest > >>> fusing >>> //│ deforest > BB⁰(B⁰) -> -//│ deforest > match: x² -//│ deforest > fields: x².bb⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².bb⁰ //│ deforest > AA⁰(A⁰) -> -//│ deforest > match: x² -//│ deforest > fields: x².aa⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².aa⁰ //│ deforest > <<< fusing <<< //│ = A @@ -81,15 +82,15 @@ if x is BB(x) then f(x) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(B⁰) -> -//│ deforest > match: x³ -//│ deforest > fields: x³.bb⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.bb⁰ //│ deforest > B⁰ -> -//│ deforest > match: a⁰ +//│ deforest > match: scrut⁴ //│ deforest > AA⁰(A⁰) -> -//│ deforest > match: x³ -//│ deforest > fields: x³.aa⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.aa⁰ //│ deforest > A⁰ -> -//│ deforest > match: a⁰ +//│ deforest > match: scrut⁴ //│ deforest > <<< fusing <<< //│ = 1 @@ -109,16 +110,6 @@ if x is AA then f1(x.AA#aa) BB then f2(x.BB#bb) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(B⁰) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.bb⁰ -//│ deforest > B⁰ -> -//│ deforest > match: a¹ -//│ deforest > AA⁰(A⁰) -> -//│ deforest > match: x⁴ -//│ deforest > fields: x⁴.aa⁰ -//│ deforest > A⁰ -> -//│ deforest > match: a² //│ deforest > <<< fusing <<< //│ = 1 @@ -131,11 +122,11 @@ fun c(x) = if x is c(g(true)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(11) -> -//│ deforest > match: x⁵ -//│ deforest > fields: x⁵.aa⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.aa⁰ //│ deforest > BB⁰(22) -> -//│ deforest > match: x⁵ -//│ deforest > fields: x⁵.bb⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.bb⁰ //│ deforest > <<< fusing <<< //│ = 11 @@ -166,9 +157,9 @@ if y is B then 2 //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: y⁰ +//│ deforest > match: scrut⁶ //│ deforest > A⁰ -> -//│ deforest > match: x⁶ +//│ deforest > match: scrut⁷ //│ deforest > <<< fusing <<< //│ = 2 @@ -183,9 +174,9 @@ fun c(a) = c(A) + c(B) //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: a³ +//│ deforest > match: scrut⁸ //│ deforest > A⁰ -> -//│ deforest > match: a³ +//│ deforest > match: scrut⁸ //│ deforest > <<< fusing <<< //│ > 1 //│ > 2 @@ -214,9 +205,9 @@ fun f(a, b) = if a is f(A, B) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: a⁴ +//│ deforest > match: scrut⁹ //│ deforest > B⁰ -> -//│ deforest > match: b⁰ +//│ deforest > match: scrut¹⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -226,14 +217,6 @@ fun f(x) = if x is Some then if x.Some#value > 1 then f(Some(x.Some#value - 1)) else 0 f(Some(2)) //│ deforest > >>> fusing >>> -//│ deforest > Some⁰(tmp¹) -> -//│ deforest > match: x⁷ -//│ deforest > fields: x⁷.value⁰ -//│ deforest > fields: x⁷.value⁰ -//│ deforest > Some⁰(2) -> -//│ deforest > match: x⁷ -//│ deforest > fields: x⁷.value⁰ -//│ deforest > fields: x⁷.value⁰ //│ deforest > <<< fusing <<< //│ = 0 @@ -248,9 +231,9 @@ if y is B then 2 //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: y¹ +//│ deforest > match: scrut¹¹ //│ deforest > A⁰ -> -//│ deforest > match: x⁸ +//│ deforest > match: scrut¹² //│ deforest > <<< fusing <<< //│ = 2 @@ -265,21 +248,21 @@ fun f(x, y) = f(AAA(1, 3), 1) + f(BBB(2, 3), 2) + f(AAA(3, 2), 4) + f(BBB(4, 6), 0) //│ deforest > >>> fusing >>> //│ deforest > BBB⁰(4, 6) -> -//│ deforest > match: x⁹ -//│ deforest > fields: x⁹.x¹⁰ -//│ deforest > fields: x⁹.y² +//│ deforest > match: scrut¹³ +//│ deforest > fields: scrut¹³.x⁰ +//│ deforest > fields: scrut¹³.y⁰ //│ deforest > AAA⁰(3, 2) -> -//│ deforest > match: x⁹ -//│ deforest > fields: x⁹.x¹¹ -//│ deforest > fields: x⁹.y³ +//│ deforest > match: scrut¹³ +//│ deforest > fields: scrut¹³.x¹ +//│ deforest > fields: scrut¹³.y¹ //│ deforest > BBB⁰(2, 3) -> -//│ deforest > match: x⁹ -//│ deforest > fields: x⁹.x¹⁰ -//│ deforest > fields: x⁹.y² +//│ deforest > match: scrut¹³ +//│ deforest > fields: scrut¹³.x⁰ +//│ deforest > fields: scrut¹³.y⁰ //│ deforest > AAA⁰(1, 3) -> -//│ deforest > match: x⁹ -//│ deforest > fields: x⁹.x¹¹ -//│ deforest > fields: x⁹.y³ +//│ deforest > match: scrut¹³ +//│ deforest > fields: scrut¹³.x¹ +//│ deforest > fields: scrut¹³.y¹ //│ deforest > <<< fusing <<< //│ = 21 @@ -295,11 +278,11 @@ fun p(a) = c1(a) + c2(a) p(AA(1)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut¹⁴ //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut¹⁴ //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut¹⁴ //│ deforest > <<< fusing <<< //│ = 2 @@ -313,10 +296,10 @@ fun c(x, y) = if x is c(AA(2), A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: y⁴ +//│ deforest > match: scrut¹⁵ //│ deforest > AA⁰(2) -> -//│ deforest > match: x¹² -//│ deforest > fields: x¹².aa⁰ +//│ deforest > match: scrut¹⁶ +//│ deforest > fields: scrut¹⁶.aa⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -330,19 +313,19 @@ fun f(x, y) = f(AAA(1, 3), 1) + f(CCC(2), 2) + f(AAA(3, 2), 4) + f(CCC(4), 0) //│ deforest > >>> fusing >>> //│ deforest > CCC⁰(4) -> -//│ deforest > match: x¹³ -//│ deforest > fields: x¹³.c⁰ +//│ deforest > match: scrut¹⁷ +//│ deforest > fields: scrut¹⁷.c⁰ //│ deforest > AAA⁰(3, 2) -> -//│ deforest > match: x¹³ -//│ deforest > fields: x¹³.x¹¹ -//│ deforest > fields: x¹³.y³ +//│ deforest > match: scrut¹⁷ +//│ deforest > fields: scrut¹⁷.x¹ +//│ deforest > fields: scrut¹⁷.y¹ //│ deforest > CCC⁰(2) -> -//│ deforest > match: x¹³ -//│ deforest > fields: x¹³.c⁰ +//│ deforest > match: scrut¹⁷ +//│ deforest > fields: scrut¹⁷.c⁰ //│ deforest > AAA⁰(1, 3) -> -//│ deforest > match: x¹³ -//│ deforest > fields: x¹³.x¹¹ -//│ deforest > fields: x¹³.y³ +//│ deforest > match: scrut¹⁷ +//│ deforest > fields: scrut¹⁷.x¹ +//│ deforest > fields: scrut¹⁷.y¹ //│ deforest > <<< fusing <<< //│ = 24 @@ -357,15 +340,6 @@ fun f(x, y) = a + 3 f(AAA(1, 3), 1) + f(CCC(2), 2) + f(AAA(3, 2), 4) //│ deforest > >>> fusing >>> -//│ deforest > AAA⁰(3, 2) -> -//│ deforest > match: x¹⁴ -//│ deforest > fields: x¹⁴.y³ -//│ deforest > CCC⁰(2) -> -//│ deforest > match: x¹⁴ -//│ deforest > fields: x¹⁴.c⁰ -//│ deforest > AAA⁰(1, 3) -> -//│ deforest > match: x¹⁴ -//│ deforest > fields: x¹⁴.y³ //│ deforest > <<< fusing <<< //│ = 22 @@ -377,7 +351,7 @@ fun c(x, m) = if x is c(BB(3), 0) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(3) -> -//│ deforest > match: x¹⁵ +//│ deforest > match: scrut¹⁸ //│ deforest > <<< fusing <<< //│ = 0 @@ -391,12 +365,12 @@ fun g(b) = if b is AA(a) then a + 1 f(AA(AA(0))) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(tmp²) -> -//│ deforest > match: x¹⁶ -//│ deforest > fields: x¹⁶.aa⁰ +//│ deforest > AA⁰(tmp¹) -> +//│ deforest > match: scrut¹⁹ +//│ deforest > fields: scrut¹⁹.aa⁰ //│ deforest > AA⁰(0) -> -//│ deforest > match: b¹ -//│ deforest > fields: b¹.aa⁰ +//│ deforest > match: scrut²⁰ +//│ deforest > fields: scrut²⁰.aa⁰ //│ deforest > <<< fusing <<< //│ = 1 @@ -406,7 +380,7 @@ f(AA(AA(0))) fun f(x) = x.AA#aa.AA#aa f(AA(AA(3))) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(tmp³) -> +//│ deforest > AA⁰(tmp²) -> //│ deforest > select: aa //│ deforest > AA⁰(3) -> //│ deforest > select: aa @@ -433,8 +407,8 @@ let p = AA(AA(3)) outer1(p, 1, 2) + outer2(p, 3, 4) + inner(AA(5), 6) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(5) -> -//│ deforest > match: x¹⁷ -//│ deforest > fields: x¹⁷.aa⁰ +//│ deforest > match: scrut²¹ +//│ deforest > fields: scrut²¹.aa⁰ //│ deforest > <<< fusing <<< //│ = 103 //│ p = AA(AA(3)) @@ -445,10 +419,10 @@ fun mapHead(f, x) = if x is AAA(h, t) then f(h) mapHead(x => x, AAA(1, AAA(2, None))) //│ deforest > >>> fusing >>> -//│ deforest > AAA⁰(1, tmp⁴) -> -//│ deforest > match: x¹⁸ -//│ deforest > fields: x¹⁸.x¹¹ -//│ deforest > fields: x¹⁸.y³ +//│ deforest > AAA⁰(1, tmp³) -> +//│ deforest > match: scrut²² +//│ deforest > fields: scrut²².x¹ +//│ deforest > fields: scrut²².y¹ //│ deforest > <<< fusing <<< //│ = 1 @@ -461,7 +435,7 @@ if A is k + 2 //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut²³ //│ deforest > <<< fusing <<< //│ = 5 //│ k = undefined @@ -473,9 +447,9 @@ fun test(a) = if a is test(B) + test(C) //│ deforest > >>> fusing >>> //│ deforest > C⁰ -> -//│ deforest > match: a⁵ +//│ deforest > match: scrut²⁴ //│ deforest > B⁰ -> -//│ deforest > match: a⁵ +//│ deforest > match: scrut²⁴ //│ deforest > <<< fusing <<< //│ = 4 @@ -493,11 +467,11 @@ fun test(x, y, z) = if x is test(AA(1), AA(2), AA(3)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> -//│ deforest > match: y⁵ -//│ deforest > fields: y⁵.aa⁰ +//│ deforest > match: scrut²⁵ +//│ deforest > fields: scrut²⁵.aa⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: x¹⁹ -//│ deforest > fields: x¹⁹.aa⁰ +//│ deforest > match: scrut²⁶ +//│ deforest > fields: scrut²⁶.aa⁰ //│ deforest > <<< fusing <<< //│ = "2AA(3)NaN" @@ -512,15 +486,15 @@ fun test(x) = let p = if true then AA(CCC(AA)) else BB(CCC(AA)) in... test(p) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(tmp⁵) -> -//│ deforest > match: x²⁰ -//│ deforest > fields: x²⁰.bb⁰ +//│ deforest > BB⁰(tmp⁴) -> +//│ deforest > match: scrut²⁷ +//│ deforest > fields: scrut²⁷.bb⁰ //│ deforest > CCC⁰(AA⁰) -> //│ deforest > match: arg$BB$0$⁰ //│ deforest > fields: arg$BB$0$⁰.c⁰ -//│ deforest > AA⁰(tmp⁶) -> -//│ deforest > match: x²⁰ -//│ deforest > fields: x²⁰.aa⁰ +//│ deforest > AA⁰(tmp⁵) -> +//│ deforest > match: scrut²⁷ +//│ deforest > fields: scrut²⁷.aa⁰ //│ deforest > CCC⁰(AA⁰) -> //│ deforest > match: arg$AA$0$¹ //│ deforest > fields: arg$AA$0$¹.c⁰ @@ -540,11 +514,11 @@ let p = if true then AA(CCC) else BB(CCC) in... test(p, id(CCC(123))) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(CCC⁰) -> -//│ deforest > match: x²¹ -//│ deforest > fields: x²¹.bb⁰ +//│ deforest > match: scrut²⁸ +//│ deforest > fields: scrut²⁸.bb⁰ //│ deforest > AA⁰(CCC⁰) -> -//│ deforest > match: x²¹ -//│ deforest > fields: x²¹.aa⁰ +//│ deforest > match: scrut²⁸ +//│ deforest > fields: scrut²⁸.aa⁰ //│ deforest > <<< fusing <<< //│ = 141 @@ -556,24 +530,28 @@ fun f(x) = if x is f(id(A)) + f(A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: x²² +//│ deforest > match: scrut²⁹ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp, tmp1, tmp2, f_8$f, f_8$x_A, f_8$x_rest, deforest$lam; +//│ let tmp, tmp1, tmp2, f_8$f, f_8$scrut_A, f_8$scrut_rest, deforest$lam; //│ @private //│ define f_8$f as fun f_8$f⁰(x) { -//│ return x() +//│ let scrut; +//│ set scrut = x; +//│ return scrut() //│ }; //│ @affine(1) @private -//│ define f_8$x_A as fun f_8$x_A⁰()() { +//│ define f_8$scrut_A as fun f_8$scrut_A⁰()() { //│ return 1 //│ }; //│ @private -//│ define f_8$x_rest as fun f_8$x_rest⁰() { +//│ define f_8$scrut_rest as fun f_8$scrut_rest⁰() { //│ return null //│ }; //│ define f⁰ as fun f¹(x) { -//│ match x +//│ let scrut; +//│ set scrut = x; +//│ match scrut //│ A⁰ => //│ return 1 //│ else @@ -582,7 +560,7 @@ f(id(A)) + f(A) //│ }; //│ set tmp = Predef⁰.id⁰(A⁰); //│ set tmp1 = f¹(tmp); -//│ set deforest$lam = f_8$x_A⁰(); +//│ set deforest$lam = f_8$scrut_A⁰(); //│ set tmp2 = f_8$f⁰(deforest$lam); //│ return +⁰(tmp1, tmp2) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -600,11 +578,11 @@ fun g(x) = if x is f(g(AA(1))) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: x²³ +//│ deforest > match: scrut³⁰ //│ deforest > B⁰ -> -//│ deforest > match: x²³ +//│ deforest > match: scrut³⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: x²⁴ +//│ deforest > match: scrut³¹ //│ deforest > <<< fusing <<< //│ = 0 @@ -616,9 +594,9 @@ fun f(a, b) = if a is f(A, B) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: a⁶ +//│ deforest > match: scrut³² //│ deforest > B⁰ -> -//│ deforest > match: b² +//│ deforest > match: scrut³³ //│ deforest > <<< fusing <<< //│ = 1 @@ -635,13 +613,13 @@ fun c2(x) = if x is c1(app(AA)) + c1(AA(2)) + c2(AA(3)) + c2(A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: x²⁵ +//│ deforest > match: scrut³⁴ //│ deforest > AA⁰(3) -> -//│ deforest > match: x²⁵ -//│ deforest > fields: x²⁵.aa⁰ +//│ deforest > match: scrut³⁴ +//│ deforest > fields: scrut³⁴.aa⁰ //│ deforest > AA⁰(2) -> -//│ deforest > match: x1⁰ -//│ deforest > fields: x1⁰.aa⁰ +//│ deforest > match: scrut³⁵ +//│ deforest > fields: scrut³⁵.aa⁰ //│ deforest > <<< fusing <<< //│ = 6 @@ -657,27 +635,27 @@ print(c(p(true)) + c(p(false))) c(B) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut³⁶ //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut³⁶ //│ deforest > B⁰ -> -//│ deforest > match: x²⁶ +//│ deforest > match: scrut³⁷ //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut³⁶ //│ deforest > new AA¹(2) -> -//│ deforest > match: x²⁶ -//│ deforest > fields: x²⁶.aa⁰ +//│ deforest > match: scrut³⁷ +//│ deforest > fields: scrut³⁷.aa⁰ //│ deforest > BB⁰(3) -> -//│ deforest > match: x²⁶ -//│ deforest > fields: x²⁶.bb⁰ +//│ deforest > match: scrut³⁷ +//│ deforest > fields: scrut³⁷.bb⁰ //│ deforest > A⁰ -> -//│ deforest > match: A⁰ +//│ deforest > match: scrut³⁶ //│ deforest > new AA¹(2) -> -//│ deforest > match: x²⁶ -//│ deforest > fields: x²⁶.aa⁰ +//│ deforest > match: scrut³⁷ +//│ deforest > fields: scrut³⁷.aa⁰ //│ deforest > BB⁰(3) -> -//│ deforest > match: x²⁶ -//│ deforest > fields: x²⁶.bb⁰ +//│ deforest > match: scrut³⁷ +//│ deforest > fields: scrut³⁷.bb⁰ //│ deforest > <<< fusing <<< //│ > 5 //│ ═══[RUNTIME ERROR] Error: e1 @@ -691,8 +669,8 @@ f(AA(1), BB(2)) //│ deforest > BB⁰(2) -> //│ deforest > select: bb //│ deforest > AA⁰(1) -> -//│ deforest > match: a⁷ -//│ deforest > fields: a⁷.aa⁰ +//│ deforest > match: scrut³⁸ +//│ deforest > fields: scrut³⁸.aa⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -704,7 +682,7 @@ fun g(k, x) = if k is g(A, AA(3)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: k⁰ +//│ deforest > match: scrut³⁹ //│ deforest > <<< fusing <<< //│ = 9 @@ -730,8 +708,8 @@ let p = AA(AA(3)) outer1(p)(1, 2) + outer2(p, 3)(4) + inner(AA(5))(6) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(5) -> -//│ deforest > match: x²⁷ -//│ deforest > fields: x²⁷.aa⁰ +//│ deforest > match: scrut⁴⁰ +//│ deforest > fields: scrut⁴⁰.aa⁰ //│ deforest > <<< fusing <<< //│ = 103 //│ p = AA(AA(3)) @@ -746,10 +724,6 @@ fun f(x) = if x is AA then x.AA#aa.AA#aa f(AA(AA(3))) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(tmp⁷) -> -//│ deforest > match: x²⁸ -//│ deforest > fields: x²⁸.aa⁰ -//│ deforest > fields: x²⁸.aa⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -763,9 +737,9 @@ fun f2(a2) = if a2 is AA then 2 f1(id(AA(1))) + f2(id(AA(2))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> -//│ deforest > match: a2⁰ +//│ deforest > match: scrut⁴¹ //│ deforest > AA⁰(1) -> -//│ deforest > match: a1⁰ +//│ deforest > match: scrut⁴² //│ deforest > <<< fusing <<< //│ = 3 @@ -780,11 +754,11 @@ fun c1(x) = if x is c1(1 :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, Nil⁰) -> -//│ deforest > match: x²⁹ -//│ deforest > fields: x²⁹.h⁰ -//│ deforest > fields: x²⁹.t⁰ +//│ deforest > match: scrut⁴³ +//│ deforest > fields: scrut⁴³.h⁰ +//│ deforest > fields: scrut⁴³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: t¹ +//│ deforest > match: scrut⁴⁴ //│ deforest > <<< fusing <<< //│ = 0 @@ -802,11 +776,6 @@ fun apply(f) = f(AA(inner)) apply(f) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(inner⁰) -> -//│ deforest > match: x³⁰ -//│ deforest > fields: x³⁰.aa⁰ -//│ deforest > AA⁰(0) -> -//│ deforest > match: y1⁰ //│ deforest > <<< fusing <<< //│ = 0 @@ -819,10 +788,10 @@ fun f1(a1) = if a1 is AA then 1 fun f2(a2) = if a2 is AA then 2 f1(wrap(1)) + f2(wrap(2)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x³¹) -> -//│ deforest > match: a2¹ -//│ deforest > AA⁰(x³¹) -> -//│ deforest > match: a1¹ +//│ deforest > AA⁰(x²) -> +//│ deforest > match: scrut⁴⁵ +//│ deforest > AA⁰(x²) -> +//│ deforest > match: scrut⁴⁶ //│ deforest > <<< fusing <<< //│ = 3 @@ -833,9 +802,9 @@ fun p2(x) = if x == 0 then AA(x) else p1(x - 1) fun c(a) = if a is AA(x) then x c(p1(3)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x³²) -> -//│ deforest > match: a⁸ -//│ deforest > fields: a⁸.aa⁰ +//│ deforest > AA⁰(x³) -> +//│ deforest > match: scrut⁴⁷ +//│ deforest > fields: scrut⁴⁷.aa⁰ //│ deforest > <<< fusing <<< //│ = 0 @@ -856,25 +825,25 @@ fun g(b) = [f(3), g(true)] //│ deforest > >>> fusing >>> //│ deforest > BB⁰(0) -> -//│ deforest > match: scrut⁰ +//│ deforest > match: scrut⁴⁸ //│ deforest > D⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: scrut⁴⁸ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁴⁹ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁴⁹ //│ deforest > C⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁴⁹ //│ deforest > BB⁰(0) -> -//│ deforest > match: scrut⁰ +//│ deforest > match: scrut⁴⁸ //│ deforest > D⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: scrut⁴⁸ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁴⁹ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁴⁹ //│ deforest > C⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: scrut⁴⁹ //│ deforest > <<< fusing <<< //│ = [D, AA(1)] @@ -890,7 +859,7 @@ fun f(ls) = if ls is [] then f([]) //│ deforest > >>> fusing >>> //│ deforest > [] -> -//│ deforest > match: ls⁰ +//│ deforest > match: scrut⁵⁰ //│ deforest > <<< fusing <<< //│ = 4 @@ -901,9 +870,9 @@ fun f(x, b) = if x is f(Nil, false) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: x³³ +//│ deforest > match: scrut⁵¹ //│ deforest > Nil⁰ -> -//│ deforest > match: x³³ +//│ deforest > match: scrut⁵¹ //│ deforest > <<< fusing <<< //│ = 0 @@ -929,9 +898,9 @@ fun c2(x) = if x is AA then 2 c1(p(1)) + c2(p(2)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(3) -> -//│ deforest > match: x³⁴ +//│ deforest > match: scrut⁵² //│ deforest > AA⁰(3) -> -//│ deforest > match: x³⁵ +//│ deforest > match: scrut⁵³ //│ deforest > <<< fusing <<< //│ = 3 @@ -945,21 +914,21 @@ fun f4(a4) = if a4 is AA(aa) then aa fun f5(a5) = if a5 is AA(aa) then aa f1(p(1)) + f2(p(2)) + f3(p(3)) + f4(p(4)) + f5(p(5)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x³⁶) -> -//│ deforest > match: a5⁰ -//│ deforest > fields: a5⁰.aa⁰ -//│ deforest > AA⁰(x³⁶) -> -//│ deforest > match: a4⁰ -//│ deforest > fields: a4⁰.aa⁰ -//│ deforest > AA⁰(x³⁶) -> -//│ deforest > match: a3⁰ -//│ deforest > fields: a3⁰.aa⁰ -//│ deforest > AA⁰(x³⁶) -> -//│ deforest > match: a2² -//│ deforest > fields: a2².aa⁰ -//│ deforest > AA⁰(x³⁶) -> -//│ deforest > match: a1² -//│ deforest > fields: a1².aa⁰ +//│ deforest > AA⁰(x⁴) -> +//│ deforest > match: scrut⁵⁴ +//│ deforest > fields: scrut⁵⁴.aa⁰ +//│ deforest > AA⁰(x⁴) -> +//│ deforest > match: scrut⁵⁵ +//│ deforest > fields: scrut⁵⁵.aa⁰ +//│ deforest > AA⁰(x⁴) -> +//│ deforest > match: scrut⁵⁶ +//│ deforest > fields: scrut⁵⁶.aa⁰ +//│ deforest > AA⁰(x⁴) -> +//│ deforest > match: scrut⁵⁷ +//│ deforest > fields: scrut⁵⁷.aa⁰ +//│ deforest > AA⁰(x⁴) -> +//│ deforest > match: scrut⁵⁸ +//│ deforest > fields: scrut⁵⁸.aa⁰ //│ deforest > <<< fusing <<< //│ = 15 @@ -986,30 +955,30 @@ fun f4(ls4) = if ls4 is Nil then 5 f1(to(4)) + f2(to(5)) + f3(to(6)) + f4(to(7)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(n⁰, tmp⁸) -> -//│ deforest > match: ls4⁰ -//│ deforest > fields: ls4⁰.h⁰ -//│ deforest > fields: ls4⁰.t⁰ +//│ deforest > Cons⁰(n⁰, tmp⁶) -> +//│ deforest > match: scrut⁵⁹ +//│ deforest > fields: scrut⁵⁹.h⁰ +//│ deforest > fields: scrut⁵⁹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls4⁰ -//│ deforest > Cons⁰(n⁰, tmp⁸) -> -//│ deforest > match: ls3⁰ -//│ deforest > fields: ls3⁰.h⁰ -//│ deforest > fields: ls3⁰.t⁰ +//│ deforest > match: scrut⁵⁹ +//│ deforest > Cons⁰(n⁰, tmp⁶) -> +//│ deforest > match: scrut⁶⁰ +//│ deforest > fields: scrut⁶⁰.h⁰ +//│ deforest > fields: scrut⁶⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls3⁰ -//│ deforest > Cons⁰(n⁰, tmp⁸) -> -//│ deforest > match: ls2⁰ -//│ deforest > fields: ls2⁰.h⁰ -//│ deforest > fields: ls2⁰.t⁰ +//│ deforest > match: scrut⁶⁰ +//│ deforest > Cons⁰(n⁰, tmp⁶) -> +//│ deforest > match: scrut⁶¹ +//│ deforest > fields: scrut⁶¹.h⁰ +//│ deforest > fields: scrut⁶¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls2⁰ -//│ deforest > Cons⁰(n⁰, tmp⁸) -> -//│ deforest > match: ls1⁰ -//│ deforest > fields: ls1⁰.h⁰ -//│ deforest > fields: ls1⁰.t⁰ +//│ deforest > match: scrut⁶¹ +//│ deforest > Cons⁰(n⁰, tmp⁶) -> +//│ deforest > match: scrut⁶² +//│ deforest > fields: scrut⁶².h⁰ +//│ deforest > fields: scrut⁶².t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls1⁰ +//│ deforest > match: scrut⁶² //│ deforest > <<< fusing <<< //│ = 88 @@ -1025,18 +994,18 @@ fun f1(p) = if p is AA(a) then a + 1 fun f2(p) = if p is AA(b) then b + 2 f1(wrap(true)) + f2(g2(false)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x³⁷) -> -//│ deforest > match: p⁰ -//│ deforest > fields: p⁰.aa⁰ -//│ deforest > AA⁰(x³⁷) -> -//│ deforest > match: p⁰ -//│ deforest > fields: p⁰.aa⁰ -//│ deforest > AA⁰(x³⁷) -> -//│ deforest > match: p¹ -//│ deforest > fields: p¹.aa⁰ -//│ deforest > AA⁰(x³⁷) -> -//│ deforest > match: p¹ -//│ deforest > fields: p¹.aa⁰ +//│ deforest > AA⁰(x⁵) -> +//│ deforest > match: scrut⁶³ +//│ deforest > fields: scrut⁶³.aa⁰ +//│ deforest > AA⁰(x⁵) -> +//│ deforest > match: scrut⁶³ +//│ deforest > fields: scrut⁶³.aa⁰ +//│ deforest > AA⁰(x⁵) -> +//│ deforest > match: scrut⁶⁴ +//│ deforest > fields: scrut⁶⁴.aa⁰ +//│ deforest > AA⁰(x⁵) -> +//│ deforest > match: scrut⁶⁴ +//│ deforest > fields: scrut⁶⁴.aa⁰ //│ deforest > <<< fusing <<< //│ = 8 @@ -1050,9 +1019,9 @@ fun f2(p) = if p is AA(b) then b + 2 fun f3(p) = if p is AA(c) then c + 3 f1(one) + f2(one) + f3(two) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x³⁸) -> -//│ deforest > match: p² -//│ deforest > fields: p².aa⁰ +//│ deforest > AA⁰(x⁶) -> +//│ deforest > match: scrut⁶⁵ +//│ deforest > fields: scrut⁶⁵.aa⁰ //│ deforest > <<< fusing <<< //│ = 10 @@ -1064,11 +1033,11 @@ fun f2(a) = if a is AA(i) then i + 1 f1(id(AA(1))) + f2(id(AA(2))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> -//│ deforest > match: a⁹ -//│ deforest > fields: a⁹.aa⁰ +//│ deforest > match: scrut⁶⁶ +//│ deforest > fields: scrut⁶⁶.aa⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: a¹⁰ -//│ deforest > fields: a¹⁰.aa⁰ +//│ deforest > match: scrut⁶⁷ +//│ deforest > fields: scrut⁶⁷.aa⁰ //│ deforest > <<< fusing <<< //│ = 4 @@ -1082,11 +1051,11 @@ fun f2(a2) = if a2 is AA(a) then a + 2 fun wrap(a) = a f1(wrap(p(2))) + f2(p(1)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x³⁹) -> -//│ deforest > match: a2³ -//│ deforest > fields: a2³.aa⁰ -//│ deforest > AA⁰(x³⁹) -> -//│ deforest > match: a1³ -//│ deforest > fields: a1³.aa⁰ +//│ deforest > AA⁰(x⁷) -> +//│ deforest > match: scrut⁶⁸ +//│ deforest > fields: scrut⁶⁸.aa⁰ +//│ deforest > AA⁰(x⁷) -> +//│ deforest > match: scrut⁶⁹ +//│ deforest > fields: scrut⁶⁹.aa⁰ //│ deforest > <<< fusing <<< //│ = 6 diff --git a/hkmc2/shared/src/test/mlscript/deforest/todos.mls b/hkmc2/shared/src/test/mlscript/deforest/todos.mls index f7f7263a26..64e6c1356b 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/todos.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/todos.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -40,36 +41,38 @@ let p = AA(AA(AA(10))) test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: x⁰ +//│ deforest > match: scrut⁰ //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: x⁰ -//│ deforest > fields: x⁰.x¹ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x⁰ //│ deforest > AA⁰(tmp¹) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x¹ +//│ deforest > fields: arg$AA$0$⁰.x⁰ //│ deforest > AA⁰(10) -> //│ deforest > match: arg$AA$0$¹ -//│ deforest > fields: arg$AA$0$¹.x¹ +//│ deforest > fields: arg$AA$0$¹.x⁰ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, test_28$test, test_25$test, test_28$x_dflt, test_28$x_rest, test_25$x_AA, test_25$x_rest, test_25$arg$AA$0$_AA, test_25$arg$AA$0$_rest, test_25$arg$AA$0$_AA1, test_25$arg$AA$0$_rest1, AA_x, AA_x1, AA_x2, deforest$lam; +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, test_28$test, test_25$test, test_28$scrut_dflt, test_28$scrut_rest, test_25$scrut_AA, test_25$scrut_rest, test_25$arg$AA$0$_AA, test_25$arg$AA$0$_rest, test_25$arg$AA$0$_AA1, test_25$arg$AA$0$_rest1, AA_x, AA_x1, AA_x2, deforest$lam; //│ @private //│ define test_28$test as fun test_28$test⁰(x) { -//│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; -//│ return x(tmp11, t, tmp11, tmp12, tmp13, tmp14) +//│ let t, scrut, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; +//│ set scrut = x; +//│ return scrut(tmp11, t, tmp11, tmp12, tmp13, tmp14) //│ }; //│ @private //│ define test_25$test as fun test_25$test⁰(x) { -//│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; -//│ return x(t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14, t, tmp11, tmp12, tmp13, tmp14) +//│ let t, scrut, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; +//│ set scrut = x; +//│ return scrut(t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14, t, tmp11, tmp12, tmp13, tmp14) //│ }; //│ @affine(1) @private -//│ define test_28$x_dflt as fun test_28$x_dflt⁰()(fv_tmp, fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) { +//│ define test_28$scrut_dflt as fun test_28$scrut_dflt⁰()(fv_tmp, fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) { //│ set fv_tmp1 = 4; -//│ return test_28$x_rest⁰(fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) +//│ return test_28$scrut_rest⁰(fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) //│ }; //│ @affine(1) @private -//│ define test_25$x_AA as fun test_25$x_AA⁰(AA_x3)(fv_t, fv_a, fv_arg$AA$0$, fv_arg$AA$0$1, fv_arg$AA$0$2, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3, fv_t1, fv_tmp4, fv_tmp5, fv_tmp6, fv_tmp7) { +//│ define test_25$scrut_AA as fun test_25$scrut_AA⁰(AA_x3)(fv_t, fv_a, fv_arg$AA$0$, fv_arg$AA$0$1, fv_arg$AA$0$2, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3, fv_t1, fv_tmp4, fv_tmp5, fv_tmp6, fv_tmp7) { //│ set fv_arg$AA$0$ = AA_x3; //│ return fv_arg$AA$0$(fv_t1, fv_a, fv_arg$AA$0$1, fv_arg$AA$0$2, fv_tmp4, fv_tmp5, fv_tmp6, fv_tmp7, fv_t1, fv_tmp4, fv_tmp5, fv_tmp6, fv_tmp7) //│ }; @@ -86,7 +89,7 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ return test_25$arg$AA$0$_rest⁰(fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) //│ }; //│ @private -//│ define test_28$x_rest as fun test_28$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { +//│ define test_28$scrut_rest as fun test_28$scrut_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { //│ set fv_t = fv_tmp; //│ set fv_tmp1 = +⁰(fv_t, 20); //│ set fv_tmp2 = -⁰(fv_tmp1, 3); @@ -94,7 +97,7 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ return -⁰(fv_tmp3, 1) //│ }; //│ @private -//│ define test_25$x_rest as fun test_25$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { +//│ define test_25$scrut_rest as fun test_25$scrut_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { //│ set fv_t = fv_tmp; //│ set fv_tmp1 = +⁰(fv_t, 20); //│ set fv_tmp2 = -⁰(fv_tmp1, 3); @@ -103,23 +106,24 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ }; //│ @private //│ define test_25$arg$AA$0$_rest as fun test_25$arg$AA$0$_rest¹(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { -//│ return test_25$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) +//│ return test_25$scrut_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) //│ }; //│ @private //│ define test_25$arg$AA$0$_rest1 as fun test_25$arg$AA$0$_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { //│ return test_25$arg$AA$0$_rest¹(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) //│ }; //│ define test⁰ as fun test¹(x) { -//│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; -//│ match x +//│ let t, scrut, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; +//│ set scrut = x; +//│ match scrut //│ AA¹ => -//│ set arg$AA$0$ = x.x¹; +//│ set arg$AA$0$ = scrut.x⁰; //│ match arg$AA$0$ //│ AA¹ => -//│ set arg$AA$0$1 = arg$AA$0$.x¹; +//│ set arg$AA$0$1 = arg$AA$0$.x⁰; //│ match arg$AA$0$1 //│ AA¹ => -//│ set arg$AA$0$2 = arg$AA$0$1.x¹; +//│ set arg$AA$0$2 = arg$AA$0$1.x⁰; //│ set a = arg$AA$0$2; //│ set tmp11 = a; //│ end @@ -141,10 +145,11 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ return -⁰(tmp14, 1) //│ }; //│ define f⁰ as fun f¹(a) { -//│ let arg$AA$0$; -//│ match a +//│ let scrut, arg$AA$0$; +//│ set scrut = a; +//│ match scrut //│ AA¹ => -//│ set arg$AA$0$ = a.x¹; +//│ set arg$AA$0$ = scrut.x⁰; //│ match arg$AA$0$ //│ AA¹ => //│ return 0 @@ -166,10 +171,10 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ set AA_x1 = tmp5; //│ set tmp6 = test_25$arg$AA$0$_AA⁰(AA_x1); //│ set AA_x2 = tmp6; -//│ set tmp7 = test_25$x_AA⁰(AA_x2); +//│ set tmp7 = test_25$scrut_AA⁰(AA_x2); //│ set tmp8 = test_25$test⁰(tmp7); //│ set tmp9 = +⁰(tmp4, tmp8); -//│ set deforest$lam = test_28$x_dflt⁰(); +//│ set deforest$lam = test_28$scrut_dflt⁰(); //│ set tmp10 = test_28$test⁰(deforest$lam); //│ return +⁰(tmp9, tmp10) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -188,17 +193,17 @@ fun test() = test() //│ deforest > >>> fusing >>> //│ deforest > Global⁰(1) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x² +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.x¹ //│ deforest > Global⁰(1) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x² +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.x¹ //│ deforest > <<< fusing <<< //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'Local' -//│ ║ l.187: Global(x) then Local(x + 1) +//│ ║ l.192: Global(x) then Local(x + 1) //│ ║ ^^^^^ //│ ╟── which references the symbol introduced here -//│ ║ l.185: data class Local(x) +//│ ║ l.190: data class Local(x) //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] ReferenceError: Local is not defined //│ ═══[RUNTIME ERROR] Expected: 'Local(2)', got: 'undefined' @@ -223,9 +228,9 @@ fun f(x) = if x is A then 1 f(A) + f(A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: x³ +//│ deforest > match: scrut² //│ deforest > A⁰ -> -//│ deforest > match: x³ +//│ deforest > match: scrut² //│ deforest > <<< fusing <<< //│ = 2 @@ -239,7 +244,7 @@ module Test with Test.Test2.foo(AA(2)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> -//│ deforest > match: a⁰ -//│ deforest > fields: a⁰.x¹ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.x⁰ //│ deforest > <<< fusing <<< //│ ═══[RUNTIME ERROR] ReferenceError: Test2 is not defined diff --git a/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls b/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls index d9bb2a716b..c1cd347eaf 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls @@ -1,3 +1,4 @@ +:. :js :deforest @@ -21,25 +22,25 @@ fun testUnzipZip(n) = unzip(zip(id(enumFromTo(1, n)), id(enumFromTo(2, n + 3)))) testUnzipZip(3) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls_unzip⁰ +//│ deforest > match: scrut⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_unzip⁰ +//│ deforest > match: scrut⁰ //│ deforest > Cons⁰(tmp⁰, tmp¹) -> -//│ deforest > match: ls_unzip⁰ -//│ deforest > fields: ls_unzip⁰.h⁰ -//│ deforest > fields: ls_unzip⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h⁰ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a⁰ //│ deforest > fields: arg$Cons$0$⁰.b⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_unzip⁰ +//│ deforest > match: scrut⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_unzip⁰ +//│ deforest > match: scrut⁰ //│ deforest > Cons⁰(tmp⁰, tmp¹) -> -//│ deforest > match: ls_unzip⁰ -//│ deforest > fields: ls_unzip⁰.h⁰ -//│ deforest > fields: ls_unzip⁰.t⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.h⁰ +//│ deforest > fields: scrut⁰.t⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a⁰ @@ -62,29 +63,29 @@ fun testZipUnzip(n) = if unzip(id(makeZippedList(n))) is testZipUnzip(3) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ +//│ deforest > match: scrut¹ //│ deforest > Cons⁰(a¹, atail⁰) -> -//│ deforest > match: xs_zip⁰ -//│ deforest > fields: xs_zip⁰.h⁰ -//│ deforest > fields: xs_zip⁰.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ +//│ deforest > match: scrut² //│ deforest > Cons⁰(b¹, btail⁰) -> -//│ deforest > match: ys_zip⁰ -//│ deforest > fields: ys_zip⁰.h⁰ -//│ deforest > fields: ys_zip⁰.t⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².h⁰ +//│ deforest > fields: scrut².t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip⁰ +//│ deforest > match: scrut¹ //│ deforest > Cons⁰(a¹, atail⁰) -> -//│ deforest > match: xs_zip⁰ -//│ deforest > fields: xs_zip⁰.h⁰ -//│ deforest > fields: xs_zip⁰.t⁰ +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.h⁰ +//│ deforest > fields: scrut¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip⁰ +//│ deforest > match: scrut² //│ deforest > Cons⁰(b¹, btail⁰) -> -//│ deforest > match: ys_zip⁰ -//│ deforest > fields: ys_zip⁰.h⁰ -//│ deforest > fields: ys_zip⁰.t⁰ +//│ deforest > match: scrut² +//│ deforest > fields: scrut².h⁰ +//│ deforest > fields: scrut².t⁰ //│ deforest > <<< fusing <<< //│ = Cons(Pair(3, 4), Cons(Pair(2, 3), Cons(Pair(1, 2), Nil))) @@ -108,53 +109,53 @@ fun testZipMapBothUnzip(n) = if unzip(id(makeZippedList(n))) is testZipMapBothUnzip(4) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: ls_map⁰ +//│ deforest > match: scrut³ //│ deforest > Cons⁰(a², atail¹) -> -//│ deforest > match: ls_map⁰ -//│ deforest > fields: ls_map⁰.h⁰ -//│ deforest > fields: ls_map⁰.t⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.h⁰ +//│ deforest > fields: scrut³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_map⁰ +//│ deforest > match: scrut³ //│ deforest > Cons⁰(b², btail¹) -> -//│ deforest > match: ls_map⁰ -//│ deforest > fields: ls_map⁰.h⁰ -//│ deforest > fields: ls_map⁰.t⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.h⁰ +//│ deforest > fields: scrut³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip¹ +//│ deforest > match: scrut⁴ //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: xs_zip¹ -//│ deforest > fields: xs_zip¹.h⁰ -//│ deforest > fields: xs_zip¹.t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip¹ +//│ deforest > match: scrut⁵ //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: ys_zip¹ -//│ deforest > fields: ys_zip¹.h⁰ -//│ deforest > fields: ys_zip¹.t⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.h⁰ +//│ deforest > fields: scrut⁵.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_map⁰ +//│ deforest > match: scrut³ //│ deforest > Cons⁰(a², atail¹) -> -//│ deforest > match: ls_map⁰ -//│ deforest > fields: ls_map⁰.h⁰ -//│ deforest > fields: ls_map⁰.t⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.h⁰ +//│ deforest > fields: scrut³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ls_map⁰ +//│ deforest > match: scrut³ //│ deforest > Cons⁰(b², btail¹) -> -//│ deforest > match: ls_map⁰ -//│ deforest > fields: ls_map⁰.h⁰ -//│ deforest > fields: ls_map⁰.t⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.h⁰ +//│ deforest > fields: scrut³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: xs_zip¹ +//│ deforest > match: scrut⁴ //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: xs_zip¹ -//│ deforest > fields: xs_zip¹.h⁰ -//│ deforest > fields: xs_zip¹.t⁰ +//│ deforest > match: scrut⁴ +//│ deforest > fields: scrut⁴.h⁰ +//│ deforest > fields: scrut⁴.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: ys_zip¹ +//│ deforest > match: scrut⁵ //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: ys_zip¹ -//│ deforest > fields: ys_zip¹.h⁰ -//│ deforest > fields: ys_zip¹.t⁰ +//│ deforest > match: scrut⁵ +//│ deforest > fields: scrut⁵.h⁰ +//│ deforest > fields: scrut⁵.t⁰ //│ deforest > <<< fusing <<< //│ = Cons( //│ Pair(5, 25), diff --git a/hkmc2/shared/src/test/mlscript/handlers/.mls b/hkmc2/shared/src/test/mlscript/handlers/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/handlers/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/handlers/CurriedFunctionCalls.mls b/hkmc2/shared/src/test/mlscript/handlers/CurriedFunctionCalls.mls index a3875cb9c5..0437cadca1 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/CurriedFunctionCalls.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/CurriedFunctionCalls.mls @@ -1,3 +1,4 @@ +:. :js :effectHandlers @@ -60,7 +61,7 @@ fun g(a)(b) = f(a)(b) + f(b)(a) //│ set pc = 2; //│ end //│ else -//│ return runtime⁰.unwind﹖(g¹, 2, "CurriedFunctionCalls.mls:32:15", null, null, 2, 1, a, 1, b, 1, tmp) +//│ return runtime⁰.unwind﹖(g¹, 2, "CurriedFunctionCalls.mls:33:15", null, null, 2, 1, a, 1, b, 1, tmp) //│ end //│ match pc //│ 2 => @@ -73,7 +74,7 @@ fun g(a)(b) = f(a)(b) + f(b)(a) //│ set pc = 1; //│ end //│ else -//│ return runtime⁰.unwind﹖(g¹, 1, "CurriedFunctionCalls.mls:32:25", null, null, 2, 1, a, 1, b, 1, tmp) +//│ return runtime⁰.unwind﹖(g¹, 1, "CurriedFunctionCalls.mls:33:25", null, null, 2, 1, a, 1, b, 1, tmp) //│ end //│ match pc //│ 1 => diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls index 51f96b4eec..3b5685c0de 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsHygiene.mls @@ -1,3 +1,4 @@ +:. :js :effectHandlers :lift @@ -16,16 +17,16 @@ fun foo(h): Object = module A A //│ ╔══[WARNING] Modules are not yet lifted. -//│ ║ l.16: module A +//│ ║ l.17: module A //│ ╙── ^ //│ ╔══[WARNING] Modules are not yet lifted. -//│ ║ l.13: module A +//│ ║ l.14: module A //│ ╙── ^ //│ ╔══[INTERNAL ERROR] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.13: module A +//│ ║ l.14: module A //│ ╙── ^ //│ ╔══[INTERNAL ERROR] Unexpected nested class: lambdas may not function correctly. -//│ ║ l.16: module A +//│ ║ l.17: module A //│ ╙── ^ //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo; diff --git a/hkmc2/shared/src/test/mlscript/handlers/Fallthrough.mls b/hkmc2/shared/src/test/mlscript/handlers/Fallthrough.mls index 863dbaceac..e64fb4945e 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Fallthrough.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Fallthrough.mls @@ -1,3 +1,4 @@ +:. :js :effectHandlers :lift @@ -36,7 +37,7 @@ fun f() = //│ if (runtime.curEffect === null) { //│ pc = 6; //│ } else { -//│ return runtime.unwind(f, 6, "Fallthrough.mls:11:19", null, null, 1, 0, 1, tmp) +//│ return runtime.unwind(f, 6, "Fallthrough.mls:12:19", null, null, 1, 0, 1, tmp) //│ } //│ case 6: //│ runtime.resumeValue; @@ -46,7 +47,7 @@ fun f() = //│ if (runtime.curEffect === null) { //│ pc = 4; //│ } else { -//│ return runtime.unwind(f, 4, "Fallthrough.mls:12:11", null, null, 1, 0, 1, tmp) +//│ return runtime.unwind(f, 4, "Fallthrough.mls:13:11", null, null, 1, 0, 1, tmp) //│ } //│ case 4: //│ runtime.resumeValue; @@ -54,7 +55,7 @@ fun f() = //│ if (runtime.curEffect === null) { //│ pc = 3; //│ } else { -//│ return runtime.unwind(f, 3, "Fallthrough.mls:13:11", null, null, 1, 0, 1, tmp) +//│ return runtime.unwind(f, 3, "Fallthrough.mls:14:11", null, null, 1, 0, 1, tmp) //│ } //│ case 3: //│ runtime.resumeValue; @@ -62,7 +63,7 @@ fun f() = //│ if (runtime.curEffect === null) { //│ pc = 2; //│ } else { -//│ return runtime.unwind(f, 2, "Fallthrough.mls:14:11", null, null, 1, 0, 1, tmp) +//│ return runtime.unwind(f, 2, "Fallthrough.mls:15:11", null, null, 1, 0, 1, tmp) //│ } //│ case 2: //│ runtime.resumeValue; @@ -70,7 +71,7 @@ fun f() = //│ if (runtime.curEffect === null) { //│ pc = 1; //│ } else { -//│ return runtime.unwind(f, 1, "Fallthrough.mls:15:11", null, null, 1, 0, 1, tmp) +//│ return runtime.unwind(f, 1, "Fallthrough.mls:16:11", null, null, 1, 0, 1, tmp) //│ } //│ case 1: //│ return runtime.resumeValue; @@ -81,7 +82,7 @@ fun f() = //│ pc = 7; //│ continue main //│ } -//│ return runtime.unwind(f, 7, "Fallthrough.mls:11:13", null, null, 1, 0, 1, tmp); +//│ return runtime.unwind(f, 7, "Fallthrough.mls:12:13", null, null, 1, 0, 1, tmp); //│ } //│ pc = 5; //│ continue main; diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlerReset.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlerReset.mls index 40ad2030da..c5514cb8cf 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlerReset.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlerReset.mls @@ -1,3 +1,4 @@ +:. :js :effectHandlers :lift diff --git a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls index 67b143ed20..252ae4ede3 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/HandlersScratch.mls @@ -1,3 +1,4 @@ +:. :js :effectHandlers :lift diff --git a/hkmc2/shared/src/test/mlscript/handlers/Loops.mls b/hkmc2/shared/src/test/mlscript/handlers/Loops.mls index 542d3fc2b5..72d6492946 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Loops.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Loops.mls @@ -1,3 +1,4 @@ +:. :js :lift :effectHandlers diff --git a/hkmc2/shared/src/test/mlscript/handlers/NoHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/NoHandler.mls index f292f4cc6f..f07d713f9a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NoHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NoHandler.mls @@ -1,3 +1,4 @@ +:. :js abstract class Effect with @@ -8,10 +9,10 @@ handle h = Effect with fun perform(arg)(k) = k(arg) h.perform("k") //│ ╔══[COMPILATION ERROR] Effect handlers are not enabled -//│ ║ l.7: handle h = Effect with +//│ ║ l.8: handle h = Effect with //│ ║ ^^^^^^^^^^^ -//│ ║ l.8: fun perform(arg)(k) = k(arg) +//│ ║ l.9: fun perform(arg)(k) = k(arg) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.9: h.perform("k") -//│ ╙── ^^^^^^^^^^^^^^ +//│ ║ l.10: h.perform("k") +//│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/handlers/SavedVars.mls b/hkmc2/shared/src/test/mlscript/handlers/SavedVars.mls index cb7981397e..375787ec9f 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/SavedVars.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/SavedVars.mls @@ -1,3 +1,4 @@ +:. :js :effectHandlers :lift @@ -38,7 +39,7 @@ fun g() = //│ if (runtime.curEffect === null) { //│ pc = 3; //│ } else { -//│ return runtime.unwind(g, 3, "SavedVars.mls:17:8", null, null, 1, 0, 0) +//│ return runtime.unwind(g, 3, "SavedVars.mls:18:8", null, null, 1, 0, 0) //│ } //│ case 3: //│ scrut = runtime.resumeValue; @@ -50,7 +51,7 @@ fun g() = //│ pc = 2; //│ continue main //│ } -//│ return runtime.unwind(g, 2, "SavedVars.mls:18:7", null, null, 1, 0, 0); +//│ return runtime.unwind(g, 2, "SavedVars.mls:19:7", null, null, 1, 0, 0); //│ } //│ tmp = 2; //│ pc = 1; diff --git a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls index f1e674d953..ba8edcb1d6 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/ZCombinator.mls @@ -1,3 +1,4 @@ +:. :js :effectHandlers :lift diff --git a/hkmc2/shared/src/test/mlscript/interop/.mls b/hkmc2/shared/src/test/mlscript/interop/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/interop/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls index aa30b2d956..483ff811a5 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,8 +7,16 @@ let arr = [true, false] // * Array methods annoyingly supply extra arguments, such as the index and the array itself +:e :re arr.map(_ === false) +//│ ╔══[COMPILATION ERROR] Resolution error in function type +//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Callback parameter list does not match its declared function type. +//│ ║ in Arrays.mls:12 +//│ ║ l.12: arr.map(_ === false) +//│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 1 argument but got 3 :sjs @@ -33,7 +42,7 @@ arr.map((_, ...) => 1) :e arr.map((..., ...) => 1) //│ ╔══[COMPILATION ERROR] Spread parameters must be the last in the parameter list. -//│ ║ l.34: arr.map((..., ...) => 1) +//│ ║ l.43: arr.map((..., ...) => 1) //│ ╙── ^^^ //│ = [1, 1] @@ -41,8 +50,15 @@ arr.map((..., ...) => 1) :re arr.map((..., e) => e) //│ ╔══[COMPILATION ERROR] Spread parameters must be the last in the parameter list. -//│ ║ l.42: arr.map((..., e) => e) +//│ ║ l.51: arr.map((..., e) => e) //│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in function type +//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Callback parameter list does not match its declared function type. +//│ ║ in Arrays.mls:51 +//│ ║ l.51: arr.map((..., e) => e) +//│ ╙── ^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 3 arr.map(pass1(_ === false)) @@ -65,16 +81,40 @@ Array(1) :e Array() -//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 0 -//│ ║ l.67: Array() -//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.83: Array() +//│ ║ ^^^^^^^ +//│ ╟── Class 'Array' expected 1 argument, but got 0 +//│ ║ in ../decls/Prelude.mls +//│ ║ l.177: declare class Array[T](val length: Int): Array[T] with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.178: fun join(...separators): Str +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.179: fun reverse(): Array[T] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.180: fun sort(...compare): Array[T] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.181: ... (more lines omitted) ... +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] :e Array(1, 2, 3) -//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 3 -//│ ║ l.74: Array(1, 2, 3) -//│ ╙── ^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.102: Array(1, 2, 3) +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Class 'Array' expected 1 argument, but got 3 +//│ ║ in ../decls/Prelude.mls +//│ ║ l.177: declare class Array[T](val length: Int): Array[T] with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.178: fun join(...separators): Str +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.179: fun reverse(): Array[T] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.180: fun sort(...compare): Array[T] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.181: ... (more lines omitted) ... +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2, 3] diff --git a/hkmc2/shared/src/test/mlscript/interop/CtorBypass.mls b/hkmc2/shared/src/test/mlscript/interop/CtorBypass.mls index c3756f7c68..b7209e62e9 100644 --- a/hkmc2/shared/src/test/mlscript/interop/CtorBypass.mls +++ b/hkmc2/shared/src/test/mlscript/interop/CtorBypass.mls @@ -1,3 +1,4 @@ +:. :js @@ -13,11 +14,12 @@ let a = new A a.n //│ = 123 -A.prototype +A!prototype //│ = {} -// * This can be used for deserialization -let b = Object.create(A.prototype) +// * This can be used for deserialization. The constructor has not initialized its +// * fields, so use a dynamic receiver to retain runtime checks for missing fields. +val b: dyn = Object.create(A!prototype) //│ b = A :sjs diff --git a/hkmc2/shared/src/test/mlscript/interop/Functions.mls b/hkmc2/shared/src/test/mlscript/interop/Functions.mls index c9d872691f..c5ab3451d0 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Functions.mls @@ -1,23 +1,24 @@ +:. :js -(x => x).constructor == Function +(x => x)!constructor == Function //│ = true fun foo(x) = x -foo.constructor == Function +foo!constructor == Function //│ = true -foo.name +foo!name //│ = "foo" -foo.constructor.name +foo!constructor!name //│ = "Function" typeof(foo) //│ = "function" -typeof(foo.constructor) +typeof(foo!constructor) //│ = "function" diff --git a/hkmc2/shared/src/test/mlscript/interop/JSCollections.mls b/hkmc2/shared/src/test/mlscript/interop/JSCollections.mls index a4a6ec7b72..10c539a4c3 100644 --- a/hkmc2/shared/src/test/mlscript/interop/JSCollections.mls +++ b/hkmc2/shared/src/test/mlscript/interop/JSCollections.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/interop/JSDerp.mls b/hkmc2/shared/src/test/mlscript/interop/JSDerp.mls index 9149486604..25d3ec54f9 100644 --- a/hkmc2/shared/src/test/mlscript/interop/JSDerp.mls +++ b/hkmc2/shared/src/test/mlscript/interop/JSDerp.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/interop/Null.mls b/hkmc2/shared/src/test/mlscript/interop/Null.mls index 5e1f0c24b5..e0d6f73216 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Null.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Null.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/interop/Number.mls b/hkmc2/shared/src/test/mlscript/interop/Number.mls index 3773c0a80d..92045a2952 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Number.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Number.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/interop/Objects.mls b/hkmc2/shared/src/test/mlscript/interop/Objects.mls index 5fd07cbc93..9e9438fce1 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Objects.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Objects.mls @@ -1,13 +1,14 @@ +:. :js let rcd = { x: 0, y: undefined, z: null} //│ rcd = {x: 0, y: undefined, z: null} -rcd.constructor +rcd!constructor //│ = class Object -typeof(rcd.constructor) +typeof(rcd!constructor) //│ = "function" Object.entries of rcd @@ -30,23 +31,23 @@ Object.entries of Foo Object.entries of foo //│ = [["x", 1], ["y", 2], ["a", 3]] -Foo.name +Foo!name //│ = "Foo" -Foo.class.name +Foo!class!name //│ = "Foo" -foo.constructor +foo!constructor //│ = class Foo // ! will be changed by minification -foo.constructor.name +foo!constructor!name //│ = "Foo" typeof(foo) //│ = "object" -typeof(foo.constructor) +typeof(foo!constructor) //│ = "function" diff --git a/hkmc2/shared/src/test/mlscript/interop/Symbols.mls b/hkmc2/shared/src/test/mlscript/interop/Symbols.mls index aa5046983e..6401d565d7 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Symbols.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Symbols.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/interop/Virtualization.mls b/hkmc2/shared/src/test/mlscript/interop/Virtualization.mls index 915c2beed9..ff8c13f0cc 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Virtualization.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Virtualization.mls @@ -1,3 +1,4 @@ +:. :js @@ -6,12 +7,26 @@ js.try_catch(() => 123, id) //│ = 123 +:e js.try_catch(() => 123(), id) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.11: js.try_catch(() => 123(), id) +//│ ║ ^^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.11: js.try_catch(() => 123(), id) +//│ ╙── ^^^ //│ = TypeError: 123 is not a function +:e js.try_catch of () => 123() case { TypeError then 666, e then throw e } +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.22: () => 123() +//│ ║ ^^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.22: () => 123() +//│ ╙── ^^^ //│ = 666 :re diff --git a/hkmc2/shared/src/test/mlscript/lifter/ClassWithCompanion.mls b/hkmc2/shared/src/test/mlscript/lifter/ClassWithCompanion.mls index 11dea82494..53c2ba0228 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ClassWithCompanion.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ClassWithCompanion.mls @@ -1,3 +1,4 @@ +:.. :. @@ -9,7 +10,7 @@ fun foo(x) = val empty = C(123) C.empty //│ ╔══[WARNING] Modules are not yet lifted. -//│ ║ l.8: module C with +//│ ║ l.9: module C with //│ ╙── ^ foo(10).get @@ -23,7 +24,7 @@ fun foo(x) = val empty = new C C.empty //│ ╔══[WARNING] Modules are not yet lifted. -//│ ║ l.22: module C with +//│ ║ l.23: module C with //│ ╙── ^ foo(10).get diff --git a/hkmc2/shared/src/test/mlscript/lifter/CompanionsInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/CompanionsInFun.mls index 353c1d9b18..256f6f47bb 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/CompanionsInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/CompanionsInFun.mls @@ -1,3 +1,4 @@ +:.. :. @@ -9,8 +10,8 @@ fun f(x) = module A fun g = new A //│ ╔══[WARNING] Modules are not yet lifted. -//│ ║ l.9: module A -//│ ╙── ^ +//│ ║ l.10: module A +//│ ╙── ^ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let g; //│ define g as fun g⁰(A$) { diff --git a/hkmc2/shared/src/test/mlscript/lifter/CurriedClassInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/CurriedClassInFun.mls index 863e1f53eb..6d8e674fcc 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/CurriedClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/CurriedClassInFun.mls @@ -1,3 +1,4 @@ +:.. :. diff --git a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls index 38b485aa7f..d6bbb7d6c7 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls @@ -1,3 +1,4 @@ +:.. :. // NOTE: due to conflict between flow analysis and lifter :flowBasedOpt off diff --git a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls index aa6ce6560d..3605ce18f8 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls @@ -1,3 +1,4 @@ +:.. :. @@ -130,12 +131,12 @@ x + y data class Tuple(a, b) -fun f(used1) = - fun g1(used2) = +fun f(used1: Int) = + fun g1(used2: Int) = fun h() = set used1 = 10 set used2 = 100 - fun i() = used1 + used2 + fun i(): Int = used1 + used2 Tuple(h, i) fun g22() = used1 Tuple(g1(10), g22) diff --git a/hkmc2/shared/src/test/mlscript/lifter/FunInMethod.mls b/hkmc2/shared/src/test/mlscript/lifter/FunInMethod.mls index 4b9f5034e3..ebba796d18 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/FunInMethod.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/FunInMethod.mls @@ -1,3 +1,4 @@ +:.. :. :expect 6 diff --git a/hkmc2/shared/src/test/mlscript/lifter/Imports.mls b/hkmc2/shared/src/test/mlscript/lifter/Imports.mls index e34c1d1402..320a87d14b 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/Imports.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/Imports.mls @@ -1,3 +1,4 @@ +:.. :. import "../../mlscript-compile/Option.mls" diff --git a/hkmc2/shared/src/test/mlscript/lifter/Labels.mls b/hkmc2/shared/src/test/mlscript/lifter/Labels.mls index b561387edc..6791ddddbd 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/Labels.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/Labels.mls @@ -1,3 +1,4 @@ +:.. :. diff --git a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls index eb9b30886d..c600616795 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls @@ -1,3 +1,4 @@ +:.. :. // NOTE: due to conflict between flow analysis and lifter :flowBasedOpt off @@ -15,7 +16,7 @@ fun foo(x, y) = x + y + test M.foo() //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.11: object M with +//│ ║ l.12: object M with //│ ╙── ^ :expect 14 @@ -60,8 +61,9 @@ fun foo(y) = let cc = foo(10) - c0 = cc.0 - c1 = cc.1 +// Exercise writes to undeclared fields with checked dynamic access. +val c0: dyn = cc.0 +val c1: dyn = cc.1 //│ c0 = C //│ c1 = C //│ cc = [C, C] @@ -86,10 +88,10 @@ fun foo(y) = set y = 2 O //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.84: object O with +//│ ║ l.86: object O with //│ ╙── ^ -let o = foo(10) +val o: dyn = foo(10) //│ o = O :re @@ -108,7 +110,7 @@ fun foo(x, y) = fun foo3 = M.foo2() foo3 //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.104: object M with +//│ ║ l.106: object M with //│ ╙── ^ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let foo3; @@ -138,7 +140,7 @@ data class A(x) with fun bar = M.getB() bar //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.135: object M with +//│ ║ l.137: object M with //│ ╙── ^ :expect 2 @@ -195,7 +197,7 @@ fun f = foo f.a //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.191: object A with +//│ ║ l.193: object A with //│ ╙── ^ //│ = 2 @@ -274,7 +276,7 @@ fun foo(x) = val baz = x [x === O.bar, set x += 1 in [x === O.bar, x === O.baz], x === O.bar] //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.272: object O with +//│ ║ l.274: object O with //│ ╙── ^ foo(123) @@ -323,13 +325,13 @@ fun f = A.B.C f //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.320: object A with +//│ ║ l.322: object A with //│ ╙── ^ //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.321: object B with +//│ ║ l.323: object B with //│ ╙── ^ //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.322: object C +//│ ║ l.324: object C //│ ╙── ^ //│ = C @@ -342,10 +344,10 @@ fun f = A.B.C f //│ ╔══[WARNING] Modules are not yet lifted. -//│ ║ l.339: module A with +//│ ║ l.341: module A with //│ ╙── ^ //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.341: object C +//│ ║ l.343: object C //│ ╙── ^ //│ = C @@ -358,7 +360,7 @@ fun f = A.B.C f //│ ╔══[WARNING] Modules are not yet lifted. -//│ ║ l.355: module A with +//│ ║ l.357: module A with //│ ╙── ^ //│ = C diff --git a/hkmc2/shared/src/test/mlscript/lifter/Mutation.mls b/hkmc2/shared/src/test/mlscript/lifter/Mutation.mls index ed032aa929..170cae4b55 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/Mutation.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/Mutation.mls @@ -1,3 +1,4 @@ +:.. :. @@ -30,7 +31,7 @@ fun foo() = //│ set scope0$cap = new mut Capture$scope0¹(x); //│ set scope0$cap.x$0¹ = 1; //│ set bar$here = bar$⁰(scope0$cap); -//│ do xs⁰.push﹖(bar$here); +//│ do xs⁰.push⁰(bar$here); //│ set scope0$cap.x$0¹ = 2; //│ return runtime⁰.Unit⁰ //│ }; diff --git a/hkmc2/shared/src/test/mlscript/lifter/PatternInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/PatternInFun.mls index 301fba91dc..39b6c4c753 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/PatternInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/PatternInFun.mls @@ -1,3 +1,4 @@ +:.. :. diff --git a/hkmc2/shared/src/test/mlscript/meta/.mls b/hkmc2/shared/src/test/mlscript/meta/.mls index f2133a669e..bccc957f6b 100644 --- a/hkmc2/shared/src/test/mlscript/meta/.mls +++ b/hkmc2/shared/src/test/mlscript/meta/.mls @@ -1,3 +1,4 @@ +:.. :js console.log("Imported from '" + source.file.slice(source.file.lastIndexOf("/src/") + 1) + "'") 40 + 2 diff --git a/hkmc2/shared/src/test/mlscript/meta/BlockDiffTesting.mls b/hkmc2/shared/src/test/mlscript/meta/BlockDiffTesting.mls index d6e24d540f..efd5cc618d 100644 --- a/hkmc2/shared/src/test/mlscript/meta/BlockDiffTesting.mls +++ b/hkmc2/shared/src/test/mlscript/meta/BlockDiffTesting.mls @@ -1,3 +1,4 @@ +:.. :js diff --git a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls index c5c7391ddf..6025b27144 100644 --- a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls +++ b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls @@ -1,10 +1,11 @@ +:.. :js :ge source //│ ╔══[COMPILATION ERROR] Module 'source' is virtual (i.e., "compiler fiction"); cannot be used directly -//│ ║ l.5: source +//│ ║ l.6: source //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -12,25 +13,26 @@ source :ge js //│ ╔══[COMPILATION ERROR] Module 'js' is virtual (i.e., "compiler fiction"); cannot be used directly -//│ ║ l.13: js +//│ ║ l.14: js //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :ge js.oops -//│ ╔══[COMPILATION ERROR] Module 'js' does not contain member 'oops' -//│ ║ l.21: js.oops -//│ ╙── ^^^^^ -//│ ╔══[COMPILATION ERROR] Module 'js' is virtual (i.e., "compiler fiction"); cannot be used directly -//│ ║ l.21: js.oops -//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.22: js.oops +//│ ║ ^^^^^^^ +//│ ╟── Module 'js' does not contain member 'oops' +//│ ║ in ../decls/Prelude.mls +//│ ║ l.359: declare module js with +//│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge js.try_catch //│ ╔══[COMPILATION ERROR] Module 'js' is virtual (i.e., "compiler fiction"); cannot be used directly -//│ ║ l.31: js.try_catch +//│ ║ l.33: js.try_catch //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/meta/ImportedTest.mls b/hkmc2/shared/src/test/mlscript/meta/ImportedTest.mls index f8704a4d09..ba9ab1df04 100644 --- a/hkmc2/shared/src/test/mlscript/meta/ImportedTest.mls +++ b/hkmc2/shared/src/test/mlscript/meta/ImportedTest.mls @@ -1,3 +1,4 @@ +:.. fun hello() = "Hello!" diff --git a/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls b/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls index 7a78953375..650b85d694 100644 --- a/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls +++ b/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls @@ -1,3 +1,4 @@ +:.. :js diff --git a/hkmc2/shared/src/test/mlscript/meta/LocDebugging.mls b/hkmc2/shared/src/test/mlscript/meta/LocDebugging.mls index ba30eecea3..8267ab6678 100644 --- a/hkmc2/shared/src/test/mlscript/meta/LocDebugging.mls +++ b/hkmc2/shared/src/test/mlscript/meta/LocDebugging.mls @@ -1,3 +1,4 @@ +:.. :js :loc diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 76baf82789..a56d8b8f72 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.193: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -141,15 +141,15 @@ new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.173: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.177: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.174: fun join(...separators): Str +//│ ║ l.178: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.175: fun reverse(): Array[T] +//│ ║ l.179: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.176: fun sort(...compare): Array[T] +//│ ║ l.180: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.177: ... (more lines omitted) ... +//│ ║ l.181: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2] @@ -160,15 +160,15 @@ new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.173: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.177: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.174: fun join(...separators): Str +//│ ║ l.178: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.175: fun reverse(): Array[T] +//│ ║ l.179: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.176: fun sort(...compare): Array[T] +//│ ║ l.180: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.177: ... (more lines omitted) ... +//│ ║ l.181: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.207: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.211: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 1bd0768b0d..f461fc3cf2 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.193: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls index a6ce875201..e885878109 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls @@ -99,7 +99,7 @@ pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Int' does not contain member 'toUpperCase' //│ ║ in ../decls/Prelude.mls -//│ ║ l.140: val length: Int +//│ ║ l.143: val length: Int //│ ╙── ^^^ // Conjunction produces a pair, not the string tested by its left operand. diff --git a/hkmc2/shared/src/test/mlscript/nofib/.mls b/hkmc2/shared/src/test/mlscript/nofib/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/nofib/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/nofib/ansi.mls b/hkmc2/shared/src/test/mlscript/nofib/ansi.mls index 04aee5c4e8..599a33ef1c 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/ansi.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/ansi.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/ansi.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/atom.mls b/hkmc2/shared/src/test/mlscript/nofib/atom.mls index 40f30ace6e..fc5597befe 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/atom.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/atom.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/atom.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/awards.mls b/hkmc2/shared/src/test/mlscript/nofib/awards.mls index e3211acff9..998d675c70 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/awards.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/awards.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/awards.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/banner.mls b/hkmc2/shared/src/test/mlscript/nofib/banner.mls index 98a42f1d4c..5ceb25f203 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/banner.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/banner.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/banner.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/boyer.mls b/hkmc2/shared/src/test/mlscript/nofib/boyer.mls index b660f42654..33a9aa5abe 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/boyer.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/boyer.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/boyer.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls b/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls index 078957f0ec..8b361e61a5 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/boyer2.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/calendar.mls b/hkmc2/shared/src/test/mlscript/nofib/calendar.mls index 886b255109..5e74a9a5eb 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/calendar.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/calendar.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/calendar.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/cichelli.mls b/hkmc2/shared/src/test/mlscript/nofib/cichelli.mls index 8fd8eeb4d5..71a72cfe52 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/cichelli.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/cichelli.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/cichelli.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/circsim.mls b/hkmc2/shared/src/test/mlscript/nofib/circsim.mls index 76365dadc2..64f2263db8 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/circsim.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/circsim.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/circsim.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/clausify.mls b/hkmc2/shared/src/test/mlscript/nofib/clausify.mls index 3a82414420..55237e49f7 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/clausify.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/clausify.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/clausify.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/constraints.mls b/hkmc2/shared/src/test/mlscript/nofib/constraints.mls index 64f5a4d2de..d160e239ee 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/constraints.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/constraints.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/constraints.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/cryptarithm1.mls b/hkmc2/shared/src/test/mlscript/nofib/cryptarithm1.mls index 009cd5ae60..74cda05d56 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/cryptarithm1.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/cryptarithm1.mls @@ -1,3 +1,4 @@ +:. :js :re diff --git a/hkmc2/shared/src/test/mlscript/nofib/cryptarithm2.mls b/hkmc2/shared/src/test/mlscript/nofib/cryptarithm2.mls index a1d12ba3eb..300b73e91e 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/cryptarithm2.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/cryptarithm2.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/cryptarithm2.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/cse.mls b/hkmc2/shared/src/test/mlscript/nofib/cse.mls index 43d678f089..f7cfd4fc67 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/cse.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/cse.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/cse.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/eliza.mls b/hkmc2/shared/src/test/mlscript/nofib/eliza.mls index 15b4b391aa..4499060cde 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/eliza.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/eliza.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/eliza.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/fish.mls b/hkmc2/shared/src/test/mlscript/nofib/fish.mls index d4a51d64ba..45e9490f98 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/fish.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/fish.mls @@ -1,3 +1,4 @@ +:. :js // This is caused by the implementation of `+:` in `NofibPrelude.mls`. Replacing diff --git a/hkmc2/shared/src/test/mlscript/nofib/gcd.mls b/hkmc2/shared/src/test/mlscript/nofib/gcd.mls index bc82435615..14a0850494 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/gcd.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/gcd.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/nofib/integer.mls b/hkmc2/shared/src/test/mlscript/nofib/integer.mls index d72e8091fe..e506c76508 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/integer.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/integer.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/integer.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/knights.mls b/hkmc2/shared/src/test/mlscript/nofib/knights.mls index d76a7cc8ca..64654de69e 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/knights.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/knights.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/knights.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/lambda.mls b/hkmc2/shared/src/test/mlscript/nofib/lambda.mls index 81a8b57e06..7cf077e86d 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/lambda.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/lambda.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/lambda.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/last-piece.mls b/hkmc2/shared/src/test/mlscript/nofib/last-piece.mls index 2098253fca..710ce2cae6 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/last-piece.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/last-piece.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/lastpiece.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/lcss.mls b/hkmc2/shared/src/test/mlscript/nofib/lcss.mls index a8bbbc5604..d3a1c3eb1e 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/lcss.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/lcss.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/nofib/life.mls b/hkmc2/shared/src/test/mlscript/nofib/life.mls index d8e640e840..79099b2875 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/life.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/life.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/life.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/mandel.mls b/hkmc2/shared/src/test/mlscript/nofib/mandel.mls index 8fd4ce158f..1353b48699 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/mandel.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/mandel.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/nofib/mandel2.mls b/hkmc2/shared/src/test/mlscript/nofib/mandel2.mls index 66811b5c2c..49f981d6b3 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/mandel2.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/mandel2.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/mandel2.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/mate.mls b/hkmc2/shared/src/test/mlscript/nofib/mate.mls index 0c6a7c5cdb..90ea407deb 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/mate.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/mate.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/nofib/minimax.mls b/hkmc2/shared/src/test/mlscript/nofib/minimax.mls index d1cf1b9595..40afeaa1a1 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/minimax.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/minimax.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/nofib/para.mls b/hkmc2/shared/src/test/mlscript/nofib/para.mls index 2bef06b48d..5e6a336ee0 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/para.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/para.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/nofib/power.mls b/hkmc2/shared/src/test/mlscript/nofib/power.mls index 3c47a30052..1864eb922a 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/power.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/power.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/power.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/pretty.mls b/hkmc2/shared/src/test/mlscript/nofib/pretty.mls index 0447b4a378..4142663e6c 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/pretty.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/pretty.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/pretty.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/primetest.mls b/hkmc2/shared/src/test/mlscript/nofib/primetest.mls index f4c545232d..c3a997034b 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/primetest.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/primetest.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/nofib/puzzle.mls b/hkmc2/shared/src/test/mlscript/nofib/puzzle.mls index 9afd0ba071..dbda3a7a73 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/puzzle.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/puzzle.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/nofib/rsa.mls b/hkmc2/shared/src/test/mlscript/nofib/rsa.mls index 08c20bbecf..54f9a5c35a 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/rsa.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/rsa.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/rsa.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/scc.mls b/hkmc2/shared/src/test/mlscript/nofib/scc.mls index d7fc427493..85c85630b7 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/scc.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/scc.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/nofib/secretary.mls b/hkmc2/shared/src/test/mlscript/nofib/secretary.mls index 796de90420..e5d7ef6d69 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/secretary.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/secretary.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/secretary.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/sorting.mls b/hkmc2/shared/src/test/mlscript/nofib/sorting.mls index c0c4816737..094746cd24 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/sorting.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/sorting.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/sorting.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/sphere.mls b/hkmc2/shared/src/test/mlscript/nofib/sphere.mls index be049cf287..4cd705486d 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/sphere.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/sphere.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/nofib/sphere.mls" diff --git a/hkmc2/shared/src/test/mlscript/nofib/treejoin.mls b/hkmc2/shared/src/test/mlscript/nofib/treejoin.mls index 3fa18c0477..0ce568b9ef 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/treejoin.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/treejoin.mls @@ -1,3 +1,4 @@ +:. :js :re // nodejs stack size 8192 can handle this diff --git a/hkmc2/shared/src/test/mlscript/objbuf/.mls b/hkmc2/shared/src/test/mlscript/objbuf/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/objbuf/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/objbuf/ObjectBufferAllocator.mls b/hkmc2/shared/src/test/mlscript/objbuf/ObjectBufferAllocator.mls index bd7a6add7c..496c63f0ab 100644 --- a/hkmc2/shared/src/test/mlscript/objbuf/ObjectBufferAllocator.mls +++ b/hkmc2/shared/src/test/mlscript/objbuf/ObjectBufferAllocator.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/ObjectBuffer.mls" diff --git a/hkmc2/shared/src/test/mlscript/opt/.mls b/hkmc2/shared/src/test/mlscript/opt/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/opt/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/opt/AbortivePrefix.mls b/hkmc2/shared/src/test/mlscript/opt/AbortivePrefix.mls index 5561d8fd89..f327ea4ecd 100644 --- a/hkmc2/shared/src/test/mlscript/opt/AbortivePrefix.mls +++ b/hkmc2/shared/src/test/mlscript/opt/AbortivePrefix.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/AlmostTailCall.mls b/hkmc2/shared/src/test/mlscript/opt/AlmostTailCall.mls index 5627beb8f2..85b83203b9 100644 --- a/hkmc2/shared/src/test/mlscript/opt/AlmostTailCall.mls +++ b/hkmc2/shared/src/test/mlscript/opt/AlmostTailCall.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/BasicVarPropag.mls b/hkmc2/shared/src/test/mlscript/opt/BasicVarPropag.mls index 1d6bf7f7ea..70bc7d3a62 100644 --- a/hkmc2/shared/src/test/mlscript/opt/BasicVarPropag.mls +++ b/hkmc2/shared/src/test/mlscript/opt/BasicVarPropag.mls @@ -1,3 +1,4 @@ +:. :js open scope { locally } diff --git a/hkmc2/shared/src/test/mlscript/opt/CapturedVariableMutation.mls b/hkmc2/shared/src/test/mlscript/opt/CapturedVariableMutation.mls index fd3a137254..94e1594a58 100644 --- a/hkmc2/shared/src/test/mlscript/opt/CapturedVariableMutation.mls +++ b/hkmc2/shared/src/test/mlscript/opt/CapturedVariableMutation.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/CaseOfCase.mls b/hkmc2/shared/src/test/mlscript/opt/CaseOfCase.mls index 2d35074fa2..d91a709c75 100644 --- a/hkmc2/shared/src/test/mlscript/opt/CaseOfCase.mls +++ b/hkmc2/shared/src/test/mlscript/opt/CaseOfCase.mls @@ -1,3 +1,4 @@ +:. // * [test:case-of-case] :js @@ -217,7 +218,7 @@ prefixDependency(false) @inline -fun partlyKnownProducer(x) = if x is +fun partlyKnownProducer(x: Str) = if x is "" then 0 "1" then x.length "2" then 1 @@ -233,12 +234,13 @@ x => if partlyKnownProducer(x) is //│ let lambda; //│ @private //│ define lambda as fun lambda¹(x) { -//│ let inlinedVal; -//│ match x +//│ let x1, inlinedVal; +//│ set x1 = (x as! Str⁰); +//│ match x1 //│ "" => //│ return Predef⁰.print⁰("Z") //│ "1" => -//│ set inlinedVal = x.length﹖; +//│ set inlinedVal = x1.length⁰; //│ match inlinedVal //│ 0 => //│ return Predef⁰.print⁰("Z") @@ -306,13 +308,14 @@ x => if partlyKnownProducer(x) is //│ let lambda; //│ @private //│ define lambda as fun lambda³(x) { -//│ let inlinedVal; -//│ match x +//│ let x1, inlinedVal; +//│ set x1 = (x as! Str⁰); +//│ match x1 //│ "" => //│ set inlinedVal = 0; //│ end //│ "1" => -//│ set inlinedVal = x.length﹖; +//│ set inlinedVal = x1.length⁰; //│ end //│ "2" => //│ set inlinedVal = 1; @@ -342,12 +345,13 @@ x => if partlyKnownProducer(x) is //│ let lambda; //│ @private //│ define lambda as fun lambda⁴(x) { -//│ let inlinedVal; -//│ match x +//│ let x1, inlinedVal; +//│ set x1 = (x as! Str⁰); +//│ match x1 //│ "" => //│ return Predef⁰.print⁰("Z") //│ "1" => -//│ set inlinedVal = x.length﹖; +//│ set inlinedVal = x1.length⁰; //│ match inlinedVal //│ 0 => //│ return Predef⁰.print⁰("Z") diff --git a/hkmc2/shared/src/test/mlscript/opt/ConstantFolding.mls b/hkmc2/shared/src/test/mlscript/opt/ConstantFolding.mls index 497ac0b432..3d0ce0fad6 100644 --- a/hkmc2/shared/src/test/mlscript/opt/ConstantFolding.mls +++ b/hkmc2/shared/src/test/mlscript/opt/ConstantFolding.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/DeadAssignmentRemoval.mls b/hkmc2/shared/src/test/mlscript/opt/DeadAssignmentRemoval.mls index b86a3e8af6..276b14aa89 100644 --- a/hkmc2/shared/src/test/mlscript/opt/DeadAssignmentRemoval.mls +++ b/hkmc2/shared/src/test/mlscript/opt/DeadAssignmentRemoval.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/DeadBranchRemoval.mls b/hkmc2/shared/src/test/mlscript/opt/DeadBranchRemoval.mls index c1f5fb83ce..62c05d1e9a 100644 --- a/hkmc2/shared/src/test/mlscript/opt/DeadBranchRemoval.mls +++ b/hkmc2/shared/src/test/mlscript/opt/DeadBranchRemoval.mls @@ -1,3 +1,4 @@ +:. :js #config(deadBranchRemoval: true) diff --git a/hkmc2/shared/src/test/mlscript/opt/DeadConstructorElim.mls b/hkmc2/shared/src/test/mlscript/opt/DeadConstructorElim.mls index 398710616f..1b425384e1 100644 --- a/hkmc2/shared/src/test/mlscript/opt/DeadConstructorElim.mls +++ b/hkmc2/shared/src/test/mlscript/opt/DeadConstructorElim.mls @@ -1,3 +1,4 @@ +:. :js :flowBasedOpt debugDce @@ -314,7 +315,7 @@ s(new O) //│ dead-constructor-elim > eliminable: Call(member:A²‹term:A⁰›, [[Select(y⁰, Ident(x))]]) //│ dead-constructor-elim > <<< dead-constructor-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let tmp; define s⁰ as fun s¹(y) { do y.x﹖; return 2 }; set tmp = new O⁰(); return s¹(tmp) +//│ let tmp; define s⁰ as fun s¹(y) { do y.x¹; return 2 }; set tmp = new O⁰(); return s¹(tmp) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > get x //│ = 2 @@ -332,7 +333,7 @@ t(new O) //│ dead-constructor-elim > <<< dead-constructor-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ let tmp; -//│ define t⁰ as fun t¹(y) { do y.z﹖; do y.x﹖; do y!"z"; return 2 }; +//│ define t⁰ as fun t¹(y) { do y.z⁰; do y.x¹; do y!"z"; return 2 }; //│ set tmp = new O⁰(); //│ return t¹(tmp) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -356,8 +357,8 @@ u(new O) //│ let tmp; //│ define u⁰ as fun u¹(y) { //│ let b; -//│ do y.z﹖; -//│ do y.x﹖; +//│ do y.z⁰; +//│ do y.x¹; //│ set b = A⁰(undefined); //│ match b //│ A¹ => @@ -382,7 +383,7 @@ v(new O) //│ dead-constructor-elim > >>> dead-constructor-elim results >>> //│ dead-constructor-elim > <<< dead-constructor-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let tmp; define v⁰ as fun v¹(y) { return [y.z﹖, y.x﹖] }; set tmp = new O⁰(); return v¹(tmp) +//│ let tmp; define v⁰ as fun v¹(y) { return [y.z⁰, y.x¹] }; set tmp = new O⁰(); return v¹(tmp) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > get z //│ > get x @@ -402,7 +403,7 @@ w(new O) //│ let tmp; //│ define w⁰ as fun w¹(y) { //│ let b; -//│ set b = [y.z﹖, 1]; +//│ set b = [y.z⁰, 1]; //│ match b //│ Array(2) => //│ do runtime⁰.Tuple⁰.get⁰(b, 0); @@ -443,17 +444,20 @@ consume() //│ }; //│ @data //│ define Observed⁰ as class Observed² { -//│ val x¹; +//│ val x²; //│ constructor Observed¹(x) { -//│ define x¹ as val x² = x; +//│ define x² as val x³ = x; //│ end //│ } //│ }; //│ define Consumer¹ as class Consumer² { -//│ private val x³; +//│ private val x⁴; //│ constructor Consumer⁰(x) { -//│ set Consumer².this.x³ = x; -//│ match Consumer².this.x³ +//│ let scrut; +//│ @private +//│ define x⁵ as val x⁴ = x; +//│ set scrut = Consumer².this.x⁴; +//│ match scrut //│ Observed² => //│ end //│ else @@ -500,8 +504,9 @@ capture() //│ define capture⁰ as fun capture¹() { //│ let instance; //│ set instance = new Capturing¹(0, undefined); -//│ return instance.get﹖() +//│ return instance.get²() //│ }; //│ return capture¹() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] Expected: '[1, 2]', got: '()' +//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. +//│ ═══[RUNTIME ERROR] Expected: '[1, 2]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/opt/DeadObjRemoval.mls b/hkmc2/shared/src/test/mlscript/opt/DeadObjRemoval.mls index a9130444fb..333d391c7d 100644 --- a/hkmc2/shared/src/test/mlscript/opt/DeadObjRemoval.mls +++ b/hkmc2/shared/src/test/mlscript/opt/DeadObjRemoval.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/DeadSelRemoval.mls b/hkmc2/shared/src/test/mlscript/opt/DeadSelRemoval.mls index 813c14dbd2..e2d82fd7fe 100644 --- a/hkmc2/shared/src/test/mlscript/opt/DeadSelRemoval.mls +++ b/hkmc2/shared/src/test/mlscript/opt/DeadSelRemoval.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/DeadStatRemoval.mls b/hkmc2/shared/src/test/mlscript/opt/DeadStatRemoval.mls index fead8cfc85..f91a5073a5 100644 --- a/hkmc2/shared/src/test/mlscript/opt/DeadStatRemoval.mls +++ b/hkmc2/shared/src/test/mlscript/opt/DeadStatRemoval.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/DeadVarRemoval.mls b/hkmc2/shared/src/test/mlscript/opt/DeadVarRemoval.mls index e21bf6bc6e..e42bf4a249 100644 --- a/hkmc2/shared/src/test/mlscript/opt/DeadVarRemoval.mls +++ b/hkmc2/shared/src/test/mlscript/opt/DeadVarRemoval.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/EtaExpansion.mls b/hkmc2/shared/src/test/mlscript/opt/EtaExpansion.mls index 5c55b55ecb..4794e58e3c 100644 --- a/hkmc2/shared/src/test/mlscript/opt/EtaExpansion.mls +++ b/hkmc2/shared/src/test/mlscript/opt/EtaExpansion.mls @@ -1,3 +1,4 @@ +:. :js :flowBasedOpt debugEta @@ -125,10 +126,10 @@ g(f, 1)(2) + g(h, 3)(4) :w fun h(x) = @affine(1) y => x + y //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.126: fun h(x) = @affine(1) y => x + y +//│ ║ l.127: fun h(x) = @affine(1) y => x + y //│ ║ ^^^^^^^^^^ //│ ╟── This annotation is not supported on function literal terms. -//│ ║ l.126: fun h(x) = @affine(1) y => x + y +//│ ║ l.127: fun h(x) = @affine(1) y => x + y //│ ╙── ^^^^^^^^^^ //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > <<< eta-expansion targets shapes <<< @@ -136,7 +137,7 @@ fun h(x) = @affine(1) y => x + y :w @affine(0) val v = 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.137: @affine(0) val v = 1 +//│ ║ l.138: @affine(0) val v = 1 //│ ╙── ^^^^^^^^^^ //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > <<< eta-expansion targets shapes <<< @@ -145,10 +146,10 @@ fun h(x) = @affine(1) y => x + y :w fun h(x) = @affine(0) x //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.146: fun h(x) = @affine(0) x +//│ ║ l.147: fun h(x) = @affine(0) x //│ ║ ^^^^^^^^^^ //│ ╟── This annotation is not supported on reference terms. -//│ ║ l.146: fun h(x) = @affine(0) x +//│ ║ l.147: fun h(x) = @affine(0) x //│ ╙── ^ //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > <<< eta-expansion targets shapes <<< diff --git a/hkmc2/shared/src/test/mlscript/opt/IllFormedAccesses.mls b/hkmc2/shared/src/test/mlscript/opt/IllFormedAccesses.mls index 767549b7be..5a99ab0024 100644 --- a/hkmc2/shared/src/test/mlscript/opt/IllFormedAccesses.mls +++ b/hkmc2/shared/src/test/mlscript/opt/IllFormedAccesses.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/InfiniteInlining.mls b/hkmc2/shared/src/test/mlscript/opt/InfiniteInlining.mls index d8c281f8e5..7bcb47ae8a 100644 --- a/hkmc2/shared/src/test/mlscript/opt/InfiniteInlining.mls +++ b/hkmc2/shared/src/test/mlscript/opt/InfiniteInlining.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/InlineAcrossFiles.mls b/hkmc2/shared/src/test/mlscript/opt/InlineAcrossFiles.mls index b22d3bed34..5dd555df6e 100644 --- a/hkmc2/shared/src/test/mlscript/opt/InlineAcrossFiles.mls +++ b/hkmc2/shared/src/test/mlscript/opt/InlineAcrossFiles.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/InlineModuleMethods.mls b/hkmc2/shared/src/test/mlscript/opt/InlineModuleMethods.mls index 5e7ff89b51..0e2e2bce70 100644 --- a/hkmc2/shared/src/test/mlscript/opt/InlineModuleMethods.mls +++ b/hkmc2/shared/src/test/mlscript/opt/InlineModuleMethods.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/InlineNestedFunctionsAcrossFiles.mls b/hkmc2/shared/src/test/mlscript/opt/InlineNestedFunctionsAcrossFiles.mls index 0dc8f581f6..868eea7b21 100644 --- a/hkmc2/shared/src/test/mlscript/opt/InlineNestedFunctionsAcrossFiles.mls +++ b/hkmc2/shared/src/test/mlscript/opt/InlineNestedFunctionsAcrossFiles.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/LoopOpt.mls b/hkmc2/shared/src/test/mlscript/opt/LoopOpt.mls index 943467ae25..d7c3934cec 100644 --- a/hkmc2/shared/src/test/mlscript/opt/LoopOpt.mls +++ b/hkmc2/shared/src/test/mlscript/opt/LoopOpt.mls @@ -1,3 +1,4 @@ +:. :js @@ -142,7 +143,9 @@ fun loopReturnDoesNotPolluteExit(flag) = //│ let x; //│ set x = 1; //│ loop lbl: -//│ match flag +//│ let scrut; +//│ set scrut = flag; +//│ match scrut //│ true => //│ set x = 2; //│ return x diff --git a/hkmc2/shared/src/test/mlscript/opt/MatchSimplification.mls b/hkmc2/shared/src/test/mlscript/opt/MatchSimplification.mls index 41778b5ca5..3a29fb3f70 100644 --- a/hkmc2/shared/src/test/mlscript/opt/MatchSimplification.mls +++ b/hkmc2/shared/src/test/mlscript/opt/MatchSimplification.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/NestedLoopOpt.mls b/hkmc2/shared/src/test/mlscript/opt/NestedLoopOpt.mls index bdeec7674d..7b3e751d6b 100644 --- a/hkmc2/shared/src/test/mlscript/opt/NestedLoopOpt.mls +++ b/hkmc2/shared/src/test/mlscript/opt/NestedLoopOpt.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/NoCaseOfCase.mls b/hkmc2/shared/src/test/mlscript/opt/NoCaseOfCase.mls index a3a3656da6..8ec56002e5 100644 --- a/hkmc2/shared/src/test/mlscript/opt/NoCaseOfCase.mls +++ b/hkmc2/shared/src/test/mlscript/opt/NoCaseOfCase.mls @@ -1,3 +1,4 @@ +:. :js @@ -35,7 +36,7 @@ fun foo(x) = false //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ define foo⁰ as fun foo¹(x) { -//│ let y, f, lambda; +//│ let y, f, scrut, lambda; //│ define lambda as fun lambda⁰(w) { //│ set y = w; //│ return runtime⁰.Unit⁰ @@ -45,14 +46,15 @@ fun foo(x) = //│ true => //│ set y = "ok"; //│ do f("nope"); -//│ match y -//│ "ok" => -//│ return true -//│ else -//│ return false //│ end //│ else //│ set y = "nope"; +//│ end +//│ set scrut = y; +//│ match scrut +//│ "ok" => +//│ return true +//│ else //│ return false //│ end //│ }; diff --git a/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls b/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls index 3d3594d83e..728d776c6a 100644 --- a/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls +++ b/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/PureCallPropagation.mls b/hkmc2/shared/src/test/mlscript/opt/PureCallPropagation.mls index f7ef55b4b4..2876ff84b0 100644 --- a/hkmc2/shared/src/test/mlscript/opt/PureCallPropagation.mls +++ b/hkmc2/shared/src/test/mlscript/opt/PureCallPropagation.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/TailRecOpt.mls b/hkmc2/shared/src/test/mlscript/opt/TailRecOpt.mls index 278f3e5240..749ac70b36 100644 --- a/hkmc2/shared/src/test/mlscript/opt/TailRecOpt.mls +++ b/hkmc2/shared/src/test/mlscript/opt/TailRecOpt.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/UnspecifiedBehavior.mls b/hkmc2/shared/src/test/mlscript/opt/UnspecifiedBehavior.mls index 1d8fcd7692..0c95e40334 100644 --- a/hkmc2/shared/src/test/mlscript/opt/UnspecifiedBehavior.mls +++ b/hkmc2/shared/src/test/mlscript/opt/UnspecifiedBehavior.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/ValPropag.mls b/hkmc2/shared/src/test/mlscript/opt/ValPropag.mls index e5f404bf65..6b979dd7fb 100644 --- a/hkmc2/shared/src/test/mlscript/opt/ValPropag.mls +++ b/hkmc2/shared/src/test/mlscript/opt/ValPropag.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/VarPropagStress.mls b/hkmc2/shared/src/test/mlscript/opt/VarPropagStress.mls index b067592f04..7b78a384bb 100644 --- a/hkmc2/shared/src/test/mlscript/opt/VarPropagStress.mls +++ b/hkmc2/shared/src/test/mlscript/opt/VarPropagStress.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/opt/WorkerWrapper.mls b/hkmc2/shared/src/test/mlscript/opt/WorkerWrapper.mls index cdc91a0a57..8fd2e329aa 100644 --- a/hkmc2/shared/src/test/mlscript/opt/WorkerWrapper.mls +++ b/hkmc2/shared/src/test/mlscript/opt/WorkerWrapper.mls @@ -1,3 +1,4 @@ +:. :js open annotations { noInline } diff --git a/hkmc2/shared/src/test/mlscript/std/.mls b/hkmc2/shared/src/test/mlscript/std/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/std/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls index c960aec027..6a95715a08 100644 --- a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/FingerTreeList.mls" @@ -34,19 +35,19 @@ ys |> ith(8) ys |> ith(9) //│ = None -let ys = (ys |> popFront).rest +let ys = if ys |> popFront is {rest: rest} then rest //│ ys = [2, 3, 1, 2, 3, 1, 2, 3] -let ys = (ys |> popBack).rest +let ys = if ys |> popBack is {rest: rest} then rest //│ ys = [2, 3, 1, 2, 3, 1, 2] -let ys = (ys |> popBack).rest +let ys = if ys |> popBack is {rest: rest} then rest //│ ys = [2, 3, 1, 2, 3, 1] -let ys = (ys |> popBack).rest +let ys = if ys |> popBack is {rest: rest} then rest //│ ys = [2, 3, 1, 2, 3] -let ys = (ys |> popFront).rest +let ys = if ys |> popFront is {rest: rest} then rest //│ ys = [3, 1, 2, 3] -let ys = (ys |> popFront).rest +let ys = if ys |> popFront is {rest: rest} then rest //│ ys = [1, 2, 3] -let ys = (ys |> popFront).rest +let ys = if ys |> popFront is {rest: rest} then rest //│ ys = [2, 3] let ys = xs ++ xs ++ xs @@ -99,13 +100,13 @@ let xs = FingerTreeList.mk(1, 2, 3) if xs is [a, ...ls] then [a, ls] //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let element0$; -//│ if (runtime.Tuple.isArrayLike(xs2) && xs2.length >= 1) { -//│ let xs3, inlinedVal38, tmp16; -//│ element0$ = runtime.Tuple.get(xs2, 0); -//│ xs3 = xs2; -//│ tmp16 = xs3.length - 0; -//│ inlinedVal38 = runtime.safeCall(xs3.slice(1, tmp16)); +//│ let scrut, element0$; +//│ scrut = xs2; +//│ if (runtime.Tuple.isArrayLike(scrut) && scrut.length >= 1) { +//│ let inlinedVal38, tmp16; +//│ element0$ = runtime.Tuple.get(scrut, 0); +//│ tmp16 = scrut.length - 0; +//│ inlinedVal38 = runtime.safeCall(scrut.slice(1, tmp16)); //│ return globalThis.Object.freeze([ //│ element0$, //│ inlinedVal38 @@ -132,16 +133,16 @@ fun popUntilEmpty(lft, acc) = [] then acc :sjs -fun popByIndex(start, end, acc, lft) = +fun popByIndex(start, end, acc, lft: Indexable) = if start >= end then acc else popByIndex(start + 1, end, [...acc, lft.at(start)], lft) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let popByIndex; //│ popByIndex = function popByIndex(start, end, acc, lft) { //│ loopLabel: while (true) { -//│ let scrut, tmp16, tmp17, tmp18; -//│ scrut = start >= end; -//│ if (scrut === true) { +//│ let scrut1, tmp16, tmp17, tmp18; +//│ scrut1 = start >= end; +//│ if (scrut1 === true) { //│ return acc //│ } //│ tmp16 = start + 1; diff --git a/hkmc2/shared/src/test/mlscript/std/IterTest.mls b/hkmc2/shared/src/test/mlscript/std/IterTest.mls index 73f1be4d76..7da62e30b2 100644 --- a/hkmc2/shared/src/test/mlscript/std/IterTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/IterTest.mls @@ -1,3 +1,4 @@ +:. :js @@ -28,7 +29,7 @@ arr //│ = ["0!", "1!", "3!"] ["abc", "01234"] - |> Iter.withMap of _.toUpperCase() + |> Iter.withMap of (s: Str) => s.toUpperCase() |> Iter.flattening |> Iter.withFilter of _ != "0" |> Iter.toArray diff --git a/hkmc2/shared/src/test/mlscript/std/LazyArrayTest.mls b/hkmc2/shared/src/test/mlscript/std/LazyArrayTest.mls index a53454d991..24c33bfc48 100644 --- a/hkmc2/shared/src/test/mlscript/std/LazyArrayTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/LazyArrayTest.mls @@ -1,8 +1,10 @@ +:. :js import "../../mlscript-compile/LazyArray.mls" import "../../mlscript-compile/MutMap.mls" +open LazyArray { equals } open LazyArray // Splices diff --git a/hkmc2/shared/src/test/mlscript/std/MutMapTest.mls b/hkmc2/shared/src/test/mlscript/std/MutMapTest.mls index 7bb835cc73..5462dcba4a 100644 --- a/hkmc2/shared/src/test/mlscript/std/MutMapTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/MutMapTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/MutMap.mls" diff --git a/hkmc2/shared/src/test/mlscript/std/PredefTest.mls b/hkmc2/shared/src/test/mlscript/std/PredefTest.mls index c82205e440..fee9a43103 100644 --- a/hkmc2/shared/src/test/mlscript/std/PredefTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/PredefTest.mls @@ -1,3 +1,4 @@ +:. :js @@ -9,9 +10,13 @@ id(123) :e :re id() -//│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 0 -//│ ║ l.11: id() -//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.12: id() +//│ ║ ^^^^ +//│ ╟── Function 'id' expected 1 argument, but got 0 +//│ ║ in ../../mlscript-compile/Predef.mls +//│ ║ l.19: fun id(x) = x +//│ ╙── ^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. @@ -183,14 +188,26 @@ foldr(tuple)(0, 1, 2, 3) :todo foldr(+)() -//│ ╔══[COMPILATION ERROR] Expected at least 1 arguments, got 0 -//│ ║ l.185: foldr(+)() -//│ ╙── ^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.190: foldr(+)() +//│ ║ ^^^^^^^^^^ +//│ ╟── Function 'foldr' expected 1 argument, but got 0 +//│ ║ in ../../mlscript-compile/Predef.mls +//│ ║ l.118: fun foldr[A](f: (A, A) -> A)(first: A, ...rest: Array[A]): A = +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.119: let len = rest.length +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.120: if len === 0 then first else... +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.121: let +//│ ║ ^^^^^ +//│ ║ l.122: ... (more lines omitted) ... +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. // An alternative implementation of foldr -fun foldr(f)(first, ...rest) = +fun foldr(f)(first, ...rest: Array) = let len = rest.length if len == 0 then first else... let @@ -218,12 +235,28 @@ fold(+) of import "../../mlscript-compile/Iter.mls" +:e Iter.joined(_, "") of [ "0" "1" "2" "3" ] +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.239: Iter.joined(_, "") of [ +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.240: "0" +//│ ║ ^^^^^ +//│ ║ l.241: "1" +//│ ║ ^^^^^ +//│ ║ l.242: "2" +//│ ║ ^^^^^ +//│ ║ l.243: ... (more lines omitted) ... +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── String literal cannot be called like a function. +//│ ║ in ../../mlscript-compile/Iter.mls +//│ ║ l.226: if next.done then "" else +//│ ╙── ^^ //│ = fun diff --git a/hkmc2/shared/src/test/mlscript/std/PrettyPrintTest.mls b/hkmc2/shared/src/test/mlscript/std/PrettyPrintTest.mls index 679abe04e4..1d9c1103eb 100644 --- a/hkmc2/shared/src/test/mlscript/std/PrettyPrintTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/PrettyPrintTest.mls @@ -1,7 +1,9 @@ +:. :js import "../../mlscript-compile/Rendering.mls" +open Rendering { render } open Rendering // We set `breakLength` to 74. With the `print` function and diff test comments @@ -195,14 +197,14 @@ let testMap = new Map([[testKey, testKey]]) show of testMap //│ > Map(1) {{x: 0, y: 0, z: 0} => {x: 0, y: 0, z: 0}} ┊ -set testKey.w = 0, show of testMap +set testKey!w = 0, show of testMap //│ > Map(1) {{x: 0, y: 0, z: 0, w: 0} => {x: 0, y: 0, z: 0, w: 0}} ┊ -set testKey.a = 42, show of testMap +set testKey!a = 42, show of testMap //│ > Map(1) {{x: 0, y: 0, z: 0, w: 0, a: 42} => {x: 0, y: 0, z: 0, w: 0, a: 42}} // TODO -set testKey.b = 404, show of testMap +set testKey!b = 404, show of testMap //│ > Map(1) { ┊ //│ > {x: 0, y: 0, z: 0, w: 0, a: 42, b: 404} => {x: 0, y: 0, z: 0, w: 0, a: 42, b: 404} //│ > } ┊ @@ -214,15 +216,15 @@ let testMap = new Map([[testKey, shortText]]) show of testMap //│ > Map(1) {{a: 1, b: 10, c: 100, d: 1000, e: 10000} => "Hello, world!"} ┊ -set testKey.x = 42, show of testMap +set testKey!x = 42, show of testMap //│ > Map(1) {{a: 1, b: 10, c: 100, d: 1000, e: 10000, x: 42} => "Hello, world!"} -set testKey.y = 100, show of testMap +set testKey!y = 100, show of testMap //│ > Map(1) { ┊ //│ > {a: 1, b: 10, c: 100, d: 1000, e: 10000, x: 42, y: 100} => "Hello, world!" //│ > } ┊ -set testKey.z = shortText, show of testMap +set testKey!z = shortText, show of testMap //│ > Map(1) { ┊ //│ > {a: 1, b: 10, c: 100, d: 1000, e: 10000, x: 42, y: 100, z: "Hello, world!"} => "Hello, world!" //│ > } ┊ diff --git a/hkmc2/shared/src/test/mlscript/std/RecordTest.mls b/hkmc2/shared/src/test/mlscript/std/RecordTest.mls index 7a70e9e7ed..9b4603bc1f 100644 --- a/hkmc2/shared/src/test/mlscript/std/RecordTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/RecordTest.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/Record.mls" @@ -25,8 +26,8 @@ let dst = import "../../mlscript-compile/Iter.mls" -fun steal(...fields_from) = - let from = fields_from.at(-1).from +fun steal(...fields_from: Array) = + let from = (fields_from.at(-1) as {from: Object}).from let fields = fields_from.slice(0, -1) let rcd = new mut Object fields Iter.each of f => set rcd.(f) = from.(f) diff --git a/hkmc2/shared/src/test/mlscript/std/StackTests.mls b/hkmc2/shared/src/test/mlscript/std/StackTests.mls index b5746514ba..f702ff1ea4 100644 --- a/hkmc2/shared/src/test/mlscript/std/StackTests.mls +++ b/hkmc2/shared/src/test/mlscript/std/StackTests.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/Stack.mls" diff --git a/hkmc2/shared/src/test/mlscript/syntax/.mls b/hkmc2/shared/src/test/mlscript/syntax/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/syntax/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/syntax/AbusiveSuspensions.mls b/hkmc2/shared/src/test/mlscript/syntax/AbusiveSuspensions.mls index 68c9b93c25..3cadc514af 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/AbusiveSuspensions.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/AbusiveSuspensions.mls @@ -1,3 +1,4 @@ +:. :js @@ -30,22 +31,22 @@ fun foo(expr1) = Ok(a) then... a //│ ╔══[PARSE ERROR] Expected start of expression in this position; found new line instead -//│ ║ l.28: if expr1 is +//│ ║ l.29: if expr1 is //│ ║ ^ -//│ ║ l.29: Error(b) then b +//│ ║ l.30: Error(b) then b //│ ╙── ^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (‹erroneous syntax›). -//│ ║ l.28: if expr1 is +//│ ║ l.29: if expr1 is //│ ║ ^ -//│ ║ l.29: Error(b) then b +//│ ║ l.30: Error(b) then b //│ ╙── ^^ //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.29: Error(b) then b +//│ ║ l.30: Error(b) then b //│ ╙── ^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.30: Ok(a) then... +//│ ║ l.31: Ok(a) then... //│ ║ ^^^^^^^^^^^^^ -//│ ║ l.31: a +//│ ║ l.32: a //│ ╙── ^^^ fun foo(expr1) = @@ -61,13 +62,13 @@ fun foo(expr1) = is Ok(a) then... a //│ ╔══[PARSE ERROR] Expected start of expression in this position; found 'is' keyword instead -//│ ║ l.60: is Error(b) then b +//│ ║ l.61: is Error(b) then b //│ ╙── ^^ //│ ╔══[PARSE ERROR] Unexpected identifier here -//│ ║ l.60: is Error(b) then b +//│ ║ l.61: is Error(b) then b //│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized term split (identifier) -//│ ║ l.59: if expr1 +//│ ║ l.60: if expr1 //│ ╙── ^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/syntax/BackslashApps.mls b/hkmc2/shared/src/test/mlscript/syntax/BackslashApps.mls index 9cffe89835..7fe2af2a92 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/BackslashApps.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/BackslashApps.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/syntax/BasicIfs.mls b/hkmc2/shared/src/test/mlscript/syntax/BasicIfs.mls index e9fc5c0d96..c4933b38f1 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/BasicIfs.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/BasicIfs.mls @@ -1,3 +1,4 @@ +:. :js diff --git a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls index 944f1761a5..327953e695 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/KeywordStutters.mls @@ -1,3 +1,4 @@ +:. :js @@ -30,7 +31,7 @@ abstract class Foo(a) Bar(b) //│ ╔══[PARSE ERROR] Unexpected identifier here -//│ ║ l.31: Bar(b) +//│ ║ l.32: Bar(b) //│ ╙── ^^^ @@ -46,10 +47,10 @@ object \$ extends Foo A B //│ ╔══[LEXICAL ERROR] unexpected character '$' -//│ ║ l.45: object \$ extends Foo +//│ ║ l.46: object \$ extends Foo //│ ╙── ^ //│ ╔══[PARSE ERROR] Expected end of input; found error instead -//│ ║ l.45: object \$ extends Foo +//│ ║ l.46: object \$ extends Foo //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/syntax/TypesInTerms.mls b/hkmc2/shared/src/test/mlscript/syntax/TypesInTerms.mls index d7805d0340..15969985a2 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/TypesInTerms.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/TypesInTerms.mls @@ -1,24 +1,25 @@ +:. :js :ge 1 | 2 //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) -//│ ║ l.5: 1 | 2 +//│ ║ l.6: 1 | 2 //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge print("A") & print("B") //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (composition) -//│ ║ l.12: print("A") & print("B") +//│ ║ l.13: print("A") & print("B") //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge ~false //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (negation type) -//│ ║ l.19: ~false +//│ ║ l.20: ~false //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls b/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls index adb124e0be..479b73264f 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls @@ -1,3 +1,4 @@ +:. :js @@ -11,15 +12,19 @@ :e \{ Int } //│ ╔══[COMPILATION ERROR] Unsupported refinement in this position. -//│ ║ l.12: \{ Int } +//│ ║ l.13: \{ Int } //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e \(0) -//│ ╔══[COMPILATION ERROR] Expected 2 arguments, got 1 -//│ ║ l.19: \(0) -//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.20: \(0) +//│ ║ ^^^^ +//│ ╟── Function 'passTo' expected 2 arguments, but got 1 +//│ ║ in ../../mlscript-compile/Predef.mls +//│ ║ l.46: fun (\) passTo(receiver, f: dyn)(...args) = f(receiver, ...args) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = fun fun (\) huh(x) = x @@ -33,7 +38,7 @@ fun (\) huh(x) = x // syntax. In the new `Desugarer`, we chose not to support this syntax. fun f(x) = if x is {( Int then 1, Bool then 2 )} //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (parenthesis section). -//│ ║ l.34: fun f(x) = if x is {( Int then 1, Bool then 2 )} +//│ ║ l.39: fun f(x) = if x is {( Int then 1, Bool then 2 )} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -42,23 +47,29 @@ fun f(x) = if x is {( Int then 1, Bool then 2 )} :e fun f(x) = if x is ( Int then 1, Bool then 2 ) //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (parenthesis section). -//│ ║ l.43: fun f(x) = if x is ( Int then 1, Bool then 2 ) +//│ ║ l.48: fun f(x) = if x is ( Int then 1, Bool then 2 ) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ :e fun f(x) = if x is \{ Int then 1, Bool then 2 } //│ ╔══[COMPILATION ERROR] Unrecognized pattern split (refinement). -//│ ║ l.49: fun f(x) = if x is \{ Int then 1, Bool then 2 } +//│ ║ l.54: fun f(x) = if x is \{ Int then 1, Bool then 2 } //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :e fun f(x) = if x is \( Int then 1, Bool then 2 ) -//│ ╔══[COMPILATION ERROR] Cannot use this reference as a pattern. -//│ ║ l.55: fun f(x) = if x is \( Int then 1, Bool then 2 ) -//│ ╙── ^ -//│ ╔══[COMPILATION ERROR] Cannot use this reference as a pattern. -//│ ║ l.55: fun f(x) = if x is \( Int then 1, Bool then 2 ) -//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.60: fun f(x) = if x is \( Int then 1, Bool then 2 ) +//│ ║ ^^^^^^ +//│ ╟── Function 'huh' cannot be used as a constructor pattern. +//│ ║ l.30: fun (\) huh(x) = x +//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in constructor +//│ ║ l.60: fun f(x) = if x is \( Int then 1, Bool then 2 ) +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Function 'huh' cannot be used as a constructor pattern. +//│ ║ l.30: fun (\) huh(x) = x +//│ ╙── ^^^^^^^^^^^^^^^^^^ class Box[T](val content: T) @@ -67,12 +78,14 @@ fun interesting(x) = if x is Box( Int then 1, Bool then 2 ) //│ Split with nested patterns: //│ > if -//│ > x is Box(Int) then 1 -//│ > x is Box(Bool) then 2 +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is member:Box⁰^term:interesting⁰(member:Int⁰^term:interesting⁰) then 1 +//│ > tmp:scrut is member:Box⁰^term:interesting⁰(member:Bool⁰^term:interesting⁰) then 2 //│ Expanded split with flattened patterns: //│ > ‹if|while› -//│ > x is Box(arg$Box$0$) and tmp:arg$Box$0$ is Int then 1 -//│ > x is Box(arg$Box$0$) and tmp:arg$Box$0$ is Bool then 2 +//│ > let tmp:scrut = x⁰ +//│ > tmp:scrut is member:Box⁰^term:interesting⁰(arg$Box$0$) and tmp:arg$Box$0$ is member:Int⁰^term:interesting⁰ then 1 +//│ > tmp:scrut is member:Box⁰^term:interesting⁰(arg$Box$0$) and tmp:arg$Box$0$ is member:Bool⁰^term:interesting⁰ then 2 interesting of Box(42) //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index 7a4bdd5aee..025e52a701 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -1,3 +1,4 @@ +:. :js @@ -20,70 +21,81 @@ fun foo(x) = if x is (@annotations.compile A(0)) as y then y :e fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╔══[PARSE ERROR] Unexpected keyword 'then' in this position -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ║ ^ +//│ ╙── Expected a term; got pattern 'A' //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Object 'compile' cannot be called like a function. +//│ ║ in ../../decls/Prelude.mls +//│ ║ l.477: object compile +//│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^ //│ ╔══[WARNING] This annotation is not supported here. -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╙── Note: Patterns only support the `@compile` annotation. :e fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.40: fun foo(x) = if x is @annotations.compile {A(0) as y} then y +//│ ║ l.52: fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.59: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.59: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.59: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.59: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.75: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.75: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.75: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.75: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ :e fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.78: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y +//│ ║ l.90: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╙── ^ :fixme fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.84: fun foo(x) = if x is (@annotations.compile A(0) as y) then y +//│ ║ l.96: fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╙── ^ @@ -91,43 +103,63 @@ fun foo(x) = if x is (@annotations.compile A(0) as y) then y type Test +:e :w @Test 1 +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.108: @Test 1 +//│ ║ ^^^^^ +//│ ╙── Expected a term; got type alias 'Test' //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.95: @Test 1 -//│ ║ ^^^^^ +//│ ║ l.108: @Test 1 +//│ ║ ^^^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.95: @Test 1 -//│ ╙── ^ +//│ ║ l.108: @Test 1 +//│ ╙── ^ //│ = 1 +:e :w @Test 2 + 1 +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.123: @Test 2 + 1 +//│ ║ ^^^^^ +//│ ╙── Expected a term; got type alias 'Test' //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.105: @Test 2 + 1 +//│ ║ l.123: @Test 2 + 1 //│ ║ ^^^^^ //│ ╟── This annotation is not supported on application terms. -//│ ║ l.105: @Test 2 + 1 +//│ ║ l.123: @Test 2 + 1 //│ ╙── ^^^^^ //│ = 3 +:e :w @Test 2 as Int +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.138: @Test 2 as Int +//│ ║ ^^^^^ +//│ ╙── Expected a term; got type alias 'Test' //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.115: @Test 2 as Int +//│ ║ l.138: @Test 2 as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.115: @Test 2 as Int +//│ ║ l.138: @Test 2 as Int //│ ╙── ^^^^^^^^ //│ = 2 +:e :w @Test id(2) as Int +//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ║ l.153: @Test id(2) as Int +//│ ║ ^^^^^ +//│ ╙── Expected a term; got type alias 'Test' //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.125: @Test id(2) as Int +//│ ║ l.153: @Test id(2) as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.125: @Test id(2) as Int +//│ ║ l.153: @Test id(2) as Int //│ ╙── ^^^^^^^^^^^^ //│ = 2 @@ -135,10 +167,10 @@ type Test :re (@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.136: (@Test) +//│ ║ l.168: (@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.136: (@Test) +//│ ║ l.168: (@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -146,10 +178,10 @@ type Test :re print(@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.147: print(@Test) +//│ ║ l.179: print(@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.147: print(@Test) +//│ ║ l.179: print(@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Call.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Call.mls index de61cfc842..4c6433d420 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Call.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Call.mls @@ -1,3 +1,4 @@ +:. :sir //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— @@ -31,7 +32,14 @@ fun curried(x)(y) = x //│ return @special curried¹(1)(2) +:e @special identity(1)(2) +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.36: @special identity(1)(2) +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.36: @special identity(1)(2) +//│ ╙── ^ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let baseCall; set baseCall = identity¹(1); return @special baseCall(2) @@ -40,7 +48,14 @@ fun nullary = 1 //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define nullary⁰ as fun nullary¹() { return 1 }; end +:w @special nullary +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.52: @special nullary +//│ ║ ^^^^^^^^ +//│ ╟── This annotation is not supported on member reference terms. +//│ ║ l.52: @special nullary +//│ ╙── ^^^^^^^ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ return @special nullary¹() @@ -49,7 +64,7 @@ class Box(value: Int) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define Box⁰ as class Box² { //│ private val value⁰; -//│ constructor Box¹(value) { set Box².this.value⁰ = value; end } +//│ constructor Box¹(value) { @private define value¹ as val value⁰ = value; end } //│ }; //│ end @@ -64,8 +79,10 @@ class CurriedBox(first: Int)(second: Int) //│ private val first⁰; //│ private val second⁰; //│ constructor CurriedBox¹(first, second) { -//│ set CurriedBox².this.first⁰ = first; -//│ set CurriedBox².this.second⁰ = second; +//│ @private +//│ define first¹ as val first⁰ = first; +//│ @private +//│ define second¹ as val second⁰ = second; //│ end //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls index 35fa9187ad..a6dcbce499 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Declarations.mls @@ -1,3 +1,4 @@ +:. :js object tailrec @@ -9,8 +10,8 @@ tailrec @tailrec fun fact_n(n, acc) = if n == 0 then acc else fact_n(n - 1, n * acc) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.9: @tailrec fun fact_n(n, acc) = -//│ ╙── ^^^^^^^^ +//│ ║ l.10: @tailrec fun fact_n(n, acc) = +//│ ╙── ^^^^^^^^ :w fun fact(n) = @@ -18,7 +19,7 @@ fun fact(n) = if n == 0 then acc else go(n - 1, n * acc) go(n, 1) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.17: @tailrec fun go(n, acc) = +//│ ║ l.18: @tailrec fun go(n, acc) = //│ ╙── ^^^^^^^^ data class Freezed(degree: Num) @@ -26,19 +27,19 @@ data class Freezed(degree: Num) :w @Freezed(-273.15) class AbsoluteZero //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.27: @Freezed(-273.15) class AbsoluteZero +//│ ║ l.28: @Freezed(-273.15) class AbsoluteZero //│ ╙── ^^^^^^^^^^^^^^^^^ :w @Freezed(-18) class Beverage(name: Str) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.33: @Freezed(-18) class Beverage(name: Str) +//│ ║ l.34: @Freezed(-18) class Beverage(name: Str) //│ ╙── ^^^^^^^^^^^^^ :w @Freezed(-4) let drink = Beverage("Coke") //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.39: @Freezed(-4) let drink = Beverage("Coke") +//│ ║ l.40: @Freezed(-4) let drink = Beverage("Coke") //│ ╙── ^^^^^^^^^^^^ //│ drink = Beverage(_) @@ -48,7 +49,7 @@ module Foo with :w @Foo.Bar("baz") class Qux //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.49: @Foo.Bar("baz") class Qux +//│ ║ l.50: @Foo.Bar("baz") class Qux //│ ╙── ^^^^^^^^^^^^^^^ // * Not an annotation, but an applicationo f operator `@`... @@ -56,7 +57,7 @@ module Foo with :re @42 class Qux //│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead -//│ ║ l.57: @42 class Qux +//│ ║ l.58: @42 class Qux //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] TypeError: 42 is not a function @@ -64,7 +65,7 @@ module Foo with :re @(1 + 2) class Qux //│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead -//│ ║ l.65: @(1 + 2) class Qux +//│ ║ l.66: @(1 + 2) class Qux //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] TypeError: 3 is not a function @@ -72,7 +73,7 @@ module Foo with :re @1 module Qux //│ ╔══[PARSE ERROR] Expected end of input; found 'module' keyword instead -//│ ║ l.73: @1 module Qux +//│ ║ l.74: @1 module Qux //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: 1 is not a function @@ -85,10 +86,10 @@ fun min(x, y) = if x < y then x else y max(x, y) = if x > y then x else y //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.83: @inline +//│ ║ l.84: @inline //│ ╙── ^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.83: @inline +//│ ║ l.84: @inline //│ ╙── ^^^^^^^ :w @@ -96,10 +97,10 @@ fun abs(x) = if x < 0 then -x else x clamp(x, lo, hi) = min(max(x, lo), hi) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.95: @inline let +//│ ║ l.96: @inline let //│ ╙── ^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.95: @inline let +//│ ║ l.96: @inline let //│ ╙── ^^^^^^^ //│ abs = fun abs //│ clamp = fun clamp @@ -110,7 +111,7 @@ let @inline success = 0 failure = 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.110: @inline success = 0 +//│ ║ l.111: @inline success = 0 //│ ╙── ^^^^^^^ //│ failure = 1 //│ success = 0 @@ -122,10 +123,10 @@ let @inline @tailrec fun fib(n) = if n < 2 then n else fib(n - 1) + fib(n - 2) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.122: @inline @tailrec fun fib(n) = +//│ ║ l.123: @inline @tailrec fun fib(n) = //│ ╙── ^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.122: @inline @tailrec fun fib(n) = +//│ ║ l.123: @inline @tailrec fun fib(n) = //│ ╙── ^^^^^^^^ object internal @@ -137,16 +138,16 @@ object internal transient volatile //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.134: @internal object +//│ ║ l.135: @internal object //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.134: @internal object +//│ ║ l.135: @internal object //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.134: @internal object +//│ ║ l.135: @internal object //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.134: @internal object +//│ ║ l.135: @internal object //│ ╙── ^^^^^^^^^ :w @@ -156,31 +157,31 @@ object internal @volatile @transient empty = "" normal = () //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.153: @volatile let +//│ ║ l.154: @volatile let //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.154: @shared counter = 0 +//│ ║ l.155: @shared counter = 0 //│ ╙── ^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.153: @volatile let +//│ ║ l.154: @volatile let //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.155: @transient @thread_local cache = () +//│ ║ l.156: @transient @thread_local cache = () //│ ╙── ^^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.155: @transient @thread_local cache = () +//│ ║ l.156: @transient @thread_local cache = () //│ ╙── ^^^^^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.153: @volatile let +//│ ║ l.154: @volatile let //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.156: @volatile @transient empty = "" +//│ ║ l.157: @volatile @transient empty = "" //│ ╙── ^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.156: @volatile @transient empty = "" +//│ ║ l.157: @volatile @transient empty = "" //│ ╙── ^^^^^^^^^^ //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.153: @volatile let +//│ ║ l.154: @volatile let //│ ╙── ^^^^^^^^^ //│ cache = () //│ counter = 0 diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls index 89941a9a80..390aac3733 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls @@ -1,3 +1,4 @@ +:. :js open annotations { compile } @@ -22,23 +23,23 @@ object do_not_compile :w fun foo(x) = if x is @do_not_compile Box then "yes" else "no" //│ ╔══[WARNING] This annotation is not supported here. -//│ ║ l.23: fun foo(x) = if x is @do_not_compile Box then "yes" else "no" +//│ ║ l.24: fun foo(x) = if x is @do_not_compile Box then "yes" else "no" //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Note: Patterns only support the `@compile` annotation. -//│ ║ l.23: fun foo(x) = if x is @do_not_compile Box then "yes" else "no" +//│ ║ l.24: fun foo(x) = if x is @do_not_compile Box then "yes" else "no" //│ ╙── ^^^ :w :e fun foo(x) = if x is @name_not_found Box then "yes" else "no" //│ ╔══[COMPILATION ERROR] Name not found: name_not_found -//│ ║ l.33: fun foo(x) = if x is @name_not_found Box then "yes" else "no" +//│ ║ l.34: fun foo(x) = if x is @name_not_found Box then "yes" else "no" //│ ╙── ^^^^^^^^^^^^^^^ //│ ╔══[WARNING] This annotation is not supported here. -//│ ║ l.33: fun foo(x) = if x is @name_not_found Box then "yes" else "no" +//│ ║ l.34: fun foo(x) = if x is @name_not_found Box then "yes" else "no" //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Note: Patterns only support the `@compile` annotation. -//│ ║ l.33: fun foo(x) = if x is @name_not_found Box then "yes" else "no" +//│ ║ l.34: fun foo(x) = if x is @name_not_found Box then "yes" else "no" //│ ╙── ^^^ pattern Box' = Box @@ -48,9 +49,9 @@ fun foo(x) = if x is @compile Box' then "yes" else "no" :w fun foo(x) = if x is @do_not_compile Box' then "yes" else "no" //│ ╔══[WARNING] This annotation is not supported here. -//│ ║ l.49: fun foo(x) = if x is @do_not_compile Box' then "yes" else "no" +//│ ║ l.50: fun foo(x) = if x is @do_not_compile Box' then "yes" else "no" //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Note: Patterns only support the `@compile` annotation. -//│ ║ l.49: fun foo(x) = if x is @do_not_compile Box' then "yes" else "no" +//│ ║ l.50: fun foo(x) = if x is @do_not_compile Box' then "yes" else "no" //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls index 354b84e99d..7bbbc23d92 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Unsupported.mls @@ -1,3 +1,4 @@ +:. :js // The annotations for the following expressions are not supported for now. @@ -9,59 +10,59 @@ import "../../../mlscript-compile/StrOps.mls" @debug open StrOps //│ ╔══[WARNING] This annotation has no effect -//│ ║ l.9: @debug -//│ ║ ^^^^^^ +//│ ║ l.10: @debug +//│ ║ ^^^^^^ //│ ╟── Annotations are not supported on open terms. -//│ ║ l.10: open StrOps +//│ ║ l.11: open StrOps //│ ╙── ^^^^^^^^^^^ :w @debug 0 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.19: @debug 0 +//│ ║ l.20: @debug 0 //│ ║ ^^^^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.19: @debug 0 +//│ ║ l.20: @debug 0 //│ ╙── ^ //│ = 0 :w @debug 1 + 2 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.29: @debug 1 + 2 +//│ ║ l.30: @debug 1 + 2 //│ ║ ^^^^^^ //│ ╟── This annotation is not supported on application terms. -//│ ║ l.29: @debug 1 + 2 +//│ ║ l.30: @debug 1 + 2 //│ ╙── ^^^^^ //│ = 3 :w (@debug 1 + 2) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.39: (@debug 1 + 2) +//│ ║ l.40: (@debug 1 + 2) //│ ║ ^^^^^^ //│ ╟── This annotation is not supported on application terms. -//│ ║ l.39: (@debug 1 + 2) +//│ ║ l.40: (@debug 1 + 2) //│ ╙── ^^^^^ //│ = 3 :w (@debug 1) + 2 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.49: (@debug 1) + 2 +//│ ║ l.50: (@debug 1) + 2 //│ ║ ^^^^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.49: (@debug 1) + 2 +//│ ║ l.50: (@debug 1) + 2 //│ ╙── ^ //│ = 3 :w (1 + @debug 2) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.59: (1 + @debug 2) +//│ ║ l.60: (1 + @debug 2) //│ ║ ^^^^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.59: (1 + @debug 2) +//│ ║ l.60: (1 + @debug 2) //│ ╙── ^ //│ = 3 @@ -70,10 +71,10 @@ data class Log(msg: Str) :w id(@Log 5) //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.71: id(@Log 5) +//│ ║ l.72: id(@Log 5) //│ ║ ^^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.71: id(@Log 5) +//│ ║ l.72: id(@Log 5) //│ ╙── ^ //│ = 5 @@ -81,6 +82,6 @@ id(@Log 5) :re @1 + 2 class Qux //│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead -//│ ║ l.82: @1 + 2 class Qux +//│ ║ l.83: @1 + 2 class Qux //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] TypeError: 3 is not a function diff --git a/hkmc2/shared/src/test/mlscript/tailrec/.mls b/hkmc2/shared/src/test/mlscript/tailrec/.mls new file mode 100644 index 0000000000..e3f9030862 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/tailrec/.mls @@ -0,0 +1 @@ +:.. diff --git a/hkmc2/shared/src/test/mlscript/tailrec/Errors.mls b/hkmc2/shared/src/test/mlscript/tailrec/Errors.mls index ba9d364a61..1ba2afdb86 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/Errors.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/Errors.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -7,14 +8,14 @@ fun f(x) = @tailcall f(x) f(x) //│ ╔══[COMPILATION ERROR] This call is not in tail position. -//│ ║ l.7: @tailcall f(x) +//│ ║ l.8: @tailcall f(x) //│ ╙── ^^^^ :e fun f(x) = 2 fun g(x) = @tailcall f(x) //│ ╔══[COMPILATION ERROR] This call is not optimized as it does not directly recurse through its parent function. -//│ ║ l.15: fun g(x) = @tailcall f(x) +//│ ║ l.16: fun g(x) = @tailcall f(x) //│ ╙── ^^^^ :e @@ -22,10 +23,10 @@ fun g(x) = @tailcall f(x) g(x) fun g(x) = f(x); f(x) //│ ╔══[COMPILATION ERROR] This function is marked @tailrec but is not tail recursive. -//│ ║ l.21: @tailrec fun f(x) = +//│ ║ l.22: @tailrec fun f(x) = //│ ║ ^ //│ ╟── It could self-recurse through this call, which is not a tail call. -//│ ║ l.23: fun g(x) = f(x); f(x) +//│ ║ l.24: fun g(x) = f(x); f(x) //│ ╙── ^^^^ :e @@ -37,10 +38,10 @@ fun g(x) = fun h(x) = g(x) //│ ╔══[COMPILATION ERROR] This function is marked @tailrec but is not tail recursive. -//│ ║ l.32: @tailrec fun f(x) = +//│ ║ l.33: @tailrec fun f(x) = //│ ║ ^ //│ ╟── It could self-recurse through this call, which is not a tail call. -//│ ║ l.35: f(x) +//│ ║ l.36: f(x) //│ ╙── ^^^^ :e @@ -52,10 +53,10 @@ fun g(x) = fun h(x) = f(x) //│ ╔══[COMPILATION ERROR] This function is marked @tailrec but is not tail recursive. -//│ ║ l.47: @tailrec fun f(x) = +//│ ║ l.48: @tailrec fun f(x) = //│ ║ ^ //│ ╟── It could self-recurse through this call, which is not a tail call. -//│ ║ l.50: h(x) +//│ ║ l.51: h(x) //│ ╙── ^^^^ :e @@ -65,14 +66,14 @@ module A with @tailcall f(x - 1) @tailcall f(x - 1) //│ ╔══[COMPILATION ERROR] This call is not in tail position. -//│ ║ l.65: @tailcall f(x - 1) +//│ ║ l.66: @tailcall f(x - 1) //│ ╙── ^^^^^^^^ :w @tailrec fun f = 2 //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.73: fun f = 2 +//│ ║ l.74: fun f = 2 //│ ╙── ^ :w @@ -80,7 +81,7 @@ module A with @tailrec fun f() = 2 //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.81: fun f() = 2 +//│ ║ l.82: fun f() = 2 //│ ╙── ^ :fixme // TODO: support @@ -89,7 +90,7 @@ fun foo() = Foo.bar() module Foo with fun bar() = foo() //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.88: fun foo() = Foo.bar() +//│ ║ l.89: fun foo() = Foo.bar() //│ ╙── ^^^ :e @@ -98,7 +99,7 @@ fun f(x) = fun g() = g() g() //│ ╔══[COMPILATION ERROR] This @tailrec function was not processed by the tail-call optimizer. -//│ ║ l.98: fun g() = g() +//│ ║ l.99: fun g() = g() //│ ╙── ^ :e @@ -107,7 +108,7 @@ fun scan(idx, acc) = fun go(idx, tok) = scan(idx, tok) go(idx, acc) //│ ╔══[COMPILATION ERROR] This @tailrec function was not processed by the tail-call optimizer. -//│ ║ l.107: fun go(idx, tok) = scan(idx, tok) +//│ ║ l.108: fun go(idx, tok) = scan(idx, tok) //│ ╙── ^^ :e @@ -118,10 +119,10 @@ fun scan(idx, acc) = fun go(idx, tok) = scan(idx, tok) go(idx, acc) //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.116: fun scan(idx, acc) = +//│ ║ l.117: fun scan(idx, acc) = //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] This @tailrec function was not processed by the tail-call optimizer. -//│ ║ l.118: fun go(idx, tok) = scan(idx, tok) +//│ ║ l.119: fun go(idx, tok) = scan(idx, tok) //│ ╙── ^^ :e @@ -129,21 +130,21 @@ fun scan(idx, acc) = @config(tailRecOpt: false) fun foo() = foo() //│ ╔══[COMPILATION ERROR] This @tailrec function was not processed by the tail-call optimizer. -//│ ║ l.130: fun foo() = foo() +//│ ║ l.131: fun foo() = foo() //│ ╙── ^^^ :e fun f = @tailcall id(2) //│ ╔══[COMPILATION ERROR] Only functions in this compilation unit may be marked @tailcall. -//│ ║ l.137: @tailcall id(2) +//│ ║ l.138: @tailcall id(2) //│ ╙── ^^^^^ :e fun f(x)(y) = @tailcall f(x) //│ ╔══[COMPILATION ERROR] Only fully applied calls may be marked @tailcall. -//│ ║ l.144: @tailcall f(x) +//│ ║ l.145: @tailcall f(x) //│ ╙── ^^^^ :e @@ -152,10 +153,10 @@ fun f(x)() = fun g(x)(y) = @tailcall f(x) //│ ╔══[COMPILATION ERROR] Only fully applied calls may be marked @tailcall. -//│ ║ l.153: @tailcall f(x) +//│ ║ l.154: @tailcall f(x) //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] This call is not optimized as it does not directly recurse through its parent function. -//│ ║ l.151: @tailcall g(x)(0) +//│ ║ l.152: @tailcall g(x)(0) //│ ╙── ^^^^^^^ @@ -164,7 +165,7 @@ module M with @tailrec fun f = print("ok"); f //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.165: fun f = print("ok"); f +//│ ║ l.166: fun f = print("ok"); f //│ ╙── ^ :w @@ -172,7 +173,7 @@ module M with @tailrec fun f = case 0 then f(1) //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.173: fun f = case 0 then f(1) +//│ ║ l.174: fun f = case 0 then f(1) //│ ╙── ^ @@ -181,10 +182,10 @@ module M with fun foo(x) = foo(1) + 1 //│ ╔══[COMPILATION ERROR] This function is marked @tailrec but is not tail recursive. -//│ ║ l.181: fun foo(x) = +//│ ║ l.182: fun foo(x) = //│ ║ ^^^ //│ ╟── It could self-recurse through this call, which is not a tail call. -//│ ║ l.182: foo(1) + 1 +//│ ║ l.183: foo(1) + 1 //│ ╙── ^^^^^^ :w @@ -192,6 +193,6 @@ fun foo(x) = fun foo(x) = 1 |> foo //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.192: fun foo(x) = +//│ ║ l.193: fun foo(x) = //│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/tailrec/MultiArgLists.mls b/hkmc2/shared/src/test/mlscript/tailrec/MultiArgLists.mls index 1e66425f2b..9f9420adb1 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/MultiArgLists.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/MultiArgLists.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -180,7 +181,7 @@ fun resume(x)(y) = handleEffects(x) //│ 1 => //│ let tmp; //│ set tmp = resume⁰(undefined); -//│ do sink⁰.push﹖(tmp); +//│ do sink⁰.push⁰(tmp); //│ set id = 0; //│ continue loopLabel //│ else diff --git a/hkmc2/shared/src/test/mlscript/ups/fixpoint/ListFusion.mls b/hkmc2/shared/src/test/mlscript/ups/fixpoint/ListFusion.mls index 938de67bec..5c1aa264e5 100644 --- a/hkmc2/shared/src/test/mlscript/ups/fixpoint/ListFusion.mls +++ b/hkmc2/shared/src/test/mlscript/ups/fixpoint/ListFusion.mls @@ -1,3 +1,4 @@ +:. :js // Integer list pipelines fused by a fixed-point pattern. `MapInc` and @@ -32,26 +33,28 @@ fun withCounting(f) = print("Constructed " + (numObjectConstructed - before) + " object(s)") result +type Printable = {toString: () -> Str} + object Nil -data class Cons(val head, val tail) with +data class Cons(val head, val tail: Printable) with print("new: " + this.toString()) set numObjectConstructed += 1 - fun toString() = render(head) + " :: " + tail.toString() + fun toString(): Str = render(head) + " :: " + tail.toString() -data class MapInc(val source) with +data class MapInc(val source: Printable) with print("new: " + this.toString()) set numObjectConstructed += 1 - fun toString() = "map (+ 1) (" + source.toString() + ")" + fun toString(): Str = "map (+ 1) (" + source.toString() + ")" -data class FilterNonZero(val source) with +data class FilterNonZero(val source: Printable) with print("new: " + this.toString()) set numObjectConstructed += 1 - fun toString() = "filter (!= 0) (" + source.toString() + ")" + fun toString(): Str = "filter (!= 0) (" + source.toString() + ")" data class Item(val value) with print("new: " + this.toString()) set numObjectConstructed += 1 - fun toString() = render(value) + fun toString(): Str = render(value) pattern Ctx(pattern Hole) = | Hole diff --git a/hkmc2/shared/src/test/mlscript/wasm/Binaryen.mls b/hkmc2/shared/src/test/mlscript/wasm/Binaryen.mls index 59fcbf3c8f..125fc76c6b 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Binaryen.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Binaryen.mls @@ -1,3 +1,4 @@ +:.. :js :silent diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 166f6993ef..55cfac9194 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.363: fun +//│ ║ l.369: fun //│ ║ ^^^ -//│ ║ l.364: const(c: Int): Int32 +//│ ║ l.370: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From eda7156e25cc77833f158ccb6b0a46a28f5b8730 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 16:51:37 +0800 Subject: [PATCH 182/228] Preserve direct scrutinee references in new resolution Recognize new local and self references during pattern elaboration, preserving capture provenance for resolution while matching the original runtime symbol. Keep single evaluation for getters and other expressions. This removes redundant scrutinee copies and restores the 33 constructor fusions lost by the migration. Restore migrated let bindings, with initializer ascriptions where runtime-only checks need dynamic receivers. Cover captured generic extraction, mutable loop conditions, direct matches, and single evaluation of a by-name scrutinee. Reviewed golden changes against the pre-migration baseline, including identical fused constructor sites per block. Validation: ctest, focused diff tests, and hkmc2AllTests/test (1,074 tests) passed. --- .../scala/hkmc2/semantics/Elaborator.scala | 25 +- .../scala/hkmc2/semantics/NewResolver.scala | 2 +- .../src/test/mlscript-compile/Runtime.mjs | 66 +-- .../src/test/mlscript/basics/MutRcd.mls | 10 +- .../src/test/mlscript/basics/NewMut.mls | 20 +- .../src/test/mlscript/basics/Underscores.mls | 38 +- .../test/mlscript/codegen/ClassMatching.mls | 26 +- .../src/test/mlscript/codegen/IfThenElse.mls | 6 +- .../test/mlscript/codegen/MergeMatchArms.mls | 106 ++-- .../src/test/mlscript/codegen/While.mls | 63 +-- .../test/mlscript/dead-param-elim/todos.mls | 22 +- .../src/test/mlscript/deforest/append.mls | 28 +- .../src/test/mlscript/deforest/basic.mls | 146 +++--- .../src/test/mlscript/deforest/clashes.mls | 4 +- .../src/test/mlscript/deforest/cyclic.mls | 28 +- .../test/mlscript/deforest/determinism.mls | 26 +- .../test/mlscript/deforest/erasedTypes.mls | 51 +- .../mlscript/deforest/eta-expansion/basic.mls | 40 +- .../deforest/eta-expansion/recursive.mls | 16 +- .../src/test/mlscript/deforest/fusibility.mls | 187 +++---- .../src/test/mlscript/deforest/imperative.mls | 6 +- .../mlscript/deforest/listComprehension.mls | 264 +++++----- .../src/test/mlscript/deforest/module.mls | 88 ++-- .../test/mlscript/deforest/multiArgLists.mls | 10 +- .../test/mlscript/deforest/nestedMatch.mls | 310 ++++++----- .../src/test/mlscript/deforest/recursive.mls | 488 +++++++++--------- .../deforest/selectionsInNestedMatch.mls | 55 +- .../src/test/mlscript/deforest/simple.mls | 454 ++++++++-------- .../src/test/mlscript/deforest/todos.mls | 76 ++- .../src/test/mlscript/deforest/zipunzip.mls | 116 ++--- .../src/test/mlscript/interop/CtorBypass.mls | 2 +- .../test/mlscript/lifter/ModulesObjects.mls | 8 +- .../src/test/mlscript/newres/PatMat.mls | 4 +- .../src/test/mlscript/newres/ReslScratch.mls | 21 +- .../mlscript/newres/ScrutineeReferences.mls | 140 +++++ .../shared/src/test/mlscript/opt/LoopOpt.mls | 4 +- .../src/test/mlscript/opt/NoCaseOfCase.mls | 13 +- .../test/mlscript/std/FingerTreeListTest.mls | 20 +- .../test/mlscript/syntax/WeirdBrackets.mls | 10 +- .../mlscript/ucs/hygiene/HygienicBindings.mls | 63 +-- .../ucs/normalization/Deduplication.mls | 247 ++++----- .../ucs/normalization/DeduplicationWhile.mls | 42 +- .../normalization/ExcessiveDeduplication.mls | 53 +- .../ucs/normalization/OverlapOfPrimitives.mls | 89 ++-- .../normalization/RecordImpliedByClass.mls | 6 +- .../normalization/SpecializedSplitSharing.mls | 47 +- .../ucs/patterns/CompiledClassPatterns.mls | 5 +- .../test/mlscript/ucs/patterns/Refinement.mls | 14 +- .../mlscript/ucs/syntax/NestedOpSplits.mls | 6 +- .../mlscript/ucs/syntax/PlainConditionals.mls | 2 +- .../mlscript/ups/parametric/EtaConversion.mls | 3 +- .../test/mlscript/wasm/ClassInheritance.mls | 9 +- .../src/test/mlscript/wasm/Matching.mls | 8 +- 53 files changed, 1811 insertions(+), 1782 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ScrutineeReferences.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 72ccf2987a..93fa3c908e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -903,8 +903,8 @@ extends Importer: if methods.isEmpty then body else mkEffectHandleAbortive(nonLocalHandlerSym, "NonLocalLabelEffect", methods, body) - /** A scrutinee is a function that returns a reference to the symbol. */ - private type Reference = () => Term.Ref + /** A scrutinee reference retains captures until its pattern has been resolved. */ + private type Reference = () => Term private type Connective = `do`.type | `then`.type @@ -973,10 +973,16 @@ extends Importer: // case _ => ??? Head.Let(binding, term) - protected def mkMatch(scrutinee: Term.Ref, pattern: Pattern, consequent: SimpleSplit) = + protected def mkMatch(scrutinee: Term, pattern: Pattern, consequent: SimpleSplit) = log(s"mkMatch: scrutinee = ${scrutinee.showDbg}, pattern = ${pattern.showDbg}, consequent = ${consequent.showDbg}") matchScrutPat(scrutinee, pattern) - Head.Match(scrutinee, pattern, consequent) + // Captures carry the scope crossings needed to resolve extracted bindings, + // but matching the value at runtime only needs the underlying reference. + val ref = scrutinee.withoutCaptures match + case ref: Term.Ref => ref + case ref: (Term.SimpleRef | Term.SelfRef) => ref.sym.ref().withLocOf(scrutinee) + case _ => lastWords("A pattern scrutinee must be a reference") + Head.Match(ref, pattern, consequent) /** Elaborate shorthand expressions. */ protected def shorthandSplit(tree: Tree)(using UnderCtx): Ctxl[SimpleSplit] = @@ -1148,10 +1154,13 @@ extends Importer: term match // If the term is already a reference, we can re-reference its symbol. case Term.Ref(symbol) => continuation(() => symbol.ref().withLocOf(term)) - // Otherwise, we need to create a temporary symbol holding the term. - case term: Term => - val symbol = TempSymbol(N, erasedType = N, "scrut") - mkSplitLet(symbol, term) ~: continuation(() => symbol.ref()) + // New references may carry lexical captures with no runtime evaluation. + case _ => term.withoutCaptures match + case _: Term.SimpleRef | _: Term.SelfRef => continuation(() => term) + // Member references can invoke getters; evaluate those only once. + case _ => + val symbol = TempSymbol(N, erasedType = N, "scrut") + mkSplitLet(symbol, term) ~: continuation(() => symbol.ref()) private type TT = (Tree, Tree) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index bcdf60a91c..93378216e4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2296,7 +2296,7 @@ class NewResolver: case _ => raise(ErrorReport(msg"This pattern is not supported during shape resolution." -> pattern.toLoc :: Nil)) - def matchScrutPat(scrutinee: Term.Ref, pattern: Pattern)(using NewResolverState): Unit = if newResolution then + def matchScrutPat(scrutinee: Term, pattern: Pattern)(using NewResolverState): Unit = if newResolution then listenTerm(scrutinee)(sh => matchShapePat(sh, pattern)(_ => ())) /** Both class interpretations use the original class as their scheme owner. diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index dd4ed1f198..5146fc8ce1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -5,7 +5,7 @@ import RuntimeJS from "./RuntimeJS.mjs"; import Rendering from "./Rendering.mjs"; import LazyArray from "./LazyArray.mjs"; import Iter from "./Iter.mjs"; -let continuation, Runtime1, lambda, lambda1, lambda2, lambda3, lambda4, lambda5, lambda$, lambda$1, Capture$scope321, lambda$2, Capture$scope341, lambda$3, lambda$4, continuation$; +let continuation, Runtime1, lambda, lambda1, lambda2, lambda3, lambda4, lambda5, lambda$, lambda$1, Capture$scope301, lambda$2, Capture$scope321, lambda$3, lambda$4, continuation$; continuation$ = function continuation$(Runtime2, resume) { return (value) => { return continuation(Runtime2, resume, value) @@ -33,54 +33,54 @@ lambda3 = (undefined, function (Runtime2, promise, resume) { continuation$here = continuation$(Runtime2, resume); return runtime.safeCall(promise.then(continuation$here)) }); -(class Capture$scope34 { +(class Capture$scope32 { static { - Capture$scope341 = this + Capture$scope321 = this } constructor(result$0) { this.result$0 = result$0; } toString() { return runtime.render(this); } - static [definitionMetadata] = ["class", "Capture$scope34"]; + static [definitionMetadata] = ["class", "Capture$scope32"]; }); -lambda$3 = (undefined, function (scope34$cap, cont) { +lambda$3 = (undefined, function (scope32$cap, cont) { return (m, marker, ..._) => { - return lambda2(scope34$cap, cont, m, marker, ..._) + return lambda2(scope32$cap, cont, m, marker, ..._) } }); -lambda2 = (undefined, function (scope34$cap, cont, m, marker, ..._) { +lambda2 = (undefined, function (scope32$cap, cont, m, marker, ..._) { let scrut, tmp, tmp1; scrut = runtime.safeCall(m.has(cont)); if (scrut === true) { tmp = ", " + marker; - tmp1 = scope34$cap.result$0 + tmp; - scope34$cap.result$0 = tmp1; + tmp1 = scope32$cap.result$0 + tmp; + scope32$cap.result$0 = tmp1; return runtime.Unit } return runtime.Unit; }); -(class Capture$scope32 { +(class Capture$scope30 { static { - Capture$scope321 = this + Capture$scope301 = this } constructor(result$0) { this.result$0 = result$0; } toString() { return runtime.render(this); } - static [definitionMetadata] = ["class", "Capture$scope32"]; + static [definitionMetadata] = ["class", "Capture$scope30"]; }); -lambda$2 = (undefined, function (scope32$cap, cont) { +lambda$2 = (undefined, function (scope30$cap, cont) { return (m, marker, ..._) => { - return lambda1(scope32$cap, cont, m, marker, ..._) + return lambda1(scope30$cap, cont, m, marker, ..._) } }); -lambda1 = (undefined, function (scope32$cap, cont, m, marker, ..._) { +lambda1 = (undefined, function (scope30$cap, cont, m, marker, ..._) { let scrut, tmp, tmp1; scrut = runtime.safeCall(m.has(cont)); if (scrut === true) { tmp = ", " + marker; - tmp1 = scope32$cap.result$0 + tmp; - scope32$cap.result$0 = tmp1; + tmp1 = scope30$cap.result$0 + tmp; + scope30$cap.result$0 = tmp1; return runtime.Unit } return runtime.Unit; @@ -875,12 +875,12 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { return header; } static showFunctionContChain(cont, hl, vis, reps) { - let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, scope32$cap; - scope32$cap = new Capture$scope321(undefined); + let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, scope30$cap; + scope30$cap = new Capture$scope301(undefined); if (cont instanceof Runtime.FunctionContFrame.class) { tmp = cont["constructor"]["name"] + "(pc="; - scope32$cap.result$0 = tmp + cont.saved.at(1); - runtime.safeCall(hl.forEach(lambda$2(scope32$cap, cont))); + scope30$cap.result$0 = tmp + cont.saved.at(1); + runtime.safeCall(hl.forEach(lambda$2(scope30$cap, cont))); scrut = runtime.safeCall(vis.has(cont)); if (scrut === true) { tmp1 = reps + 1; @@ -889,12 +889,12 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { if (scrut1 === true) { throw runtime.safeCall(globalThis.Error("10 repeated continuation frame (loop?)")) } - tmp2 = scope32$cap.result$0 + ", REPEAT"; - scope32$cap.result$0 = tmp2; + tmp2 = scope30$cap.result$0 + ", REPEAT"; + scope30$cap.result$0 = tmp2; } else { runtime.safeCall(vis.add(cont)); } - tmp3 = scope32$cap.result$0 + ") -> "; + tmp3 = scope30$cap.result$0 + ") -> "; tmp4 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp3 + tmp4 } @@ -905,11 +905,11 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { return "(NOT CONT)"; } static showHandlerContChain(cont, hl, vis, reps) { - let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, scope34$cap; - scope34$cap = new Capture$scope341(undefined); + let scrut, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, scope32$cap; + scope32$cap = new Capture$scope321(undefined); if (cont instanceof Runtime.HandlerContFrame.class) { - scope34$cap.result$0 = cont.handler["constructor"]["name"]; - runtime.safeCall(hl.forEach(lambda$3(scope34$cap, cont))); + scope32$cap.result$0 = cont.handler["constructor"]["name"]; + runtime.safeCall(hl.forEach(lambda$3(scope32$cap, cont))); scrut = runtime.safeCall(vis.has(cont)); if (scrut === true) { tmp = reps + 1; @@ -918,12 +918,12 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { if (scrut1 === true) { throw runtime.safeCall(globalThis.Error("10 repeated continuation frame (loop?)")) } - tmp1 = scope34$cap.result$0 + ", REPEAT"; - scope34$cap.result$0 = tmp1; + tmp1 = scope32$cap.result$0 + ", REPEAT"; + scope32$cap.result$0 = tmp1; } else { runtime.safeCall(vis.add(cont)); } - tmp2 = scope34$cap.result$0 + " -> "; + tmp2 = scope32$cap.result$0 + " -> "; tmp3 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp2 + tmp3 } @@ -1270,9 +1270,9 @@ export { lambda4 as _$_modulePrivate_$_lambda4 }; export { lambda5 as _$_modulePrivate_$_lambda5 }; export { lambda$ as _$_modulePrivate_$_lambda$ }; export { lambda$1 as _$_modulePrivate_$_lambda$1 }; -export { Capture$scope321 as _$_modulePrivate_$_Capture$scope32 }; +export { Capture$scope301 as _$_modulePrivate_$_Capture$scope30 }; export { lambda$2 as _$_modulePrivate_$_lambda$2 }; -export { Capture$scope341 as _$_modulePrivate_$_Capture$scope34 }; +export { Capture$scope321 as _$_modulePrivate_$_Capture$scope32 }; export { lambda$3 as _$_modulePrivate_$_lambda$3 }; export { lambda$4 as _$_modulePrivate_$_lambda$4 }; export { continuation$ as _$_modulePrivate_$_continuation$ }; diff --git a/hkmc2/shared/src/test/mlscript/basics/MutRcd.mls b/hkmc2/shared/src/test/mlscript/basics/MutRcd.mls index a7563af3d8..c5ae4b41f5 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MutRcd.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MutRcd.mls @@ -3,7 +3,7 @@ // Dynamic receivers keep property additions and missing-field checks observable at runtime. -val r: dyn = {a: 0} +let r = ({a: 0} as dyn) //│ r = {a: 0} set @@ -19,7 +19,7 @@ r.foo //│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' -val r: dyn = mut {a: 1} +let r = mut {a: 1} as dyn //│ r = {a: 1} set @@ -35,7 +35,7 @@ r.foo //│ = 1 -val r: dyn = mut {} +let r = mut {} as dyn //│ r = {} set r.foo = 1 @@ -46,7 +46,7 @@ r.foo :sjs -val r: dyn = {} +let r = ({} as dyn) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let r3; r3 = runtime.Unit; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -66,7 +66,7 @@ r.foo (() as dyn).foo //│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' -val s: dyn = {} +let s = ({} as dyn) //│ s = () set s.foo = 2 diff --git a/hkmc2/shared/src/test/mlscript/basics/NewMut.mls b/hkmc2/shared/src/test/mlscript/basics/NewMut.mls index 05f1775cf3..ec90c06401 100644 --- a/hkmc2/shared/src/test/mlscript/basics/NewMut.mls +++ b/hkmc2/shared/src/test/mlscript/basics/NewMut.mls @@ -10,9 +10,9 @@ class Foo with // Dynamic receivers allow these tests to add fields while retaining runtime checks. :sjs -val f: dyn = new Foo +let f = new Foo as dyn //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f, tmp; tmp = globalThis.Object.freeze(new Foo1()); f = tmp; +//│ let f; f = globalThis.Object.freeze(new Foo1()); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ f = Foo { x: 0 } @@ -39,9 +39,9 @@ f.y :sjs // :elt -val f: dyn = new mut Foo +let f = new mut Foo as dyn //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f1, tmp1; tmp1 = new Foo1(); f1 = tmp1; +//│ let f1; f1 = new Foo1(); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ f = Foo { x: 0 } @@ -79,7 +79,7 @@ new mut Foo.x //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -val f: dyn = new mut Foo() +let f = new mut Foo() as dyn //│ f = Foo { x: 0 } set @@ -128,7 +128,7 @@ f.x //│ = 1 -val f: dyn = Foo(0) +let f = Foo(0) as dyn //│ f = Foo(0) set @@ -168,8 +168,8 @@ f.y -val f: dyn = new Object with - val x = 12 +let f = (new Object with + val x = 12) as dyn //│ f = $anon { x: 12 } set @@ -184,8 +184,8 @@ f.y //│ ═══[RUNTIME ERROR] Error: Access to required field 'y' yielded 'undefined' -val f: dyn = new mut Object with - val x = 12 +let f = (new mut Object with + val x = 12) as dyn //│ f = $anon { x: 12 } set diff --git a/hkmc2/shared/src/test/mlscript/basics/Underscores.mls b/hkmc2/shared/src/test/mlscript/basics/Underscores.mls index b81e0e2625..897afcdaf5 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Underscores.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Underscores.mls @@ -61,35 +61,33 @@ print(_) :sjs -val test: ({f: (Int, Int, Int) -> ()}, Int) -> () = _.f(0, _, 2) +let test = (_.f(0, _, 2)) as (({f: (Int, Int, Int) -> ()}, Int) -> ()) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let test, lambda7; -//│ lambda7 = (undefined, function (_0, _1) { -//│ return runtime.safeCall(_0.f(0, _1, 2)) -//│ }); -//│ test = lambda7; +//│ let test, test1; +//│ test1 = function test(_0, _1) { return runtime.safeCall(_0.f(0, _1, 2)) }; +//│ test = test1; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ test = fun +//│ test = fun test :e :re test(f: print)(1) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.76: test(f: print)(1) +//│ ║ l.74: test(f: print)(1) //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Function with a declared signature expected 2 arguments, but got 1 -//│ ║ l.64: val test: ({f: (Int, Int, Int) -> ()}, Int) -> () = _.f(0, _, 2) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.64: let test = (_.f(0, _, 2)) as (({f: (Int, Int, Int) -> ()}, Int) -> ()) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.76: test(f: print)(1) +//│ ║ l.74: test(f: print)(1) //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Value with no known interface cannot be called like a function. -//│ ║ l.64: val test: ({f: (Int, Int, Int) -> ()}, Int) -> () = _.f(0, _, 2) -//│ ║ ^^ +//│ ║ l.64: let test = (_.f(0, _, 2)) as (({f: (Int, Int, Int) -> ()}, Int) -> ()) +//│ ║ ^^ //│ ╟── No member interface is known for this type. -//│ ║ l.64: val test: ({f: (Int, Int, Int) -> ()}, Int) -> () = _.f(0, _, 2) -//│ ╙── ^^ -//│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 1 +//│ ║ l.64: let test = (_.f(0, _, 2)) as (({f: (Int, Int, Int) -> ()}, Int) -> ()) +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] Error: Function 'test' expected 2 arguments but got 1 test((f: print), 1) //│ > 0 1 2 @@ -116,12 +114,12 @@ print(inc(_)) :sjs _ is Int //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let lambda9; -//│ lambda9 = (undefined, function (_0) { +//│ let lambda8; +//│ lambda8 = (undefined, function (_0) { //│ if (globalThis.Number.isInteger(_0)) { return true } //│ return false; //│ }); -//│ return lambda9 +//│ return lambda8 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun @@ -158,7 +156,7 @@ mkObj(1, 2) :re let mkObj = x: _, y: _, z: 3 //│ ╔══[COMPILATION ERROR] Illegal position for '_' placeholder. -//│ ║ l.159: let mkObj = x: _, y: _, z: 3 +//│ ║ l.157: let mkObj = x: _, y: _, z: 3 //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ mkObj = fun mkObj diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls index 3ef81332e7..3dacfbf2f4 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls @@ -27,10 +27,8 @@ let s = Some(0) if s is Some(x) then x //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut1; -//│ scrut1 = s; -//│ if (scrut1 instanceof Some1.class) { -//│ return scrut1.value +//│ if (s instanceof Some1.class) { +//│ return s.value //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -48,11 +46,10 @@ if s is Some(1) then 0 None then 1 //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let scrut, arg$Some$0$; -//│ set scrut = s⁰; -//│ match scrut +//│ let arg$Some$0$; +//│ match s⁰ //│ Some⁰ => -//│ set arg$Some$0$ = scrut.value⁰; +//│ set arg$Some$0$ = s⁰.value⁰; //│ match arg$Some$0$ //│ 1 => //│ return 0 @@ -65,11 +62,10 @@ if s is //│ throw new globalThis⁰.Error⁰("match error") //│ end //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let scrut, arg$Some$0$; -//│ set scrut = s⁰; -//│ match scrut +//│ let arg$Some$0$; +//│ match s⁰ //│ Some⁰ => -//│ set arg$Some$0$ = scrut.value⁰; +//│ set arg$Some$0$ = s⁰.value⁰; //│ match arg$Some$0$ //│ 1 => //│ return 0 @@ -159,11 +155,11 @@ fun f(x) = if x is //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f4; //│ f4 = function f(x2) { -//│ let scrut5, arg$Some$0$1; +//│ let scrut1, arg$Some$0$1; //│ if (x2 instanceof Some1.class) { //│ arg$Some$0$1 = x2.value; -//│ scrut5 = arg$Some$0$1 > 0; -//│ if (scrut5 === true) { +//│ scrut1 = arg$Some$0$1 > 0; +//│ if (scrut1 === true) { //│ return 42 //│ } //│ return Predef.print("oops"); diff --git a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls index 4e08e7c305..e6db47a1ed 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls @@ -49,11 +49,7 @@ let f = x => print((if x and x then "ok" else "ko") + "!") //│ lambda1 = (undefined, function (x) { //│ let tmp, tmp1; //│ if (x === true) { -//│ if (x === true) { -//│ tmp = "ok"; -//│ } else { -//│ tmp = "ko"; -//│ } +//│ tmp = "ok"; //│ } else { tmp = "ko"; } //│ tmp1 = tmp + "!"; //│ return Predef.print(tmp1) diff --git a/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls b/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls index e9c9d46985..ca86b55a2a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls @@ -23,15 +23,13 @@ if a is C then 3 D then 4 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut; -//│ scrut = a; -//│ if (scrut instanceof A1.class) { +//│ if (a instanceof A1.class) { //│ return 1 -//│ } else if (scrut instanceof B1.class) { +//│ } else if (a instanceof B1.class) { //│ return 2 -//│ } else if (scrut instanceof C1.class) { +//│ } else if (a instanceof C1.class) { //│ return 3 -//│ } else if (scrut instanceof D1.class) { +//│ } else if (a instanceof D1.class) { //│ return 4 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -48,15 +46,11 @@ if a is B then 2 C then 3 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut1, scrut2; -//│ scrut1 = a; -//│ if (scrut1 instanceof A1.class) { +//│ if (a instanceof A1.class) { //│ return 1 -//│ } -//│ scrut2 = a; -//│ if (scrut2 instanceof B1.class) { +//│ } else if (a instanceof B1.class) { //│ return 2 -//│ } else if (scrut2 instanceof C1.class) { +//│ } else if (a instanceof C1.class) { //│ return 3 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -72,19 +66,16 @@ if a is B then 2 C then 3 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut3, scrut4; -//│ scrut3 = a; -//│ if (scrut3 instanceof A1.class) { -//│ scrut4 = b; -//│ if (scrut4 instanceof A1.class) { +//│ if (a instanceof A1.class) { +//│ if (b instanceof A1.class) { //│ return 1 -//│ } else if (scrut4 instanceof B1.class) { +//│ } else if (b instanceof B1.class) { //│ return 2 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); -//│ } else if (scrut3 instanceof B1.class) { +//│ } else if (a instanceof B1.class) { //│ return 2 -//│ } else if (scrut3 instanceof C1.class) { +//│ } else if (a instanceof C1.class) { //│ return 3 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -110,30 +101,22 @@ let x = if a is print("done") print(x) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let x, scrut5, scrut6, scrut7, tmp; -//│ scrut5 = a; -//│ if (scrut5 instanceof A1.class) { +//│ let x, tmp; +//│ if (a instanceof A1.class) { //│ tmp = 1; -//│ } else if (scrut5 instanceof B1.class) { +//│ } else if (a instanceof B1.class) { //│ tmp = 2; -//│ } else if (scrut5 instanceof C1.class) { +//│ } else if (a instanceof C1.class) { //│ tmp = 3; -//│ } else { -//│ scrut6 = a; -//│ if (scrut6 instanceof D1.class) { -//│ scrut7 = a; -//│ if (scrut7 instanceof A1.class) { -//│ /* Rest moved to non-abortive branch(es) */ -//│ } else if (scrut7 instanceof B1.class) { -//│ /* Rest moved to non-abortive branch(es) */ -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) -//│ } -//│ /* Rest moved to non-abortive branch(es) */ -//│ } else if (scrut6 instanceof E1.class) {} else { +//│ } else if (a instanceof D1.class) { +//│ if (a instanceof A1.class) {} else if (a instanceof B1.class) {} else { //│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } //│ tmp = Predef.print("done"); +//│ } else if (a instanceof E1.class) { +//│ tmp = Predef.print("done"); +//│ } else { +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } //│ x = tmp; //│ return Predef.print(tmp) @@ -150,11 +133,9 @@ if a is let tmp = 2 B then 2 + tmp //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut8; -//│ scrut8 = a; -//│ if (scrut8 instanceof A1.class) { +//│ if (a instanceof A1.class) { //│ return 1 -//│ } else if (scrut8 instanceof B1.class) { +//│ } else if (a instanceof B1.class) { //│ return 4 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -174,13 +155,11 @@ if a is let tmp = printAndId(3) B then 2 + tmp //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut9; -//│ scrut9 = a; -//│ if (scrut9 instanceof A1.class) { +//│ if (a instanceof A1.class) { //│ return 1 //│ } //│ Predef.print(3); -//│ if (scrut9 instanceof B1.class) { +//│ if (a instanceof B1.class) { //│ return 5 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -199,19 +178,14 @@ if a is C then 3 print(x) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut10, scrut11; -//│ scrut10 = a; -//│ if (scrut10 instanceof A1.class) { +//│ if (a instanceof A1.class) { //│ return 1 -//│ } -//│ scrut11 = a; -//│ if (scrut11 instanceof B1.class) { +//│ } else if (a instanceof B1.class) { //│ return Predef.print(2) -//│ } else if (scrut11 instanceof C1.class) { +//│ } else if (a instanceof C1.class) { //│ return Predef.print(3) //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); -//│ /* Rest moved to non-abortive branch(es) */ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 @@ -230,15 +204,13 @@ if a is print(x + 1) print(x + 2) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut12, scrut13, tmp1, tmp2, tmp3; -//│ scrut12 = a; -//│ if (scrut12 instanceof B1.class) { +//│ let tmp1, tmp2, tmp3; +//│ if (a instanceof B1.class) { //│ return 1 //│ } -//│ scrut13 = a; -//│ if (scrut13 instanceof A1.class) { +//│ if (a instanceof A1.class) { //│ tmp1 = 2; -//│ } else if (scrut13 instanceof C1.class) { +//│ } else if (a instanceof C1.class) { //│ tmp1 = 3; //│ } else { //│ throw globalThis.Object.freeze(new globalThis.Error("match error")) @@ -270,14 +242,12 @@ fun f(x) = //│ match x //│ 5 => //│ return "" +//│ 8 => +//│ do Predef⁰.print⁰("8"); +//│ do Predef⁰.print⁰("hi"); +//│ return "" //│ else -//│ match x -//│ 8 => -//│ do Predef⁰.print⁰("8"); -//│ end -//│ else -//│ do Predef⁰.print⁰("?"); -//│ end +//│ do Predef⁰.print⁰("?"); //│ do Predef⁰.print⁰("hi"); //│ return "" //│ end diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index 20659f149a..08c8a51e19 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -49,12 +49,10 @@ fun f = while x else return 42 // TODO: support `break` here f //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f; +//│ let f, inlinedVal; //│ f = function f() { //│ lbl4: while (true) { -//│ let scrut; -//│ scrut = x2; -//│ if (scrut === true) { +//│ if (x2 === true) { //│ Predef.print("Hello World"); //│ x2 = false; //│ continue lbl4 @@ -62,7 +60,18 @@ f //│ return 42; //│ } //│ }; -//│ return f() +//│ inlinedLbl: { +//│ lbl4: while (true) { +//│ if (x2 === true) { +//│ Predef.print("Hello World"); +//│ x2 = false; +//│ continue lbl4 +//│ } +//│ inlinedVal = 42; +//│ break inlinedLbl; +//│ } +//│ } +//│ return inlinedVal //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > Hello World //│ = 42 @@ -73,10 +82,10 @@ fun f = while (print("Hello World"), false) else return 1 f //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.72: then 0(0) +//│ ║ l.81: then 0(0) //│ ║ ^^^^ //│ ╟── Integer literal cannot be called like a function. -//│ ║ l.72: then 0(0) +//│ ║ l.81: then 0(0) //│ ╙── ^ //│ > Hello World //│ = 1 @@ -163,10 +172,8 @@ if x else 42 //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is true and -//│ > let tmp:scrut = y⁰ -//│ > tmp:scrut is true then 0 +//│ > x is true and +//│ > y is true then 0 //│ > else 1 //│ > else 42 //│ = 42 @@ -179,10 +186,8 @@ if x else 42 //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is true and -//│ > let tmp:scrut = y⁰ -//│ > tmp:scrut is true then 0 +//│ > x is true and +//│ > y is true then 0 //│ > else 1 //│ > else 42 //│ = 42 @@ -204,14 +209,14 @@ fun f(ls) = //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f6; //│ f6 = function f(ls) { -//│ lbl8: while (true) { +//│ lbl9: while (true) { //│ let arg$Cons$0$, arg$Cons$1$; //│ if (ls instanceof Cons1.class) { //│ arg$Cons$0$ = ls.hd; //│ arg$Cons$1$ = ls.tl; //│ ls = arg$Cons$1$; //│ Predef.print(arg$Cons$0$); -//│ continue lbl8 +//│ continue lbl9 //│ } //│ break; //│ } @@ -254,10 +259,8 @@ let x = 1 :sjs while x is {} do() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ lbl10: while (true) { -//│ let scrut4; -//│ scrut4 = x3; -//│ if (scrut4 instanceof globalThis.Object) { continue lbl10 } +//│ lbl11: while (true) { +//│ if (x3 instanceof globalThis.Object) { continue lbl11 } //│ break; //│ } //│ return runtime.Unit @@ -332,10 +335,10 @@ while print("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.332: then 0(0) +//│ ║ l.335: then 0(0) //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized term split (false literal) -//│ ║ l.331: while print("Hello World"); false +//│ ║ l.334: while print("Hello World"); false //│ ╙── ^^^^^ //│ > Hello World @@ -344,12 +347,12 @@ while { print("Hello World"), false } then 0(0) else 1 //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.343: while { print("Hello World"), false } +//│ ║ l.346: while { print("Hello World"), false } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.344: then 0(0) +//│ ║ l.347: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.345: else 1 +//│ ║ l.348: else 1 //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -360,14 +363,14 @@ while then 0(0) else 1 //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.358: print("Hello World") +//│ ║ l.361: print("Hello World") //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.359: false +//│ ║ l.362: false //│ ║ ^^^^^^^^^ -//│ ║ l.360: then 0(0) +//│ ║ l.363: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.361: else 1 +//│ ║ l.364: else 1 //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/todos.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/todos.mls index cfebb10b79..aec9106837 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/todos.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/todos.mls @@ -33,9 +33,9 @@ fun c(x) = c(p(true)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: x⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: x⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > p_7$p: [1, 1] @@ -91,18 +91,18 @@ fun sumPlus(ls) = sumPlus(enum(3)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹ -//│ deforest > Cons⁰(x⁰, tmp⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > Cons⁰(x¹, tmp⁰) -> +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > enum_14$enum: [1, 9] //│ eta-expansion > <<< eta-expansion targets shapes <<< //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun sumPlus_16$scrut_Cons#1 @ -> eliminable: {0, 5} -//│ dead-param-elim > prodfun sumPlus_16$scrut_Nil#1 @ -> eliminable: {0, 5} +//│ dead-param-elim > prodfun sumPlus_16$ls_Cons#1 @ -> eliminable: {0, 5} +//│ dead-param-elim > prodfun sumPlus_16$ls_Nil#1 @ -> eliminable: {0, 5} //│ dead-param-elim > <<< dead-param-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ let enum_14$enum$worker; @@ -173,9 +173,9 @@ fun c(x) = if x is c(p(true)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: x² //│ deforest > B⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: x² //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > p_7$p: [1, 0] diff --git a/hkmc2/shared/src/test/mlscript/deforest/append.mls b/hkmc2/shared/src/test/mlscript/deforest/append.mls index d177b62c52..8dbd227175 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/append.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/append.mls @@ -64,25 +64,25 @@ appendThree of id(5 :: 6 :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(h⁰, tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h¹ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h¹ +//│ deforest > fields: xs⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: xs⁰ //│ deforest > Cons⁰(h², tmp¹) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h¹ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h¹ +//│ deforest > fields: xs⁰.t⁰ //│ deforest > Cons⁰(h⁰, tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h¹ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h¹ +//│ deforest > fields: xs⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: xs⁰ //│ deforest > Cons⁰(h², tmp¹) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h¹ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h¹ +//│ deforest > fields: xs⁰.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) diff --git a/hkmc2/shared/src/test/mlscript/deforest/basic.mls b/hkmc2/shared/src/test/mlscript/deforest/basic.mls index a9345d749e..c3b5ad93da 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/basic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/basic.mls @@ -20,14 +20,14 @@ let lsit = [2, 1] in let useless = if lsit is [a, b] then 0 //│ deforest > >>> fusing >>> //│ deforest > [2, 1] -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁰, 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁰, 1) +//│ deforest > match: lsit⁰ +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(lsit⁰, 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(lsit⁰, 1) //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let lsit, useless, scrut, a, b, element1$, element0$, tmp, $scrut_tup2, $scrut_rest, tup2_0, tup2_1; +//│ let lsit, useless, a, b, element1$, element0$, tmp, $lsit_tup2, $lsit_rest, tup2_0, tup2_1; //│ @affine(1) @private -//│ define $scrut_tup2 as fun $scrut_tup2⁰(tup2_01, tup2_11)() { +//│ define $lsit_tup2 as fun $lsit_tup2⁰(tup2_01, tup2_11)() { //│ set element0$ = tup2_01; //│ set element1$ = tup2_11; //│ set b = element1$; @@ -37,14 +37,13 @@ let lsit = [2, 1] in //│ return runtime⁰.Unit⁰ //│ }; //│ @private -//│ define $scrut_rest as fun $scrut_rest⁰() { +//│ define $lsit_rest as fun $lsit_rest⁰() { //│ return null //│ }; //│ set tup2_0 = 2; //│ set tup2_1 = 1; -//│ set lsit = $scrut_tup2⁰(tup2_0, tup2_1); -//│ set scrut = lsit; -//│ return scrut() +//│ set lsit = $lsit_tup2⁰(tup2_0, tup2_1); +//│ return lsit() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :expect O @@ -54,8 +53,8 @@ fun c(x) = if x is c(p()) //│ deforest > >>> fusing >>> //│ deforest > X⁰(O⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.a⁰ +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.a⁰ //│ deforest > <<< fusing <<< //│ = O @@ -68,30 +67,29 @@ fun f(x) = if x is f(p()) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp⁰) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².a⁰ +//│ deforest > match: x¹ +//│ deforest > fields: x¹.a⁰ //│ deforest > Y⁰(O⁰) -> //│ deforest > match: arg$X$0$⁰ //│ deforest > fields: arg$X$0$⁰.b⁰ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp, p_8$p, f_10$f, f_10$scrut_X, f_10$scrut_rest, f_10$arg$X$0$_Y, f_10$arg$X$0$_rest; +//│ let tmp, p_8$p, f_10$f, f_10$x_X, f_10$x_rest, f_10$arg$X$0$_Y, f_10$arg$X$0$_rest; //│ @private //│ define p_8$p as fun p_8$p⁰() { //│ let tmp1, Y_b, X_a; //│ set Y_b = O⁰; //│ set tmp1 = f_10$arg$X$0$_Y⁰(Y_b); //│ set X_a = tmp1; -//│ return f_10$scrut_X⁰(X_a) +//│ return f_10$x_X⁰(X_a) //│ }; //│ @private //│ define f_10$f as fun f_10$f⁰(x) { -//│ let scrut, y, arg$X$0$, arg$Y$0$; -//│ set scrut = x; -//│ return scrut(y, arg$X$0$, arg$Y$0$) +//│ let y, arg$X$0$, arg$Y$0$; +//│ return x(y, arg$X$0$, arg$Y$0$) //│ }; //│ @affine(1) @private -//│ define f_10$scrut_X as fun f_10$scrut_X⁰(X_a)(fv_y, fv_arg$X$0$, fv_arg$Y$0$) { +//│ define f_10$x_X as fun f_10$x_X⁰(X_a)(fv_y, fv_arg$X$0$, fv_arg$Y$0$) { //│ set fv_arg$X$0$ = X_a; //│ return fv_arg$X$0$(fv_y, fv_arg$Y$0$) //│ }; @@ -102,12 +100,12 @@ f(p()) //│ return fv_y //│ }; //│ @private -//│ define f_10$scrut_rest as fun f_10$scrut_rest⁰() { +//│ define f_10$x_rest as fun f_10$x_rest⁰() { //│ return null //│ }; //│ @private //│ define f_10$arg$X$0$_rest as fun f_10$arg$X$0$_rest⁰() { -//│ return f_10$scrut_rest⁰() +//│ return f_10$x_rest⁰() //│ }; //│ define p⁰ as fun p¹() { //│ let tmp1; @@ -115,11 +113,10 @@ f(p()) //│ return X⁰(tmp1) //│ }; //│ define f⁰ as fun f¹(x) { -//│ let scrut, y, arg$X$0$, arg$Y$0$; -//│ set scrut = x; -//│ match scrut +//│ let y, arg$X$0$, arg$Y$0$; +//│ match x //│ X¹ => -//│ set arg$X$0$ = scrut.a⁰; +//│ set arg$X$0$ = x.a⁰; //│ match arg$X$0$ //│ Y¹ => //│ set arg$Y$0$ = arg$X$0$.b⁰; @@ -148,11 +145,11 @@ fun f(x, res) = if x is f(p(), 2) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp¹) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.a⁰ +//│ deforest > match: x² +//│ deforest > fields: x².a⁰ //│ deforest > Y⁰(O⁰) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.b⁰ +//│ deforest > match: a¹ +//│ deforest > fields: a¹.b⁰ //│ deforest > <<< fusing <<< //│ = 14 @@ -169,8 +166,8 @@ fun f(x, y, res) = if x is f(X(1), Y(2), 3) //│ deforest > >>> fusing >>> //│ deforest > X⁰(1) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.a⁰ +//│ deforest > match: x³ +//│ deforest > fields: x³.a⁰ //│ deforest > <<< fusing <<< //│ = 16 @@ -180,18 +177,18 @@ fun c(x, d) = if x is X(a) then c(X(a), d) () => c(X(3), 2) //│ deforest > >>> fusing >>> -//│ deforest > X⁰(a¹) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.a⁰ +//│ deforest > X⁰(a²) -> +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.a⁰ //│ deforest > X⁰(3) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.a⁰ -//│ deforest > X⁰(a¹) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.a⁰ +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.a⁰ +//│ deforest > X⁰(a²) -> +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.a⁰ //│ deforest > X⁰(3) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.a⁰ +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.a⁰ //│ deforest > <<< fusing <<< //│ = fun lambda_7$lambda @@ -205,7 +202,7 @@ fun p(x) = if x is p(c()) //│ deforest > >>> fusing >>> //│ deforest > O⁰ -> -//│ deforest > match: scrut⁷ +//│ deforest > match: x⁵ //│ deforest > <<< fusing <<< //│ = 2 @@ -216,14 +213,14 @@ if [1, 2] is [a, b] then a + b //│ deforest > >>> fusing >>> //│ deforest > [1, 2] -> -//│ deforest > match: scrut⁸ -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁸, 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁸, 1) +//│ deforest > match: scrut⁰ +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁰, 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁰, 1) //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let scrut, a, b, element1$, element0$, $scrut_tup2, $scrut_rest, tup2_0, tup2_1; //│ @affine(1) @private -//│ define $scrut_tup2 as fun $scrut_tup2¹(tup2_01, tup2_11)() { +//│ define $scrut_tup2 as fun $scrut_tup2⁰(tup2_01, tup2_11)() { //│ set element0$ = tup2_01; //│ set element1$ = tup2_11; //│ set b = element1$; @@ -231,12 +228,12 @@ if [1, 2] is //│ return +⁰(a, b) //│ }; //│ @private -//│ define $scrut_rest as fun $scrut_rest¹() { +//│ define $scrut_rest as fun $scrut_rest⁰() { //│ return null //│ }; //│ set tup2_0 = 1; //│ set tup2_1 = 2; -//│ set scrut = $scrut_tup2¹(tup2_0, tup2_1); +//│ set scrut = $scrut_tup2⁰(tup2_0, tup2_1); //│ return scrut() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 3 @@ -248,9 +245,9 @@ fun f(x) = if x is f([1,2]) //│ deforest > >>> fusing >>> //│ deforest > [1, 2] -> -//│ deforest > match: scrut⁹ -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁹, 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut⁹, 1) +//│ deforest > match: x⁶ +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(x⁶, 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(x⁶, 1) //│ deforest > <<< fusing <<< //│ = 3 @@ -282,8 +279,8 @@ fun f(x) = if x is f(A(1)) //│ deforest > >>> fusing >>> //│ deforest > A⁰(1) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.x⁰ +//│ deforest > match: x⁷ +//│ deforest > fields: x⁷.x⁸ //│ deforest > <<< fusing <<< //│ = 1 @@ -293,6 +290,10 @@ fun f(x) = if x is A(a) then x.A#x + a f(A(1)) //│ deforest > >>> fusing >>> +//│ deforest > A⁰(1) -> +//│ deforest > match: x⁹ +//│ deforest > fields: x⁹.x⁸ +//│ deforest > fields: x⁹.x⁸ //│ deforest > <<< fusing <<< //│ = 2 @@ -303,11 +304,11 @@ fun f(x) = if x is f(A(2)) + f(A(3)) //│ deforest > >>> fusing >>> //│ deforest > A⁰(3) -> -//│ deforest > match: scrut¹¹ -//│ deforest > fields: scrut¹¹.x⁰ +//│ deforest > match: x¹⁰ +//│ deforest > fields: x¹⁰.x⁸ //│ deforest > A⁰(2) -> -//│ deforest > match: scrut¹¹ -//│ deforest > fields: scrut¹¹.x⁰ +//│ deforest > match: x¹⁰ +//│ deforest > fields: x¹⁰.x⁸ //│ deforest > <<< fusing <<< //│ = 4 @@ -319,18 +320,18 @@ if localFuse(3) is B(a) then a localFuse(4) //│ deforest > >>> fusing >>> -//│ deforest > A⁰(x¹) -> -//│ deforest > match: scrut¹² -//│ deforest > fields: scrut¹².x⁰ -//│ deforest > A⁰(x¹) -> -//│ deforest > match: scrut¹² -//│ deforest > fields: scrut¹².x⁰ -//│ deforest > A⁰(x¹) -> -//│ deforest > match: scrut¹² -//│ deforest > fields: scrut¹².x⁰ +//│ deforest > A⁰(x¹¹) -> +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.x⁸ +//│ deforest > A⁰(x¹¹) -> +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.x⁸ +//│ deforest > A⁰(x¹¹) -> +//│ deforest > match: scrut¹ +//│ deforest > fields: scrut¹.x⁸ //│ deforest > B⁰(y⁰) -> -//│ deforest > match: scrut¹³ -//│ deforest > fields: scrut¹³.x² +//│ deforest > match: scrut² +//│ deforest > fields: scrut².x¹² //│ deforest > <<< fusing <<< //│ = B(4) @@ -356,7 +357,7 @@ let f() = x f() //│ deforest > >>> fusing >>> //│ deforest > A⁰(0) -> -//│ deforest > match: scrut¹⁴ +//│ deforest > match: scrut³ //│ deforest > <<< fusing <<< //│ = 1 //│ f = undefined @@ -377,9 +378,12 @@ fun dtor(x) = if x is dtor(A(B(0))) //│ deforest > >>> fusing >>> //│ deforest > B⁰(4) -> -//│ deforest > match: scrut¹⁵ +//│ deforest > match: y¹ //│ deforest > B⁰(4) -> -//│ deforest > match: scrut¹⁵ +//│ deforest > match: y¹ +//│ deforest > A⁰(tmp²) -> +//│ deforest > match: x¹³ +//│ deforest > fields: x¹³.x⁸ //│ deforest > <<< fusing <<< //│ = B(0) diff --git a/hkmc2/shared/src/test/mlscript/deforest/clashes.mls b/hkmc2/shared/src/test/mlscript/deforest/clashes.mls index bf90d3a3e3..780cb7f8c8 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/clashes.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/clashes.mls @@ -43,8 +43,8 @@ fun f2(p) = if p is A(b) then b + 2 f1(one) + f2(one) + f2(two) //│ deforest > >>> fusing >>> //│ deforest > A⁰(x⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x¹ +//│ deforest > match: p⁰ +//│ deforest > fields: p⁰.x¹ //│ deforest > <<< fusing <<< //│ = 9 diff --git a/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls b/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls index ef3f4da831..1469ecf40e 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/cyclic.mls @@ -44,11 +44,11 @@ fun f(a) = if a is (x => 0)(f(f(C))) //│ deforest > >>> fusing >>> //│ deforest > C⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: a⁰ //│ deforest > C⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: a⁰ //│ deforest > D⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: a⁰ //│ deforest > <<< fusing <<< //│ = 0 @@ -78,11 +78,11 @@ fun modSome(x, m) = if x is modSome(Some(17), 3) //│ deforest > >>> fusing >>> //│ deforest > Some⁰(tmp⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.x⁰ +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.x¹ //│ deforest > Some⁰(17) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.x⁰ +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.x¹ //│ deforest > <<< fusing <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ let Some_x, fv_m, fv_scrut, fv_tmp, inlinedVal; @@ -91,7 +91,7 @@ modSome(Some(17), 3) //│ let scrut, arg$Some$0$, tmp, tmp1; //│ match x //│ Some¹ => -//│ set arg$Some$0$ = x.x⁰; +//│ set arg$Some$0$ = x.x¹; //│ set scrut = >=⁰(arg$Some$0$, m); //│ match scrut //│ true => @@ -151,12 +151,12 @@ fun zipWith(xsys, f) = if xsys is zipWith([id(Nil), id(Nil)], (x, y) => x) //│ deforest > >>> fusing >>> //│ deforest > [tx⁰, ty⁰] -> -//│ deforest > match: scrut² -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut², 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut², 1) +//│ deforest > match: xsys⁰ +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(xsys⁰, 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(xsys⁰, 1) //│ deforest > [tmp¹, tmp²] -> -//│ deforest > match: scrut² -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut², 0) -//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(scrut², 1) +//│ deforest > match: xsys⁰ +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(xsys⁰, 0) +//│ deforest > fields: runtime⁰.Tuple⁰.get⁰(xsys⁰, 1) //│ deforest > <<< fusing <<< //│ = Nil diff --git a/hkmc2/shared/src/test/mlscript/deforest/determinism.mls b/hkmc2/shared/src/test/mlscript/deforest/determinism.mls index 74ef2dbed0..c0200e13ca 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/determinism.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/determinism.mls @@ -57,8 +57,8 @@ fun c2(x) = if x is c2 of A(A(A(A(A(A(1)))))) //│ deforest > >>> fusing >>> //│ deforest > A⁰(tmp⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.a¹ +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.a¹ //│ deforest > A⁰(tmp¹) -> //│ deforest > match: arg$A$0$⁰ //│ deforest > fields: arg$A$0$⁰.a¹ @@ -76,15 +76,14 @@ c2 of A(A(A(A(A(A(1)))))) //│ deforest > fields: arg$A$0$⁴.a¹ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, c2_19$c2, c2_19$scrut_A, c2_19$scrut_rest, c2_19$arg$A$0$_A, c2_19$arg$A$0$_rest, c2_19$arg$A$0$_A1, c2_19$arg$A$0$_rest1, c2_19$arg$A$0$_A2, c2_19$arg$A$0$_rest2, c2_19$arg$A$0$_A3, c2_19$arg$A$0$_rest3, c2_19$arg$A$0$_A4, c2_19$arg$A$0$_rest4, A_a, A_a1, A_a2, A_a3, A_a4, A_a5; +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, c2_19$c2, c2_19$x_A, c2_19$x_rest, c2_19$arg$A$0$_A, c2_19$arg$A$0$_rest, c2_19$arg$A$0$_A1, c2_19$arg$A$0$_rest1, c2_19$arg$A$0$_A2, c2_19$arg$A$0$_rest2, c2_19$arg$A$0$_A3, c2_19$arg$A$0$_rest3, c2_19$arg$A$0$_A4, c2_19$arg$A$0$_rest4, A_a, A_a1, A_a2, A_a3, A_a4, A_a5; //│ @private //│ define c2_19$c2 as fun c2_19$c2⁰(x) { -//│ let scrut, a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5; -//│ set scrut = x; -//│ return scrut(a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5) +//│ let a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5; +//│ return x(a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5) //│ }; //│ @affine(1) @private -//│ define c2_19$scrut_A as fun c2_19$scrut_A⁰(A_a6)(fv_a, fv_arg$A$0$, fv_arg$A$0$1, fv_arg$A$0$2, fv_arg$A$0$3, fv_arg$A$0$4, fv_arg$A$0$5) { +//│ define c2_19$x_A as fun c2_19$x_A⁰(A_a6)(fv_a, fv_arg$A$0$, fv_arg$A$0$1, fv_arg$A$0$2, fv_arg$A$0$3, fv_arg$A$0$4, fv_arg$A$0$5) { //│ set fv_arg$A$0$ = A_a6; //│ return fv_arg$A$0$(fv_a, fv_arg$A$0$1, fv_arg$A$0$2, fv_arg$A$0$3, fv_arg$A$0$4, fv_arg$A$0$5) //│ }; @@ -115,12 +114,12 @@ c2 of A(A(A(A(A(A(1)))))) //│ return fv_a //│ }; //│ @private -//│ define c2_19$scrut_rest as fun c2_19$scrut_rest⁰() { +//│ define c2_19$x_rest as fun c2_19$x_rest⁰() { //│ return null //│ }; //│ @private //│ define c2_19$arg$A$0$_rest as fun c2_19$arg$A$0$_rest⁰() { -//│ return c2_19$scrut_rest⁰() +//│ return c2_19$x_rest⁰() //│ }; //│ @private //│ define c2_19$arg$A$0$_rest1 as fun c2_19$arg$A$0$_rest¹() { @@ -139,11 +138,10 @@ c2 of A(A(A(A(A(A(1)))))) //│ return c2_19$arg$A$0$_rest³() //│ }; //│ define c2⁰ as fun c2¹(x) { -//│ let scrut, a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5; -//│ set scrut = x; -//│ match scrut +//│ let a, arg$A$0$, arg$A$0$1, arg$A$0$2, arg$A$0$3, arg$A$0$4, arg$A$0$5; +//│ match x //│ A¹ => -//│ set arg$A$0$ = scrut.a¹; +//│ set arg$A$0$ = x.a¹; //│ match arg$A$0$ //│ A¹ => //│ set arg$A$0$1 = arg$A$0$.a¹; @@ -191,7 +189,7 @@ c2 of A(A(A(A(A(A(1)))))) //│ set A_a4 = tmp3; //│ set tmp4 = c2_19$arg$A$0$_A⁰(A_a4); //│ set A_a5 = tmp4; -//│ set tmp5 = c2_19$scrut_A⁰(A_a5); +//│ set tmp5 = c2_19$x_A⁰(A_a5); //│ return c2_19$c2⁰(tmp5) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/deforest/erasedTypes.mls b/hkmc2/shared/src/test/mlscript/deforest/erasedTypes.mls index 7c47695f5f..19a9e321c7 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/erasedTypes.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/erasedTypes.mls @@ -48,27 +48,26 @@ fun selected(n: Int, k: Str) = Box(x) then [x, k] //│ deforest > >>> fusing >>> //│ deforest > Box¹(n⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.v¹ +//│ deforest > match: b² +//│ deforest > fields: b².v¹ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let selected_4$scrut_Box, selected_4$scrut_rest; +//│ let selected_4$b_Box, selected_4$b_rest; //│ @affine(1) @private -//│ define selected_4$scrut_Box as fun selected_4$scrut_Box⁰(Box_v: Int⁰)(fv_k: Str⁰, fv_x: ?, fv_arg$Box$0$: ?): ? { +//│ define selected_4$b_Box as fun selected_4$b_Box⁰(Box_v: Int⁰)(fv_k: Str⁰, fv_x: ?, fv_arg$Box$0$: ?): ? { //│ set fv_arg$Box$0$ = Box_v; //│ set fv_x = fv_arg$Box$0$; //│ return [fv_x, fv_k] //│ }; //│ @private -//│ define selected_4$scrut_rest as fun selected_4$scrut_rest⁰(): ? { +//│ define selected_4$b_rest as fun selected_4$b_rest⁰(): ? { //│ return null //│ }; //│ define selected⁰ as fun selected¹(n: Int⁰, k: Str⁰): ? { -//│ let b: ?, scrut: ?, x: ?, arg$Box$0$: ?, Box_v: Int⁰; +//│ let b: ?, x: ?, arg$Box$0$: ?, Box_v: Int⁰; //│ set Box_v = n; -//│ set b = selected_4$scrut_Box⁰(Box_v); -//│ set scrut = b; -//│ return scrut(k, x, arg$Box$0$) +//│ set b = selected_4$b_Box⁰(Box_v); +//│ return b(k, x, arg$Box$0$) //│ }; //│ end @@ -83,29 +82,28 @@ fun withRest(n: Int, k: Str) = [r, k] //│ deforest > >>> fusing >>> //│ deforest > Box¹(n¹) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.v¹ +//│ deforest > match: b³ +//│ deforest > fields: b³.v¹ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let withRest_4$scrut_Box, withRest_4$scrut_rest; +//│ let withRest_4$b_Box, withRest_4$b_rest; //│ @affine(1) @private -//│ define withRest_4$scrut_Box as fun withRest_4$scrut_Box⁰(Box_v: Int⁰)(fv_x: ?, fv_arg$Box$0$: ?, fv_tmp: ?, fv_k: Str⁰, fv_r: ?, fv_tmp1: ?): ? { +//│ define withRest_4$b_Box as fun withRest_4$b_Box⁰(Box_v: Int⁰)(fv_x: ?, fv_arg$Box$0$: ?, fv_tmp: ?, fv_k: Str⁰, fv_r: ?, fv_tmp1: ?): ? { //│ set fv_arg$Box$0$ = Box_v; //│ set fv_x = fv_arg$Box$0$; //│ set fv_tmp1 = fv_x; -//│ return withRest_4$scrut_rest⁰(fv_k, fv_r, fv_tmp1) +//│ return withRest_4$b_rest⁰(fv_k, fv_r, fv_tmp1) //│ }; //│ @private -//│ define withRest_4$scrut_rest as fun withRest_4$scrut_rest⁰(fv_k: Str⁰, fv_r: ?, fv_tmp: ?): ? { +//│ define withRest_4$b_rest as fun withRest_4$b_rest⁰(fv_k: Str⁰, fv_r: ?, fv_tmp: ?): ? { //│ set fv_r = fv_tmp; //│ return [fv_r, fv_k] //│ }; //│ define withRest⁰ as fun withRest¹(n: Int⁰, k: Str⁰): ? { -//│ let b: ?, r: ?, scrut: ?, x: ?, arg$Box$0$: ?, tmp: ?, Box_v: Int⁰; +//│ let b: ?, r: ?, x: ?, arg$Box$0$: ?, tmp: ?, Box_v: Int⁰; //│ set Box_v = n; -//│ set b = withRest_4$scrut_Box⁰(Box_v); -//│ set scrut = b; -//│ return scrut(x, arg$Box$0$, tmp, k, r, tmp) +//│ set b = withRest_4$b_Box⁰(Box_v); +//│ return b(x, arg$Box$0$, tmp, k, r, tmp) //│ }; //│ end @@ -118,24 +116,23 @@ fun noFields(n: Int, s: Str) = Pair then 1 //│ deforest > >>> fusing >>> //│ deforest > Pair¹(n², s⁰) -> -//│ deforest > match: scrut² +//│ deforest > match: p⁰ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let noFields_2$scrut_Pair, noFields_2$scrut_rest; +//│ let noFields_2$p_Pair, noFields_2$p_rest; //│ @affine(1) @private -//│ define noFields_2$scrut_Pair as fun noFields_2$scrut_Pair⁰(_a: Int⁰, _b: Str⁰)(): ? { +//│ define noFields_2$p_Pair as fun noFields_2$p_Pair⁰(_a: Int⁰, _b: Str⁰)(): ? { //│ return 1 //│ }; //│ @private -//│ define noFields_2$scrut_rest as fun noFields_2$scrut_rest⁰(): ? { +//│ define noFields_2$p_rest as fun noFields_2$p_rest⁰(): ? { //│ return null //│ }; //│ define noFields⁰ as fun noFields¹(n: Int⁰, s: Str⁰): ? { -//│ let p: ?, scrut: ?, Pair_a: Int⁰, Pair_b: Str⁰; +//│ let p: ?, Pair_a: Int⁰, Pair_b: Str⁰; //│ set Pair_a = n; //│ set Pair_b = s; -//│ set p = noFields_2$scrut_Pair⁰(Pair_a, Pair_b); -//│ set scrut = p; -//│ return scrut() +//│ set p = noFields_2$p_Pair⁰(Pair_a, Pair_b); +//│ return p() //│ }; //│ end diff --git a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls index f192b1ad5d..8f27d08020 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/basic.mls @@ -24,8 +24,8 @@ fun c(x) = if x is c(p(3)) //│ deforest > >>> fusing >>> //│ deforest > X⁰(x⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.a⁰ +//│ deforest > match: x¹ +//│ deforest > fields: x¹.a⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > p_4$p: [1, 2] @@ -43,9 +43,9 @@ fun c(x) = if x is c(p(true)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: x² //│ deforest > B⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: x² //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > p_7$p: [1, 0] @@ -62,16 +62,16 @@ fun g(y) = if y is g(f(p())) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp⁰) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².a⁰ +//│ deforest > match: x³ +//│ deforest > fields: x³.a⁰ //│ deforest > Y⁰(O⁰) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.b⁰ +//│ deforest > match: y⁰ +//│ deforest > fields: y⁰.b⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > f_10$f: [1, 2] //│ eta-expansion > p_8$p: [0, 2, 2] -//│ eta-expansion > f_10$scrut_X: [1, 2, 2] +//│ eta-expansion > f_10$x_X: [1, 2, 2] //│ eta-expansion > <<< eta-expansion targets shapes <<< //│ = O @@ -85,14 +85,14 @@ fun c(x) = if x is c(p(true)) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp¹) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.a⁰ +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.a⁰ //│ deforest > Y⁰(22) -> //│ deforest > match: arg$X$0$⁰ //│ deforest > fields: arg$X$0$⁰.b⁰ //│ deforest > X⁰(tmp²) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.a⁰ +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.a⁰ //│ deforest > Y⁰(33) -> //│ deforest > match: arg$X$0$⁰ //│ deforest > fields: arg$X$0$⁰.b⁰ @@ -112,14 +112,14 @@ fun main(x) = main(3) //│ deforest > >>> fusing >>> //│ deforest > X⁰(tmp³) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.a⁰ +//│ deforest > match: x⁵ +//│ deforest > fields: x⁵.a⁰ //│ deforest > Y⁰(tmp⁴) -> //│ deforest > match: arg$X$0$¹ //│ deforest > fields: arg$X$0$¹.b⁰ //│ deforest > X⁰(tmp³) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.a⁰ +//│ deforest > match: x⁵ +//│ deforest > fields: x⁵.a⁰ //│ deforest > Y⁰(tmp⁴) -> //│ deforest > match: arg$X$0$¹ //│ deforest > fields: arg$X$0$¹.b⁰ @@ -138,8 +138,8 @@ fun c(x) = if x is X(a) then a c(p(3)) //│ deforest > >>> fusing >>> -//│ deforest > X⁰(x¹) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.a⁰ +//│ deforest > X⁰(x⁶) -> +//│ deforest > match: x⁷ +//│ deforest > fields: x⁷.a⁰ //│ deforest > <<< fusing <<< //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls index 330d5ea907..cab24ae204 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/eta-expansion/recursive.mls @@ -22,11 +22,11 @@ fun sum(ls, acc) = if ls is sum(enum(5), 0) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: ls⁰ //│ deforest > Cons⁰(x⁰, tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h⁰ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > enum_13$enum: [1, 7] @@ -105,11 +105,11 @@ fun len(ls) = if ls is len(flatten(id((1 :: 2 :: Nil) :: (3 :: Nil) :: Nil))) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: ls¹ //│ deforest > Cons⁰(h¹, tmp¹) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h⁰ +//│ deforest > fields: ls¹.t⁰ //│ deforest > <<< fusing <<< //│ eta-expansion > >>> eta-expansion targets shapes >>> //│ eta-expansion > flatten_31_append_16$append: [2, 6] diff --git a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls index f95772c3a3..dc71fa1982 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls @@ -33,8 +33,8 @@ c(AA(3)) //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x⁰ +//│ deforest > match: p⁰ +//│ deforest > fields: p⁰.x⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -43,11 +43,13 @@ c(AA(3)) fun c(x) = if x is AA(_) then x.AA#x + x.AA#x c(AA(7)) //│ deforest > >>> non-affine syms >>> -//│ deforest > tmp@1 -//│ deforest > x_for_c@13 -//│ deforest > x_for_c@24 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> +//│ deforest > AA⁰(7) -> +//│ deforest > match: x¹ +//│ deforest > fields: x¹.x⁰ +//│ deforest > fields: x¹.x⁰ +//│ deforest > fields: x¹.x⁰ //│ deforest > <<< fusing <<< //│ = 14 @@ -57,14 +59,14 @@ fun c(p) = cl() + cl() c(AA(7)) //│ deforest > >>> non-affine syms >>> -//│ deforest > cap_ub_(term:lambda,0)_for_c@31 -//│ deforest > cap_ub_(term:lambda,0)_for_c@49 -//│ deforest > cl_for_c@20 -//│ deforest > cl_for_c@38 -//│ deforest > lambda_for_c@29 -//│ deforest > lambda_for_c@47 -//│ deforest > p_for_c@21 -//│ deforest > p_for_c@39 +//│ deforest > cap_ub_(term:lambda,0)_for_c@30 +//│ deforest > cap_ub_(term:lambda,0)_for_c@47 +//│ deforest > cl_for_c@19 +//│ deforest > cl_for_c@36 +//│ deforest > lambda_for_c@28 +//│ deforest > lambda_for_c@45 +//│ deforest > p_for_c@20 +//│ deforest > p_for_c@37 //│ deforest > tmp@1 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> @@ -82,11 +84,11 @@ affine_branched(AA(0), AA(1), (y) => if y is AA(x) then x + 2) //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.x⁰ +//│ deforest > match: y⁰ +//│ deforest > fields: y⁰.x⁰ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².x⁰ +//│ deforest > match: x² +//│ deforest > fields: x².x⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -107,13 +109,13 @@ fun c(p) = if p is AA(v) then v fun apply(f, x) = f(x) + f(x) apply(c, AA(3)) //│ deforest > >>> non-affine syms >>> -//│ deforest > c_for_c@30 -//│ deforest > cap_ub_(term:c,0)_for_c@33 -//│ deforest > f_for_apply@22 -//│ deforest > f_for_apply@49 +//│ deforest > c_for_c@29 +//│ deforest > cap_ub_(term:c,0)_for_c@32 +//│ deforest > f_for_apply@21 +//│ deforest > f_for_apply@46 //│ deforest > tmp@1 -//│ deforest > x_for_apply@21 -//│ deforest > x_for_apply@48 +//│ deforest > x_for_apply@20 +//│ deforest > x_for_apply@45 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -125,13 +127,13 @@ fun c(k, x) = if k is AA(_) then x.AA#x + x.AA#x + x.AA#x c(AA(0), AA(3)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@2 -//│ deforest > x_for_c@18 -//│ deforest > x_for_c@33 +//│ deforest > x_for_c@17 +//│ deforest > x_for_c@31 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x⁰ +//│ deforest > match: k⁰ +//│ deforest > fields: k⁰.x⁰ //│ deforest > <<< fusing <<< //│ = 9 @@ -146,8 +148,8 @@ fun c(y) = if y is AA(_) then c(AA(1)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@1 -//│ deforest > y_for_c@14 -//│ deforest > y_for_c@26 +//│ deforest > y_for_c@13 +//│ deforest > y_for_c@24 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -160,9 +162,9 @@ fun c2(x) = if x is AA(AA(_)) then 1 else 0 let p = AA(AA(AA(10))) in c1(p) + c2(p) //│ deforest > >>> non-affine syms >>> -//│ deforest > p@5 -//│ deforest > tmp@1 +//│ deforest > p@1 //│ deforest > tmp@2 +//│ deforest > tmp@3 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -177,8 +179,8 @@ fun c2(p) = if p is Many(Once(v)) then v fun apply(f, g, p) = f(p) + g(p) apply(c1, c2, Many(Once(10))) //│ deforest > >>> non-affine syms >>> -//│ deforest > p_for_apply@32 -//│ deforest > p_for_apply@78 +//│ deforest > p_for_apply@30 +//│ deforest > p_for_apply@72 //│ deforest > tmp@1 //│ deforest > tmp@2 //│ deforest > <<< non-affine syms <<< @@ -194,23 +196,23 @@ fun nest_consume(f, p) = if p is AA(i) then f(i) + f(i) fun nest_pickB(p) = if p is BB(b) then b + 1 nest_consume(nest_pickB, nest_prod(10)) //│ deforest > >>> non-affine syms >>> -//│ deforest > arg$AA$0$_for_nest_consume@47 -//│ deforest > arg$AA$0$_for_nest_consume@76 -//│ deforest > cap_ub_(term:nest_pickB,0)_for_nest_pickB@53 -//│ deforest > f_for_nest_consume@38 -//│ deforest > f_for_nest_consume@67 -//│ deforest > i_for_nest_consume@37 -//│ deforest > i_for_nest_consume@66 -//│ deforest > nest_pickB_for_nest_pickB@50 -//│ deforest > sel_res_for_nest_consume@48 -//│ deforest > sel_res_for_nest_consume@77 -//│ deforest > tmp_for_nest_prod@34 -//│ deforest > x_for_nest_prod@31 +//│ deforest > arg$AA$0$_for_nest_consume@45 +//│ deforest > arg$AA$0$_for_nest_consume@72 +//│ deforest > cap_ub_(term:nest_pickB,0)_for_nest_pickB@50 +//│ deforest > f_for_nest_consume@36 +//│ deforest > f_for_nest_consume@63 +//│ deforest > i_for_nest_consume@35 +//│ deforest > i_for_nest_consume@62 +//│ deforest > nest_pickB_for_nest_pickB@47 +//│ deforest > sel_res_for_nest_consume@46 +//│ deforest > sel_res_for_nest_consume@73 +//│ deforest > tmp_for_nest_prod@32 +//│ deforest > x_for_nest_prod@29 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.x⁰ +//│ deforest > match: p¹ +//│ deforest > fields: p¹.x⁰ //│ deforest > <<< fusing <<< //│ = 22 @@ -223,16 +225,16 @@ fun length(ls) = if ls is fun from(n, acc) = if n == 0 then acc else from(n - 1, n :: acc) length(from(5, Nil)) //│ deforest > >>> non-affine syms >>> -//│ deforest > call_res_for_from@35 -//│ deforest > call_res_for_from@76 -//│ deforest > n_for_from@28 -//│ deforest > n_for_from@69 -//│ deforest > tmp_for_from@33 -//│ deforest > tmp_for_from@74 +//│ deforest > call_res_for_from@34 +//│ deforest > call_res_for_from@73 +//│ deforest > n_for_from@27 +//│ deforest > n_for_from@66 +//│ deforest > tmp_for_from@32 +//│ deforest > tmp_for_from@71 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> -//│ deforest > acc_for_from@29 -//│ deforest > acc_for_from@70 +//│ deforest > acc_for_from@28 +//│ deforest > acc_for_from@67 //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -253,15 +255,15 @@ length(append(id(1 :: Nil), 2 :: Nil)) //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(h⁰, tmp¹) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.h¹ -//│ deforest > fields: scrut⁵.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h¹ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.h¹ -//│ deforest > fields: scrut⁵.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h¹ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁵ +//│ deforest > match: ls⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -284,15 +286,15 @@ length(ping(id(1 :: 2 :: Nil), 3 :: 4 :: Nil)) //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(3, tmp²) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.h¹ -//│ deforest > fields: scrut⁶.t⁰ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h¹ +//│ deforest > fields: ls¹.t⁰ //│ deforest > Cons⁰(4, Nil⁰) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.h¹ -//│ deforest > fields: scrut⁶.t⁰ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h¹ +//│ deforest > fields: ls¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: ls¹ //│ deforest > <<< fusing <<< //│ = 2 @@ -314,11 +316,11 @@ sum(append(id(Nil), Nil), 0) //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(h², tmp³) -> -//│ deforest > match: scrut⁷ -//│ deforest > fields: scrut⁷.h¹ -//│ deforest > fields: scrut⁷.t⁰ +//│ deforest > match: ls² +//│ deforest > fields: ls².h¹ +//│ deforest > fields: ls².t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁷ +//│ deforest > match: ls² //│ deforest > <<< fusing <<< //│ = 0 @@ -355,11 +357,11 @@ length(flatten(id((1 :: Nil) :: Nil))) //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(h³, tmp⁴) -> -//│ deforest > match: scrut⁸ -//│ deforest > fields: scrut⁸.h¹ -//│ deforest > fields: scrut⁸.t⁰ +//│ deforest > match: ls³ +//│ deforest > fields: ls³.h¹ +//│ deforest > fields: ls³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁸ +//│ deforest > match: ls³ //│ deforest > <<< fusing <<< //│ = 1 @@ -372,8 +374,8 @@ fun alias_then_patmat(x) = alias_then_patmat(Cons(12, 13)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@1 -//│ deforest > x_for_alias_then_patmat@19 -//│ deforest > x_for_alias_then_patmat@36 +//│ deforest > x_for_alias_then_patmat@18 +//│ deforest > x_for_alias_then_patmat@34 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< @@ -390,15 +392,14 @@ fun foo(a) = AA then a.AA#x() + a.AA#x() foo of AA of () => 1 //│ deforest > >>> non-affine syms >>> -//│ deforest > a_for_foo@21 -//│ deforest > a_for_foo@34 -//│ deforest > cap_ub_(term:lambda,0)_for_lambda@19 -//│ deforest > lambda_for_lambda@17 -//│ deforest > tmp@1 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> +//│ deforest > AA⁰(lambda⁰) -> +//│ deforest > match: a⁰ +//│ deforest > fields: a⁰.x⁰ +//│ deforest > fields: a⁰.x⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -410,19 +411,19 @@ fun foo(a) = f() + f() foo of AA of () => 1 //│ deforest > >>> non-affine syms >>> -//│ deforest > a_for_foo@21 -//│ deforest > a_for_foo@34 -//│ deforest > cap_ub_(term:lambda,0)_for_lambda@19 -//│ deforest > f_for_foo@22 -//│ deforest > f_for_foo@35 -//│ deforest > lambda_for_lambda@17 -//│ deforest > sel_res_for_foo@30 -//│ deforest > sel_res_for_foo@43 -//│ deforest > tmp@1 +//│ deforest > cap_ub_(term:lambda,0)_for_lambda@18 +//│ deforest > f_for_foo@20 +//│ deforest > f_for_foo@32 +//│ deforest > lambda_for_lambda@16 +//│ deforest > sel_res_for_foo@29 +//│ deforest > sel_res_for_foo@41 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> +//│ deforest > AA⁰(lambda¹) -> +//│ deforest > match: a¹ +//│ deforest > fields: a¹.x⁰ //│ deforest > <<< fusing <<< //│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/deforest/imperative.mls b/hkmc2/shared/src/test/mlscript/deforest/imperative.mls index 661bbee616..2dc85e5756 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/imperative.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/imperative.mls @@ -46,11 +46,11 @@ fun bar(v) = if v is foo(bar, 123) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: v⁰ //│ deforest > A⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: v⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: v⁰ //│ deforest > <<< fusing <<< //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls index 7fd35953e8..b4ecfac5e5 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls @@ -29,65 +29,65 @@ fun lscomp1(ls) = if ls is lscomp1(zip(enumFromTo(1, 3), enumFromTo(2, 4))) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: xs_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h⁰ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: xs_zip⁰ +//│ deforest > fields: xs_zip⁰.h⁰ +//│ deforest > fields: xs_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: ys_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: ys_zip⁰ +//│ deforest > fields: ys_zip⁰.h⁰ +//│ deforest > fields: ys_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: ls2⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: ls2⁰ //│ deforest > Cons⁰(tmp¹, tmp²) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².h⁰ -//│ deforest > fields: scrut².t⁰ +//│ deforest > match: ls2⁰ +//│ deforest > fields: ls2⁰.h⁰ +//│ deforest > fields: ls2⁰.t⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a¹ //│ deforest > fields: arg$Cons$0$⁰.b⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: xs_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h⁰ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: xs_zip⁰ +//│ deforest > fields: xs_zip⁰.h⁰ +//│ deforest > fields: xs_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: ys_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: ys_zip⁰ +//│ deforest > fields: ys_zip⁰.h⁰ +//│ deforest > fields: ys_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: ls2⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: ls2⁰ //│ deforest > Cons⁰(tmp¹, tmp²) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².h⁰ -//│ deforest > fields: scrut².t⁰ +//│ deforest > match: ls2⁰ +//│ deforest > fields: ls2⁰.h⁰ +//│ deforest > fields: ls2⁰.t⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a¹ //│ deforest > fields: arg$Cons$0$⁰.b⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: ys_zip⁰ +//│ deforest > fields: ys_zip⁰.h⁰ +//│ deforest > fields: ys_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: ys_zip⁰ //│ deforest > Cons⁰(a⁰, tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h⁰ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: xs_zip⁰ +//│ deforest > fields: xs_zip⁰.h⁰ +//│ deforest > fields: xs_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: xs_zip⁰ //│ deforest > <<< fusing <<< //│ = Cons(Pair(1, 3), Cons(Pair(2, 4), Cons(Pair(3, 5), Nil))) @@ -107,91 +107,91 @@ fun f1(a1) = if a1 is concatMap(f1, Pair(5, 10) :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(tmp³, Nil⁰) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.h⁰ -//│ deforest > fields: scrut³.t⁰ +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h⁰ +//│ deforest > fields: xs⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: xs⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: xs⁰ //│ deforest > Cons⁰(tmp⁴, tmp⁵) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Cons⁰(tmp⁶, tmp⁷) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Cons⁰(tmp⁸, Nil⁰) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁴ +//│ deforest > match: ls⁰ //│ deforest > Pair⁰(a², b¹) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.a¹ -//│ deforest > fields: scrut⁵.b⁰ +//│ deforest > match: a2⁰ +//│ deforest > fields: a2⁰.a¹ +//│ deforest > fields: a2⁰.b⁰ //│ deforest > Pair⁰(2, 3) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.a¹ -//│ deforest > fields: scrut⁵.b⁰ +//│ deforest > match: a2⁰ +//│ deforest > fields: a2⁰.a¹ +//│ deforest > fields: a2⁰.b⁰ //│ deforest > Pair⁰(1, 3) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.a¹ -//│ deforest > fields: scrut⁵.b⁰ +//│ deforest > match: a2⁰ +//│ deforest > fields: a2⁰.a¹ +//│ deforest > fields: a2⁰.b⁰ //│ deforest > Cons⁰(tmp³, Nil⁰) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.h⁰ -//│ deforest > fields: scrut³.t⁰ +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h⁰ +//│ deforest > fields: xs⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: xs⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: xs⁰ //│ deforest > Cons⁰(tmp⁴, tmp⁵) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Cons⁰(tmp⁶, tmp⁷) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Cons⁰(tmp⁸, Nil⁰) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁴ +//│ deforest > match: ls⁰ //│ deforest > Pair⁰(a², b¹) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.a¹ -//│ deforest > fields: scrut⁵.b⁰ +//│ deforest > match: a2⁰ +//│ deforest > fields: a2⁰.a¹ +//│ deforest > fields: a2⁰.b⁰ //│ deforest > Pair⁰(2, 3) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.a¹ -//│ deforest > fields: scrut⁵.b⁰ +//│ deforest > match: a2⁰ +//│ deforest > fields: a2⁰.a¹ +//│ deforest > fields: a2⁰.b⁰ //│ deforest > Pair⁰(1, 3) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.a¹ -//│ deforest > fields: scrut⁵.b⁰ +//│ deforest > match: a2⁰ +//│ deforest > fields: a2⁰.a¹ +//│ deforest > fields: a2⁰.b⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: xs⁰ //│ deforest > Cons⁰(h¹, tmp⁹) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.h⁰ -//│ deforest > fields: scrut³.t⁰ +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h⁰ +//│ deforest > fields: xs⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: xs⁰ //│ deforest > Cons⁰(tmp¹⁰, Nil⁰) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁴ +//│ deforest > match: ls⁰ //│ deforest > Pair⁰(5, 10) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.a¹ -//│ deforest > fields: scrut⁶.b⁰ +//│ deforest > match: a1⁰ +//│ deforest > fields: a1⁰.a¹ +//│ deforest > fields: a1⁰.b⁰ //│ deforest > <<< fusing <<< //│ = Cons(Pair(5, 1), Cons(Pair(5, 2), Cons(Pair(5, 5), Nil))) @@ -207,15 +207,15 @@ fun lscomp2(ls2, h1, t1) = if ls2 is lscomp1(1 :: 2 :: Nil, 3 :: 4 :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, tmp¹¹) -> -//│ deforest > match: scrut⁷ -//│ deforest > fields: scrut⁷.h⁰ -//│ deforest > fields: scrut⁷.t⁰ +//│ deforest > match: ls1⁰ +//│ deforest > fields: ls1⁰.h⁰ +//│ deforest > fields: ls1⁰.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: scrut⁷ -//│ deforest > fields: scrut⁷.h⁰ -//│ deforest > fields: scrut⁷.t⁰ +//│ deforest > match: ls1⁰ +//│ deforest > fields: ls1⁰.h⁰ +//│ deforest > fields: ls1⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁷ +//│ deforest > match: ls1⁰ //│ deforest > <<< fusing <<< //│ = Cons(Pair(1, 3), Cons(Pair(1, 4), Nil)) @@ -229,21 +229,21 @@ if A is B then Nil //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, tmp¹²) -> -//│ deforest > match: scrut⁸ -//│ deforest > fields: scrut⁸.h⁰ -//│ deforest > fields: scrut⁸.t⁰ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h⁰ +//│ deforest > fields: ls¹.t⁰ //│ deforest > Cons⁰(2, tmp¹³) -> -//│ deforest > match: scrut⁸ -//│ deforest > fields: scrut⁸.h⁰ -//│ deforest > fields: scrut⁸.t⁰ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h⁰ +//│ deforest > fields: ls¹.t⁰ //│ deforest > Cons⁰(3, Nil⁰) -> -//│ deforest > match: scrut⁸ -//│ deforest > fields: scrut⁸.h⁰ -//│ deforest > fields: scrut⁸.t⁰ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h⁰ +//│ deforest > fields: ls¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁸ +//│ deforest > match: ls¹ //│ deforest > A⁰ -> -//│ deforest > match: scrut⁹ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = Cons(2, Cons(3, Cons(4, Nil))) @@ -261,33 +261,33 @@ fun h() = h() //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, tmp¹⁴) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.h⁰ -//│ deforest > fields: scrut¹⁰.t⁰ +//│ deforest > match: ls² +//│ deforest > fields: ls².h⁰ +//│ deforest > fields: ls².t⁰ //│ deforest > Cons⁰(2, tmp¹⁵) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.h⁰ -//│ deforest > fields: scrut¹⁰.t⁰ +//│ deforest > match: ls² +//│ deforest > fields: ls².h⁰ +//│ deforest > fields: ls².t⁰ //│ deforest > Cons⁰(3, Nil⁰) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.h⁰ -//│ deforest > fields: scrut¹⁰.t⁰ +//│ deforest > match: ls² +//│ deforest > fields: ls².h⁰ +//│ deforest > fields: ls².t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁰ +//│ deforest > match: ls² //│ deforest > Cons⁰(1, tmp¹⁴) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.h⁰ -//│ deforest > fields: scrut¹⁰.t⁰ +//│ deforest > match: ls² +//│ deforest > fields: ls².h⁰ +//│ deforest > fields: ls².t⁰ //│ deforest > Cons⁰(2, tmp¹⁵) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.h⁰ -//│ deforest > fields: scrut¹⁰.t⁰ +//│ deforest > match: ls² +//│ deforest > fields: ls².h⁰ +//│ deforest > fields: ls².t⁰ //│ deforest > Cons⁰(3, Nil⁰) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.h⁰ -//│ deforest > fields: scrut¹⁰.t⁰ +//│ deforest > match: ls² +//│ deforest > fields: ls².h⁰ +//│ deforest > fields: ls².t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁰ +//│ deforest > match: ls² //│ deforest > <<< fusing <<< //│ = Cons( //│ [1, 1], diff --git a/hkmc2/shared/src/test/mlscript/deforest/module.mls b/hkmc2/shared/src/test/mlscript/deforest/module.mls index 565917aec2..666e148f6e 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/module.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/module.mls @@ -16,8 +16,8 @@ module M with M.f(M.p()) //│ deforest > >>> fusing >>> //│ deforest > M⁰.this.XX⁰(tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.a⁰ +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.a⁰ //│ deforest > M⁰.this.YY⁰(M⁰.this.OO⁰) -> //│ deforest > match: arg$XX$0$⁰ //│ deforest > fields: arg$XX$0$⁰.b⁰ @@ -43,23 +43,23 @@ module DeforestList with DeforestList.res + DeforestList.main() //│ deforest > >>> fusing >>> //│ deforest > DeforestList⁰.this.Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: ls⁰ //│ deforest > DeforestList⁰.this.Cons⁰(n⁰, tmp¹) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > DeforestList⁰.this.Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: ls⁰ //│ deforest > DeforestList⁰.this.Cons⁰(n⁰, tmp¹) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > DeforestList⁰.this.Cons⁰(n⁰, tmp¹) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > DeforestList⁰.this.Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: ls⁰ //│ deforest > <<< fusing <<< //│ = 5056 @@ -83,23 +83,23 @@ module DeforestList with DeforestList.res + DeforestList.main() //│ deforest > >>> fusing >>> //│ deforest > DeforestList¹.this.Nil¹ -> -//│ deforest > match: scrut² +//│ deforest > match: ls¹ //│ deforest > DeforestList¹.this.Cons¹(n¹, tmp²) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².h¹ -//│ deforest > fields: scrut².t¹ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h¹ +//│ deforest > fields: ls¹.t¹ //│ deforest > DeforestList¹.this.Nil¹ -> -//│ deforest > match: scrut² +//│ deforest > match: ls¹ //│ deforest > DeforestList¹.this.Cons¹(n¹, tmp²) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².h¹ -//│ deforest > fields: scrut².t¹ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h¹ +//│ deforest > fields: ls¹.t¹ //│ deforest > DeforestList¹.this.Cons¹(n¹, tmp²) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².h¹ -//│ deforest > fields: scrut².t¹ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h¹ +//│ deforest > fields: ls¹.t¹ //│ deforest > DeforestList¹.this.Nil¹ -> -//│ deforest > match: scrut² +//│ deforest > match: ls¹ //│ deforest > <<< fusing <<< //│ = 5056 @@ -116,11 +116,11 @@ module MMM with MMM.f() //│ deforest > >>> fusing >>> //│ deforest > MMM⁰.this.A⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: scrut⁰ //│ deforest > MMM⁰.this.A⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: scrut⁰ //│ deforest > MMM⁰.this.A⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -140,7 +140,7 @@ module M with M.f(M.X) //│ deforest > >>> fusing >>> //│ deforest > M¹.X⁰ -> -//│ deforest > match: scrut⁴ +//│ deforest > match: k⁰ //│ deforest > <<< fusing <<< //│ = X @@ -167,25 +167,25 @@ module M with [M.f(3), M.g(true)] //│ deforest > >>> fusing >>> //│ deforest > B⁰(0) -> -//│ deforest > match: scrut⁵ +//│ deforest > match: scrut¹ //│ deforest > D⁰ -> -//│ deforest > match: scrut⁵ +//│ deforest > match: scrut¹ //│ deforest > A¹ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: scrut² //│ deforest > A¹ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: scrut² //│ deforest > C⁰ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: scrut² //│ deforest > B⁰(0) -> -//│ deforest > match: scrut⁵ +//│ deforest > match: scrut¹ //│ deforest > D⁰ -> -//│ deforest > match: scrut⁵ +//│ deforest > match: scrut¹ //│ deforest > A¹ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: scrut² //│ deforest > A¹ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: scrut² //│ deforest > C⁰ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: scrut² //│ deforest > <<< fusing <<< //│ = [D, A] @@ -206,8 +206,8 @@ private fun g(x, module I: O.I) = if x is g(f(O.I), O.I) //│ deforest > >>> fusing >>> //│ deforest > I⁰.C¹(3) -> -//│ deforest > match: scrut⁷ -//│ deforest > fields: scrut⁷.x⁰ +//│ deforest > match: x¹ +//│ deforest > fields: x¹.x² //│ deforest > <<< fusing <<< //│ = 5 @@ -220,7 +220,7 @@ private fun g(x, module O: O) = if x is g(f(O), O) //│ deforest > >>> fusing >>> //│ deforest > O⁰.I¹.C¹(3) -> -//│ deforest > match: scrut⁸ -//│ deforest > fields: scrut⁸.x⁰ +//│ deforest > match: x³ +//│ deforest > fields: x³.x² //│ deforest > <<< fusing <<< //│ = 5 diff --git a/hkmc2/shared/src/test/mlscript/deforest/multiArgLists.mls b/hkmc2/shared/src/test/mlscript/deforest/multiArgLists.mls index 576ffa7e56..c8501f5134 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/multiArgLists.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/multiArgLists.mls @@ -13,9 +13,9 @@ fun test(a)(b) = test(Pair(1, 2))(3) //│ deforest > >>> fusing >>> //│ deforest > Pair⁰(1, 2) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x⁰ -//│ deforest > fields: scrut⁰.y⁰ +//│ deforest > match: a⁰ +//│ deforest > fields: a⁰.x⁰ +//│ deforest > fields: a⁰.y⁰ //│ deforest > <<< fusing <<< //│ = 6 @@ -29,8 +29,8 @@ fun unbox(b)(offset) = unbox(Box(10))(5) //│ deforest > >>> fusing >>> //│ deforest > Box⁰(10) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.v⁰ +//│ deforest > match: b⁰ +//│ deforest > fields: b⁰.v⁰ //│ deforest > <<< fusing <<< //│ = 15 diff --git a/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls b/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls index ce21a3f8ff..ab7b536f4b 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls @@ -37,14 +37,14 @@ let a = AA(AA(3)) f(a, 2) + g(a) + f(BB(3), 2) + f(AA(AA(4)), 5) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x⁰ +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.x¹ //│ deforest > AA⁰(4) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.x⁰ +//│ deforest > match: a⁰ +//│ deforest > fields: a⁰.x¹ //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x¹ +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.x² //│ deforest > <<< fusing <<< //│ = 37 //│ a = AA(AA(3)) @@ -61,17 +61,17 @@ fun f(x, y) = f(AA(AA(3)), 9) + f(AA(AA(4)), 10) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp¹) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².x⁰ +//│ deforest > match: x³ +//│ deforest > fields: x³.x¹ //│ deforest > AA⁰(4) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x⁰ +//│ deforest > match: a¹ +//│ deforest > fields: a¹.x¹ //│ deforest > AA⁰(tmp²) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².x⁰ +//│ deforest > match: x³ +//│ deforest > fields: x³.x¹ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x⁰ +//│ deforest > match: a¹ +//│ deforest > fields: a¹.x¹ //│ deforest > <<< fusing <<< //│ = 30 @@ -87,11 +87,11 @@ fun f(x) = f(AA(AA(3))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp³) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.x⁰ +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.x¹ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.x⁰ +//│ deforest > match: a² +//│ deforest > fields: a².x¹ //│ deforest > <<< fusing <<< //│ = 5 @@ -105,17 +105,17 @@ fun f(x) = f(AA(AA(A))) + f(AA(AA(A))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁴) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.x⁰ +//│ deforest > match: x⁵ +//│ deforest > fields: x⁵.x¹ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x⁰ +//│ deforest > fields: arg$AA$0$⁰.x¹ //│ deforest > AA⁰(tmp⁵) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.x⁰ +//│ deforest > match: x⁵ +//│ deforest > fields: x⁵.x¹ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x⁰ +//│ deforest > fields: arg$AA$0$⁰.x¹ //│ deforest > <<< fusing <<< //│ = "A3A3" @@ -127,11 +127,11 @@ fun c2(x) = if x is c2(AA(AA(0))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁶) -> -//│ deforest > match: scrut⁷ -//│ deforest > fields: scrut⁷.x⁰ +//│ deforest > match: x⁶ +//│ deforest > fields: x⁶.x¹ //│ deforest > AA⁰(0) -> //│ deforest > match: arg$AA$0$¹ -//│ deforest > fields: arg$AA$0$¹.x⁰ +//│ deforest > fields: arg$AA$0$¹.x¹ //│ deforest > <<< fusing <<< //│ = 0 @@ -143,11 +143,11 @@ fun f(a) = if a is f(AA(BB(B))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁷) -> -//│ deforest > match: scrut⁸ -//│ deforest > fields: scrut⁸.x⁰ +//│ deforest > match: a³ +//│ deforest > fields: a³.x¹ //│ deforest > BB⁰(B⁰) -> //│ deforest > match: arg$AA$0$² -//│ deforest > fields: arg$AA$0$².x¹ +//│ deforest > fields: arg$AA$0$².x² //│ deforest > B⁰ -> //│ deforest > match: arg$BB$0$⁰ //│ deforest > <<< fusing <<< @@ -165,23 +165,23 @@ fun test(x, y, z, i) = if x is test(AA(1), AA(2), BB(3), 4) + test(AA(1), AA(2), BB(3), 4) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut⁹ -//│ deforest > fields: scrut⁹.x¹ +//│ deforest > match: z⁰ +//│ deforest > fields: z⁰.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.x⁰ +//│ deforest > match: y⁰ +//│ deforest > fields: y⁰.x¹ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut¹¹ -//│ deforest > fields: scrut¹¹.x⁰ +//│ deforest > match: x⁷ +//│ deforest > fields: x⁷.x¹ //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut⁹ -//│ deforest > fields: scrut⁹.x¹ +//│ deforest > match: z⁰ +//│ deforest > fields: z⁰.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.x⁰ +//│ deforest > match: y⁰ +//│ deforest > fields: y⁰.x¹ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut¹¹ -//│ deforest > fields: scrut¹¹.x⁰ +//│ deforest > match: x⁷ +//│ deforest > fields: x⁷.x¹ //│ deforest > <<< fusing <<< //│ = 10 @@ -200,14 +200,14 @@ fun aa(x) = AA(x) let cc = CC("cc") f1(aa(BB(cc))) + f2(aa(BB(CC(0)))) + f3(cc) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x²) -> -//│ deforest > match: scrut¹² -//│ deforest > AA⁰(x²) -> -//│ deforest > match: scrut¹³ -//│ deforest > fields: scrut¹³.x⁰ +//│ deforest > AA⁰(x⁸) -> +//│ deforest > match: x⁹ +//│ deforest > AA⁰(x⁸) -> +//│ deforest > match: x¹⁰ +//│ deforest > fields: x¹⁰.x¹ //│ deforest > BB⁰(cc⁰) -> -//│ deforest > match: scrut¹⁴ -//│ deforest > fields: scrut¹⁴.x¹ +//│ deforest > match: x1⁰ +//│ deforest > fields: x1⁰.x² //│ deforest > <<< fusing <<< //│ = "ccf2f3cc" //│ cc = CC("cc") @@ -223,8 +223,11 @@ fun c(x, y) = if x is let y = A in c(AA(2), y) //│ deforest > >>> fusing >>> +//│ deforest > AA⁰(2) -> +//│ deforest > match: x¹¹ +//│ deforest > fields: x¹¹.x¹ //│ deforest > A⁰ -> -//│ deforest > match: scrut¹⁵ +//│ deforest > match: y¹ //│ deforest > <<< fusing <<< //│ = 4 @@ -239,6 +242,10 @@ fun c2(y) = if y is A then 3 let y = A in c(AA(2), y) + c2(y) //│ deforest > >>> fusing >>> +//│ deforest > AA⁰(2) -> +//│ deforest > match: x¹² +//│ deforest > fields: x¹².x¹ +//│ deforest > fields: x¹².x¹ //│ deforest > <<< fusing <<< //│ = 9 @@ -256,8 +263,8 @@ let p = AA(AA(AA("10"))) test(p) + f(p) + test(AA(A)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(A⁰) -> -//│ deforest > match: scrut¹⁶ -//│ deforest > fields: scrut¹⁶.x⁰ +//│ deforest > match: x¹³ +//│ deforest > fields: x¹³.x¹ //│ deforest > A⁰ -> //│ deforest > match: arg$AA$0$³ //│ deforest > <<< fusing <<< @@ -281,21 +288,30 @@ fun f(x, y) = let aa = AA(3) f(aa, 3) + f(aa, 4) //│ deforest > >>> fusing >>> +//│ deforest > BB⁰(tmp⁸) -> +//│ deforest > match: tmp⁹ +//│ deforest > fields: tmp⁹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut¹⁷ -//│ deforest > fields: scrut¹⁷.x⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x¹ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut¹⁸ +//│ deforest > match: scrut¹ +//│ deforest > BB⁰(tmp⁸) -> +//│ deforest > match: tmp⁹ +//│ deforest > fields: tmp⁹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut¹⁷ -//│ deforest > fields: scrut¹⁷.x⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x¹ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut¹⁸ +//│ deforest > match: scrut¹ +//│ deforest > BB⁰(tmp⁸) -> +//│ deforest > match: tmp⁹ +//│ deforest > fields: tmp⁹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut¹⁷ -//│ deforest > fields: scrut¹⁷.x⁰ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x¹ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut¹⁸ +//│ deforest > match: scrut¹ //│ deforest > <<< fusing <<< //│ = 13 //│ aa = AA(3) @@ -314,23 +330,23 @@ fun test(n, p, q) = test(3, AA(2), AA(3)) + test(3, AA(2), AA(3)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut¹⁹ +//│ deforest > match: scrut² //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut¹⁹ +//│ deforest > match: scrut² //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut²⁰ -//│ deforest > fields: scrut²⁰.x⁰ +//│ deforest > match: q⁰ +//│ deforest > fields: q⁰.x¹ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut²¹ -//│ deforest > fields: scrut²¹.x⁰ +//│ deforest > match: p⁰ +//│ deforest > fields: p⁰.x¹ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut¹⁹ +//│ deforest > match: scrut² //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut²⁰ -//│ deforest > fields: scrut²⁰.x⁰ +//│ deforest > match: q⁰ +//│ deforest > fields: q⁰.x¹ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut²¹ -//│ deforest > fields: scrut²¹.x⁰ +//│ deforest > match: p⁰ +//│ deforest > fields: p⁰.x¹ //│ deforest > <<< fusing <<< //│ = 16 @@ -354,17 +370,17 @@ fun c(a) = if a is A then 0 test(1, AA(2), AA(3), a) + test(2, AA(2), AA(3), a) + c(a) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut²² -//│ deforest > fields: scrut²².x⁰ +//│ deforest > match: q¹ +//│ deforest > fields: q¹.x¹ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut²³ -//│ deforest > fields: scrut²³.x⁰ +//│ deforest > match: p¹ +//│ deforest > fields: p¹.x¹ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut²² -//│ deforest > fields: scrut²².x⁰ +//│ deforest > match: q¹ +//│ deforest > fields: q¹.x¹ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut²³ -//│ deforest > fields: scrut²³.x⁰ +//│ deforest > match: p¹ +//│ deforest > fields: p¹.x¹ //│ deforest > <<< fusing <<< //│ = 13 //│ a = A @@ -384,23 +400,23 @@ fun test(x, y, z, i) = test(AA(1), AA(2), BB(3), 4) + test(AA(1), AA(2), BB(3), 4) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut²⁴ -//│ deforest > fields: scrut²⁴.x¹ +//│ deforest > match: z¹ +//│ deforest > fields: z¹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut²⁵ -//│ deforest > fields: scrut²⁵.x⁰ +//│ deforest > match: y² +//│ deforest > fields: y².x¹ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut²⁶ -//│ deforest > fields: scrut²⁶.x⁰ +//│ deforest > match: x¹⁴ +//│ deforest > fields: x¹⁴.x¹ //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut²⁴ -//│ deforest > fields: scrut²⁴.x¹ +//│ deforest > match: z¹ +//│ deforest > fields: z¹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut²⁵ -//│ deforest > fields: scrut²⁵.x⁰ +//│ deforest > match: y² +//│ deforest > fields: y².x¹ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut²⁶ -//│ deforest > fields: scrut²⁶.x⁰ +//│ deforest > match: x¹⁴ +//│ deforest > fields: x¹⁴.x¹ //│ deforest > <<< fusing <<< //│ = 18 @@ -419,23 +435,23 @@ fun test(x, y, z, i) = test(AA(1), AA(2), BB(3), 4) + test(AA(1), BB(2), BB(3), 4) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut²⁷ -//│ deforest > fields: scrut²⁷.x¹ +//│ deforest > match: z² +//│ deforest > fields: z².x² //│ deforest > BB⁰(2) -> -//│ deforest > match: scrut²⁸ -//│ deforest > fields: scrut²⁸.x¹ +//│ deforest > match: y³ +//│ deforest > fields: y³.x² //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut²⁹ -//│ deforest > fields: scrut²⁹.x⁰ +//│ deforest > match: x¹⁵ +//│ deforest > fields: x¹⁵.x¹ //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut²⁷ -//│ deforest > fields: scrut²⁷.x¹ +//│ deforest > match: z² +//│ deforest > fields: z².x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut²⁸ -//│ deforest > fields: scrut²⁸.x⁰ +//│ deforest > match: y³ +//│ deforest > fields: y³.x¹ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut²⁹ -//│ deforest > fields: scrut²⁹.x⁰ +//│ deforest > match: x¹⁵ +//│ deforest > fields: x¹⁵.x¹ //│ deforest > <<< fusing <<< //│ = 10 @@ -451,20 +467,20 @@ fun c(x) = if x is AA then 0 fun p(x) = AA(x) test(p(BB(3))) + test(p(CC(3))) + c(p(A)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x³) -> -//│ deforest > match: scrut³⁰ -//│ deforest > AA⁰(x³) -> -//│ deforest > match: scrut³¹ -//│ deforest > fields: scrut³¹.x⁰ +//│ deforest > AA⁰(x¹⁶) -> +//│ deforest > match: x¹⁷ +//│ deforest > AA⁰(x¹⁶) -> +//│ deforest > match: x¹⁸ +//│ deforest > fields: x¹⁸.x¹ //│ deforest > CC⁰(3) -> //│ deforest > match: arg$AA$0$⁴ -//│ deforest > fields: arg$AA$0$⁴.x⁴ -//│ deforest > AA⁰(x³) -> -//│ deforest > match: scrut³¹ -//│ deforest > fields: scrut³¹.x⁰ +//│ deforest > fields: arg$AA$0$⁴.x¹⁹ +//│ deforest > AA⁰(x¹⁶) -> +//│ deforest > match: x¹⁸ +//│ deforest > fields: x¹⁸.x¹ //│ deforest > BB⁰(3) -> //│ deforest > match: arg$AA$0$⁴ -//│ deforest > fields: arg$AA$0$⁴.x¹ +//│ deforest > fields: arg$AA$0$⁴.x² //│ deforest > <<< fusing <<< //│ = 12 @@ -486,25 +502,43 @@ fun f(x, y, z, k) = AA then m + k f(AA(3), 3, true, 10) + f(AA(5), 4, false, 20) //│ deforest > >>> fusing >>> +//│ deforest > BB⁰(tmp¹⁰) -> +//│ deforest > match: tmp¹¹ +//│ deforest > fields: tmp¹¹.x² +//│ deforest > BB⁰(tmp¹²) -> +//│ deforest > match: tmp¹¹ +//│ deforest > fields: tmp¹¹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut³² -//│ deforest > fields: scrut³².x⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.x¹ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut³³ +//│ deforest > match: scrut⁴ +//│ deforest > BB⁰(tmp¹⁰) -> +//│ deforest > match: tmp¹¹ +//│ deforest > fields: tmp¹¹.x² +//│ deforest > BB⁰(tmp¹²) -> +//│ deforest > match: tmp¹¹ +//│ deforest > fields: tmp¹¹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut³² -//│ deforest > fields: scrut³².x⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.x¹ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut³³ +//│ deforest > match: scrut⁴ //│ deforest > AA⁰(5) -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x²⁰ +//│ deforest > BB⁰(tmp¹⁰) -> +//│ deforest > match: tmp¹¹ +//│ deforest > fields: tmp¹¹.x² +//│ deforest > BB⁰(tmp¹²) -> +//│ deforest > match: tmp¹¹ +//│ deforest > fields: tmp¹¹.x² //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut³² -//│ deforest > fields: scrut³².x⁰ +//│ deforest > match: scrut³ +//│ deforest > fields: scrut³.x¹ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut³³ +//│ deforest > match: scrut⁴ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x²⁰ //│ deforest > <<< fusing <<< //│ = 41 @@ -540,13 +574,13 @@ fun test(a, b) = test(B, B) + test(A, A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut³⁵ +//│ deforest > match: a⁴ //│ deforest > A⁰ -> -//│ deforest > match: scrut³⁶ +//│ deforest > match: b⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁵ +//│ deforest > match: a⁴ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁶ +//│ deforest > match: b⁰ //│ deforest > <<< fusing <<< //│ > B //│ > 1 @@ -567,15 +601,15 @@ fun sum(ls) = if ls is sum(prodDouble(4)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³⁷ -//│ deforest > Cons⁰(n⁰, tmp⁸) -> -//│ deforest > match: scrut³⁷ -//│ deforest > fields: scrut³⁷.h⁰ -//│ deforest > fields: scrut³⁷.t⁰ -//│ deforest > Cons⁰(n⁰, tmp⁹) -> -//│ deforest > match: scrut³⁷ -//│ deforest > fields: scrut³⁷.h⁰ -//│ deforest > fields: scrut³⁷.t⁰ +//│ deforest > match: ls⁰ +//│ deforest > Cons⁰(n⁰, tmp¹³) -> +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ +//│ deforest > Cons⁰(n⁰, tmp¹⁴) -> +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > <<< fusing <<< //│ = 20 diff --git a/hkmc2/shared/src/test/mlscript/deforest/recursive.mls b/hkmc2/shared/src/test/mlscript/deforest/recursive.mls index deef93c69d..937903abf1 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/recursive.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/recursive.mls @@ -25,11 +25,11 @@ let c = Cons(1, Nil) in map(x => x + 1, c) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, Nil⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h⁰ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: ls_map⁰ +//│ deforest > fields: ls_map⁰.h⁰ +//│ deforest > fields: ls_map⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: ls_map⁰ //│ deforest > <<< fusing <<< //│ = Cons(2, Nil) @@ -41,11 +41,11 @@ fun c(a) = Cons(a, Nil) map(x => x + 1, c(1)) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(a⁰, Nil⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: ls_map¹ +//│ deforest > fields: ls_map¹.h⁰ +//│ deforest > fields: ls_map¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: ls_map¹ //│ deforest > <<< fusing <<< //│ = Cons(2, Nil) @@ -57,15 +57,15 @@ fun map(ls, f) = if ls is map(1 :: 2 :: Nil, x => x + 1) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, tmp⁰) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².h⁰ -//│ deforest > fields: scrut².t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².h⁰ -//│ deforest > fields: scrut².t⁰ +//│ deforest > match: ls⁰ +//│ deforest > fields: ls⁰.h⁰ +//│ deforest > fields: ls⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: ls⁰ //│ deforest > <<< fusing <<< //│ = Cons(2, Cons(3, Nil)) @@ -77,11 +77,11 @@ fun enum(n) = if n > 0 then n :: enum(n - 1) else Nil sum(enum(3)) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(n⁰, tmp¹) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.h⁰ -//│ deforest > fields: scrut³.t⁰ +//│ deforest > match: ls¹ +//│ deforest > fields: ls¹.h⁰ +//│ deforest > fields: ls¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: ls¹ //│ deforest > <<< fusing <<< //│ = 6 @@ -93,11 +93,11 @@ fun map(ls, f) = if ls is map(map(id(1 :: 2 :: Nil), x => x + 1), x => x * 2) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁴ +//│ deforest > match: ls² //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: ls² +//│ deforest > fields: ls².h⁰ +//│ deforest > fields: ls².t⁰ //│ deforest > <<< fusing <<< //│ = Cons(4, Cons(6, Nil)) @@ -109,11 +109,11 @@ fun enum(a) = if a < 0 then Nil else a :: enum(a - 1) map(x => x + 1, map(x => x * 2, id(enum(3)))) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁵ +//│ deforest > match: ls³ //│ deforest > Cons⁰(tmp⁴, tmp⁵) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.h⁰ -//│ deforest > fields: scrut⁵.t⁰ +//│ deforest > match: ls³ +//│ deforest > fields: ls³.h⁰ +//│ deforest > fields: ls³.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(7, Cons(5, Cons(3, Cons(1, Nil)))) @@ -132,29 +132,29 @@ fun cons1(a1) = if a1 is cons(f(0)) + cons1(f(1)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: a1⁰ //│ deforest > Cons⁰(x⁰, tmp⁶) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.h⁰ -//│ deforest > fields: scrut⁶.t⁰ +//│ deforest > match: a1⁰ +//│ deforest > fields: a1⁰.h⁰ +//│ deforest > fields: a1⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: a1⁰ //│ deforest > Cons⁰(y⁰, tmp⁷) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.h⁰ -//│ deforest > fields: scrut⁶.t⁰ +//│ deforest > match: a1⁰ +//│ deforest > fields: a1⁰.h⁰ +//│ deforest > fields: a1⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁷ +//│ deforest > match: a¹ //│ deforest > Cons⁰(x⁰, tmp⁶) -> -//│ deforest > match: scrut⁷ -//│ deforest > fields: scrut⁷.h⁰ -//│ deforest > fields: scrut⁷.t⁰ +//│ deforest > match: a¹ +//│ deforest > fields: a¹.h⁰ +//│ deforest > fields: a¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁷ +//│ deforest > match: a¹ //│ deforest > Cons⁰(y⁰, tmp⁷) -> -//│ deforest > match: scrut⁷ -//│ deforest > fields: scrut⁷.h⁰ -//│ deforest > fields: scrut⁷.t⁰ +//│ deforest > match: a¹ +//│ deforest > fields: a¹.h⁰ +//│ deforest > fields: a¹.t⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -173,11 +173,11 @@ fun f1(ls1) = if ls1 is f1(to(4)) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(n¹, tmp⁸) -> -//│ deforest > match: scrut⁸ -//│ deforest > fields: scrut⁸.h⁰ -//│ deforest > fields: scrut⁸.t⁰ +//│ deforest > match: ls1⁰ +//│ deforest > fields: ls1⁰.h⁰ +//│ deforest > fields: ls1⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁸ +//│ deforest > match: ls1⁰ //│ deforest > <<< fusing <<< //│ = 12 @@ -190,21 +190,21 @@ fun double(x) = x * 2 map(map(1 :: 2 :: Nil, succ), double) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁹ +//│ deforest > match: ls⁴ //│ deforest > Cons⁰(tmp⁹, tmp¹⁰) -> -//│ deforest > match: scrut⁹ -//│ deforest > fields: scrut⁹.h⁰ -//│ deforest > fields: scrut⁹.t⁰ +//│ deforest > match: ls⁴ +//│ deforest > fields: ls⁴.h⁰ +//│ deforest > fields: ls⁴.t⁰ //│ deforest > Cons⁰(1, tmp¹¹) -> -//│ deforest > match: scrut⁹ -//│ deforest > fields: scrut⁹.h⁰ -//│ deforest > fields: scrut⁹.t⁰ +//│ deforest > match: ls⁴ +//│ deforest > fields: ls⁴.h⁰ +//│ deforest > fields: ls⁴.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: scrut⁹ -//│ deforest > fields: scrut⁹.h⁰ -//│ deforest > fields: scrut⁹.t⁰ +//│ deforest > match: ls⁴ +//│ deforest > fields: ls⁴.h⁰ +//│ deforest > fields: ls⁴.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁹ +//│ deforest > match: ls⁴ //│ deforest > <<< fusing <<< //│ = Cons(4, Cons(6, Nil)) @@ -220,17 +220,17 @@ fun wrap(n) = to(n) f1(wrap(4)) + f2(wrap(5)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁰ +//│ deforest > match: ls⁵ //│ deforest > Cons⁰(n², tmp¹²) -> -//│ deforest > match: scrut¹⁰ -//│ deforest > fields: scrut¹⁰.h⁰ -//│ deforest > fields: scrut¹⁰.t⁰ +//│ deforest > match: ls⁵ +//│ deforest > fields: ls⁵.h⁰ +//│ deforest > fields: ls⁵.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹¹ +//│ deforest > match: ls⁶ //│ deforest > Cons⁰(n², tmp¹²) -> -//│ deforest > match: scrut¹¹ -//│ deforest > fields: scrut¹¹.h⁰ -//│ deforest > fields: scrut¹¹.t⁰ +//│ deforest > match: ls⁶ +//│ deforest > fields: ls⁶.h⁰ +//│ deforest > fields: ls⁶.t⁰ //│ deforest > <<< fusing <<< //│ = 10 @@ -245,23 +245,23 @@ fun f2(ls, acc) = if ls is f1(to(4, Nil), 0) + f2(to(5, Nil), 0) + f1(to(6, Nil), 0) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹² +//│ deforest > match: ls⁷ //│ deforest > Cons⁰(n³, acc⁰) -> -//│ deforest > match: scrut¹² -//│ deforest > fields: scrut¹².h⁰ -//│ deforest > fields: scrut¹².t⁰ +//│ deforest > match: ls⁷ +//│ deforest > fields: ls⁷.h⁰ +//│ deforest > fields: ls⁷.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹³ +//│ deforest > match: ls⁸ //│ deforest > Cons⁰(n³, acc⁰) -> -//│ deforest > match: scrut¹³ -//│ deforest > fields: scrut¹³.h⁰ -//│ deforest > fields: scrut¹³.t⁰ +//│ deforest > match: ls⁸ +//│ deforest > fields: ls⁸.h⁰ +//│ deforest > fields: ls⁸.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹² +//│ deforest > match: ls⁷ //│ deforest > Cons⁰(n³, acc⁰) -> -//│ deforest > match: scrut¹² -//│ deforest > fields: scrut¹².h⁰ -//│ deforest > fields: scrut¹².t⁰ +//│ deforest > match: ls⁷ +//│ deforest > fields: ls⁷.h⁰ +//│ deforest > fields: ls⁷.t⁰ //│ deforest > <<< fusing <<< //│ = 51 @@ -275,27 +275,27 @@ fun triple(x) = x * 3 map(map(map(1 :: 2 :: Nil, succ), double), triple) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁴ +//│ deforest > match: ls⁹ //│ deforest > Cons⁰(tmp¹³, tmp¹⁴) -> -//│ deforest > match: scrut¹⁴ -//│ deforest > fields: scrut¹⁴.h⁰ -//│ deforest > fields: scrut¹⁴.t⁰ +//│ deforest > match: ls⁹ +//│ deforest > fields: ls⁹.h⁰ +//│ deforest > fields: ls⁹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁴ +//│ deforest > match: ls⁹ //│ deforest > Cons⁰(tmp¹³, tmp¹⁴) -> -//│ deforest > match: scrut¹⁴ -//│ deforest > fields: scrut¹⁴.h⁰ -//│ deforest > fields: scrut¹⁴.t⁰ +//│ deforest > match: ls⁹ +//│ deforest > fields: ls⁹.h⁰ +//│ deforest > fields: ls⁹.t⁰ //│ deforest > Cons⁰(1, tmp¹⁵) -> -//│ deforest > match: scrut¹⁴ -//│ deforest > fields: scrut¹⁴.h⁰ -//│ deforest > fields: scrut¹⁴.t⁰ +//│ deforest > match: ls⁹ +//│ deforest > fields: ls⁹.h⁰ +//│ deforest > fields: ls⁹.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: scrut¹⁴ -//│ deforest > fields: scrut¹⁴.h⁰ -//│ deforest > fields: scrut¹⁴.t⁰ +//│ deforest > match: ls⁹ +//│ deforest > fields: ls⁹.h⁰ +//│ deforest > fields: ls⁹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁴ +//│ deforest > match: ls⁹ //│ deforest > <<< fusing <<< //│ = Cons(12, Cons(18, Nil)) @@ -315,11 +315,11 @@ fun double(x) = x * 2 map1(incr, map(double, id(mk(5)))) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(tmp¹⁶, tmp¹⁷) -> -//│ deforest > match: scrut¹⁵ -//│ deforest > fields: scrut¹⁵.h⁰ -//│ deforest > fields: scrut¹⁵.t⁰ +//│ deforest > match: ls_map1⁰ +//│ deforest > fields: ls_map1⁰.h⁰ +//│ deforest > fields: ls_map1⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁵ +//│ deforest > match: ls_map1⁰ //│ deforest > <<< fusing <<< //│ = Cons(11, Cons(9, Cons(7, Cons(5, Cons(3, Cons(1, Nil)))))) @@ -332,12 +332,12 @@ fun map(ls) = if ls is h :: t then (h + 4) :: map(t) map(enumFromTo(1, 4)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a¹, tmp¹⁸) -> -//│ deforest > match: scrut¹⁶ -//│ deforest > fields: scrut¹⁶.h⁰ -//│ deforest > fields: scrut¹⁶.t⁰ +//│ deforest > Cons⁰(a², tmp¹⁸) -> +//│ deforest > match: ls¹⁰ +//│ deforest > fields: ls¹⁰.h⁰ +//│ deforest > fields: ls¹⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁶ +//│ deforest > match: ls¹⁰ //│ deforest > <<< fusing <<< //│ = Cons(5, Cons(6, Cons(7, Nil))) @@ -348,12 +348,12 @@ fun sum(ls) = if ls is h :: t then h + sum(t) sum(enumFromTo(1,10)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a², tmp¹⁹) -> -//│ deforest > match: scrut¹⁷ -//│ deforest > fields: scrut¹⁷.h⁰ -//│ deforest > fields: scrut¹⁷.t⁰ +//│ deforest > Cons⁰(a³, tmp¹⁹) -> +//│ deforest > match: ls¹¹ +//│ deforest > fields: ls¹¹.h⁰ +//│ deforest > fields: ls¹¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁷ +//│ deforest > match: ls¹¹ //│ deforest > <<< fusing <<< //│ = 45 @@ -365,12 +365,12 @@ fun map(f, ls) = h :: t then f(h) :: map(f, t) map(x => x + 4, enumFromTo(1, 4)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a³, tmp²⁰) -> -//│ deforest > match: scrut¹⁸ -//│ deforest > fields: scrut¹⁸.h⁰ -//│ deforest > fields: scrut¹⁸.t⁰ +//│ deforest > Cons⁰(a⁴, tmp²⁰) -> +//│ deforest > match: ls¹² +//│ deforest > fields: ls¹².h⁰ +//│ deforest > fields: ls¹².t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁸ +//│ deforest > match: ls¹² //│ deforest > <<< fusing <<< //│ = Cons(5, Cons(6, Cons(7, Nil))) @@ -383,12 +383,12 @@ fun map(f, ls) = )(f) map(x => x + 4, enumFromTo(1, 4)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a⁴, tmp²¹) -> -//│ deforest > match: scrut¹⁹ -//│ deforest > fields: scrut¹⁹.h⁰ -//│ deforest > fields: scrut¹⁹.t⁰ +//│ deforest > Cons⁰(a⁵, tmp²¹) -> +//│ deforest > match: ls¹³ +//│ deforest > fields: ls¹³.h⁰ +//│ deforest > fields: ls¹³.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹⁹ +//│ deforest > match: ls¹³ //│ deforest > <<< fusing <<< //│ = Cons(5, Cons(6, Cons(7, Nil))) @@ -401,12 +401,12 @@ fun sum(ls, a) = if ls is h :: t then sum(t, h + a) sum(enumFromTo(1, 10), 0) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(a⁵, tmp²²) -> -//│ deforest > match: scrut²⁰ -//│ deforest > fields: scrut²⁰.h⁰ -//│ deforest > fields: scrut²⁰.t⁰ +//│ deforest > Cons⁰(a⁶, tmp²²) -> +//│ deforest > match: ls¹⁴ +//│ deforest > fields: ls¹⁴.h⁰ +//│ deforest > fields: ls¹⁴.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut²⁰ +//│ deforest > match: ls¹⁴ //│ deforest > <<< fusing <<< //│ = 45 @@ -424,11 +424,11 @@ fun rev(xs_rev, acc) = if xs_rev is map(incr, rev(id(mk(5)), Nil)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut²¹ +//│ deforest > match: xs_map⁰ //│ deforest > Cons⁰(x¹, acc¹) -> -//│ deforest > match: scrut²¹ -//│ deforest > fields: scrut²¹.h⁰ -//│ deforest > fields: scrut²¹.t⁰ +//│ deforest > match: xs_map⁰ +//│ deforest > fields: xs_map⁰.h⁰ +//│ deforest > fields: xs_map⁰.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil)))))) @@ -446,11 +446,11 @@ fun rev(xs_rev, acc) = if xs_rev is rev(map(incr, id(mk(3))), Nil) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut²² +//│ deforest > match: xs_rev⁰ //│ deforest > Cons⁰(tmp²³, tmp²⁴) -> -//│ deforest > match: scrut²² -//│ deforest > fields: scrut²².h⁰ -//│ deforest > fields: scrut²².t⁰ +//│ deforest > match: xs_rev⁰ +//│ deforest > fields: xs_rev⁰.h⁰ +//│ deforest > fields: xs_rev⁰.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(2, Cons(3, Cons(4, Nil)))) @@ -500,11 +500,11 @@ fun flatten(ls_flatten) = if ls_flatten is sum(flatten(id(mk2d(4)))) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut²³ +//│ deforest > match: ls_sum⁰ //│ deforest > Cons⁰(x², tmp²⁵) -> -//│ deforest > match: scrut²³ -//│ deforest > fields: scrut²³.h⁰ -//│ deforest > fields: scrut²³.t⁰ +//│ deforest > match: ls_sum⁰ +//│ deforest > fields: ls_sum⁰.h⁰ +//│ deforest > fields: ls_sum⁰.t⁰ //│ deforest > <<< fusing <<< //│ = 20 @@ -519,21 +519,21 @@ fun rev(a, ys) = if ys is count(0, rev(Nil, 1 :: 2 :: Nil)) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut²⁴ -//│ deforest > Cons⁰(h¹, a⁶) -> -//│ deforest > match: scrut²⁴ -//│ deforest > fields: scrut²⁴.h⁰ -//│ deforest > fields: scrut²⁴.t⁰ +//│ deforest > match: xs⁰ +//│ deforest > Cons⁰(h¹, a⁷) -> +//│ deforest > match: xs⁰ +//│ deforest > fields: xs⁰.h⁰ +//│ deforest > fields: xs⁰.t⁰ //│ deforest > Cons⁰(1, tmp²⁶) -> -//│ deforest > match: scrut²⁵ -//│ deforest > fields: scrut²⁵.h⁰ -//│ deforest > fields: scrut²⁵.t⁰ +//│ deforest > match: ys⁰ +//│ deforest > fields: ys⁰.h⁰ +//│ deforest > fields: ys⁰.t⁰ //│ deforest > Cons⁰(2, Nil⁰) -> -//│ deforest > match: scrut²⁵ -//│ deforest > fields: scrut²⁵.h⁰ -//│ deforest > fields: scrut²⁵.t⁰ +//│ deforest > match: ys⁰ +//│ deforest > fields: ys⁰.h⁰ +//│ deforest > fields: ys⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut²⁵ +//│ deforest > match: ys⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -560,19 +560,19 @@ fun map(f, xs_map) = if xs_map is map(x => if x is A then 1 else 0, A :: B :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(A⁰, tmp²⁷) -> -//│ deforest > match: scrut²⁶ -//│ deforest > fields: scrut²⁶.h⁰ -//│ deforest > fields: scrut²⁶.t⁰ +//│ deforest > match: xs_map¹ +//│ deforest > fields: xs_map¹.h⁰ +//│ deforest > fields: xs_map¹.t⁰ //│ deforest > A⁰ -> -//│ deforest > match: scrut²⁷ +//│ deforest > match: x³ //│ deforest > Cons⁰(B⁰, Nil⁰) -> -//│ deforest > match: scrut²⁶ -//│ deforest > fields: scrut²⁶.h⁰ -//│ deforest > fields: scrut²⁶.t⁰ +//│ deforest > match: xs_map¹ +//│ deforest > fields: xs_map¹.h⁰ +//│ deforest > fields: xs_map¹.t⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut²⁷ +//│ deforest > match: x³ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut²⁶ +//│ deforest > match: xs_map¹ //│ deforest > <<< fusing <<< //│ = Cons(1, Cons(0, Nil)) @@ -589,34 +589,34 @@ fun c(x) = if x is c(T(A, T(B, L, L), T(A, L, L))) //│ deforest > >>> fusing >>> //│ deforest > T⁰(A⁰, tmp²⁸, tmp²⁹) -> -//│ deforest > match: scrut²⁸ -//│ deforest > fields: scrut²⁸.n⁴ -//│ deforest > fields: scrut²⁸.l⁰ -//│ deforest > fields: scrut²⁸.r⁰ +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.n⁴ +//│ deforest > fields: x⁴.l⁰ +//│ deforest > fields: x⁴.r⁰ //│ deforest > A⁰ -> -//│ deforest > match: scrut²⁹ +//│ deforest > match: n⁵ //│ deforest > T⁰(A⁰, L⁰, L⁰) -> -//│ deforest > match: scrut²⁸ -//│ deforest > fields: scrut²⁸.n⁴ -//│ deforest > fields: scrut²⁸.l⁰ -//│ deforest > fields: scrut²⁸.r⁰ +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.n⁴ +//│ deforest > fields: x⁴.l⁰ +//│ deforest > fields: x⁴.r⁰ //│ deforest > A⁰ -> -//│ deforest > match: scrut²⁹ +//│ deforest > match: n⁵ //│ deforest > L⁰ -> -//│ deforest > match: scrut²⁸ +//│ deforest > match: x⁴ //│ deforest > L⁰ -> -//│ deforest > match: scrut²⁸ +//│ deforest > match: x⁴ //│ deforest > T⁰(B⁰, L⁰, L⁰) -> -//│ deforest > match: scrut²⁸ -//│ deforest > fields: scrut²⁸.n⁴ -//│ deforest > fields: scrut²⁸.l⁰ -//│ deforest > fields: scrut²⁸.r⁰ +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.n⁴ +//│ deforest > fields: x⁴.l⁰ +//│ deforest > fields: x⁴.r⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut²⁹ +//│ deforest > match: n⁵ //│ deforest > L⁰ -> -//│ deforest > match: scrut²⁸ +//│ deforest > match: x⁴ //│ deforest > L⁰ -> -//│ deforest > match: scrut²⁸ +//│ deforest > match: x⁴ //│ deforest > <<< fusing <<< //│ = T(0, T(1, L, L), T(0, L, L)) @@ -630,27 +630,27 @@ fun max(ms, m) = if ms is max(3 :: 2 :: 4 :: 1 :: 0 :: Nil, 0) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(3, tmp³⁰) -> -//│ deforest > match: scrut³⁰ -//│ deforest > fields: scrut³⁰.h⁰ -//│ deforest > fields: scrut³⁰.t⁰ +//│ deforest > match: ms⁰ +//│ deforest > fields: ms⁰.h⁰ +//│ deforest > fields: ms⁰.t⁰ //│ deforest > Cons⁰(2, tmp³¹) -> -//│ deforest > match: scrut³⁰ -//│ deforest > fields: scrut³⁰.h⁰ -//│ deforest > fields: scrut³⁰.t⁰ +//│ deforest > match: ms⁰ +//│ deforest > fields: ms⁰.h⁰ +//│ deforest > fields: ms⁰.t⁰ //│ deforest > Cons⁰(4, tmp³²) -> -//│ deforest > match: scrut³⁰ -//│ deforest > fields: scrut³⁰.h⁰ -//│ deforest > fields: scrut³⁰.t⁰ +//│ deforest > match: ms⁰ +//│ deforest > fields: ms⁰.h⁰ +//│ deforest > fields: ms⁰.t⁰ //│ deforest > Cons⁰(1, tmp³³) -> -//│ deforest > match: scrut³⁰ -//│ deforest > fields: scrut³⁰.h⁰ -//│ deforest > fields: scrut³⁰.t⁰ +//│ deforest > match: ms⁰ +//│ deforest > fields: ms⁰.h⁰ +//│ deforest > fields: ms⁰.t⁰ //│ deforest > Cons⁰(0, Nil⁰) -> -//│ deforest > match: scrut³⁰ -//│ deforest > fields: scrut³⁰.h⁰ -//│ deforest > fields: scrut³⁰.t⁰ +//│ deforest > match: ms⁰ +//│ deforest > fields: ms⁰.h⁰ +//│ deforest > fields: ms⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³⁰ +//│ deforest > match: ms⁰ //│ deforest > <<< fusing <<< //│ = 4 @@ -743,17 +743,17 @@ fun g2() = f() f //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: scrut³¹ +//│ deforest > match: scrut⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut³¹ +//│ deforest > match: scrut⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut³¹ +//│ deforest > match: scrut⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut³¹ +//│ deforest > match: scrut⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut³¹ +//│ deforest > match: scrut⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut³¹ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = fun f_10$f @@ -800,65 +800,65 @@ fun f() = f //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: x⁵ //│ deforest > <<< fusing <<< //│ = fun f_18$f @@ -874,21 +874,21 @@ fun f() = f //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: scrut³³ +//│ deforest > match: x⁶ //│ deforest > B⁰ -> -//│ deforest > match: scrut³³ +//│ deforest > match: x⁶ //│ deforest > B⁰ -> -//│ deforest > match: scrut³³ +//│ deforest > match: x⁶ //│ deforest > B⁰ -> -//│ deforest > match: scrut³³ +//│ deforest > match: x⁶ //│ deforest > B⁰ -> -//│ deforest > match: scrut³³ +//│ deforest > match: x⁶ //│ deforest > B⁰ -> -//│ deforest > match: scrut³³ +//│ deforest > match: x⁶ //│ deforest > B⁰ -> -//│ deforest > match: scrut³³ +//│ deforest > match: x⁶ //│ deforest > B⁰ -> -//│ deforest > match: scrut³³ +//│ deforest > match: x⁶ //│ deforest > <<< fusing <<< //│ = fun f_10$f @@ -905,24 +905,24 @@ fun f() = f //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x⁷ //│ deforest > <<< fusing <<< //│ = fun f_14$f diff --git a/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls b/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls index e575cb31ac..a64c6ea021 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls @@ -34,6 +34,9 @@ c(AA(2)) //│ deforest > match: scrut⁰ //│ deforest > A⁰ -> //│ deforest > match: scrut⁰ +//│ deforest > AA⁰(2) -> +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.x¹ //│ deforest > <<< fusing <<< //│ = 2 @@ -46,7 +49,10 @@ fun c(x, y) = if x is c(AA(2), A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: y⁰ +//│ deforest > AA⁰(2) -> +//│ deforest > match: x² +//│ deforest > fields: x².x¹ //│ deforest > <<< fusing <<< //│ = 2 @@ -61,7 +67,10 @@ fun p() = AA(2) c(p(), A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: y¹ +//│ deforest > AA⁰(2) -> +//│ deforest > match: x³ +//│ deforest > fields: x³.x¹ //│ deforest > <<< fusing <<< //│ = 2 @@ -78,7 +87,10 @@ fun p() = AA(2) c(p(), A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: y² +//│ deforest > AA⁰(2) -> +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.x¹ //│ deforest > <<< fusing <<< //│ = 3 @@ -94,22 +106,22 @@ fun g(x) = if x is f(AA(AA(AA(AA(AA(A)))))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.x⁰ +//│ deforest > match: x⁵ +//│ deforest > fields: x⁵.x¹ //│ deforest > AA⁰(tmp¹) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x⁰ +//│ deforest > fields: arg$AA$0$⁰.x¹ //│ deforest > AA⁰(tmp²) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.x⁰ +//│ deforest > match: x⁶ +//│ deforest > fields: x⁶.x¹ //│ deforest > AA⁰(tmp³) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.x⁰ +//│ deforest > match: x⁵ +//│ deforest > fields: x⁵.x¹ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x⁰ +//│ deforest > fields: arg$AA$0$⁰.x¹ //│ deforest > A⁰ -> -//│ deforest > match: scrut⁵ +//│ deforest > match: x⁶ //│ deforest > <<< fusing <<< //│ = 42 @@ -119,11 +131,11 @@ fun f(x) = if x is f(AA(AA(A))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁴) -> -//│ deforest > match: scrut⁶ -//│ deforest > fields: scrut⁶.x⁰ +//│ deforest > match: x⁷ +//│ deforest > fields: x⁷.x¹ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$¹ -//│ deforest > fields: arg$AA$0$¹.x⁰ +//│ deforest > fields: arg$AA$0$¹.x¹ //│ deforest > <<< fusing <<< //│ = A @@ -134,11 +146,11 @@ fun p() = AA(AA(A)) f(p()) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁵) -> -//│ deforest > match: scrut⁷ -//│ deforest > fields: scrut⁷.x⁰ +//│ deforest > match: x⁸ +//│ deforest > fields: x⁸.x¹ //│ deforest > AA⁰(A⁰) -> //│ deforest > match: arg$AA$0$² -//│ deforest > fields: arg$AA$0$².x⁰ +//│ deforest > fields: arg$AA$0$².x¹ //│ deforest > <<< fusing <<< //│ = A @@ -154,8 +166,11 @@ fun c(x, y) = c(AA(2), 10) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut⁸ +//│ deforest > match: scrut¹ //│ deforest > A⁰ -> -//│ deforest > match: scrut⁸ +//│ deforest > match: scrut¹ +//│ deforest > AA⁰(2) -> +//│ deforest > match: x⁹ +//│ deforest > fields: x⁹.x¹ //│ deforest > <<< fusing <<< //│ = 12 diff --git a/hkmc2/shared/src/test/mlscript/deforest/simple.mls b/hkmc2/shared/src/test/mlscript/deforest/simple.mls index c6411df5a6..07c125ee5b 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/simple.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/simple.mls @@ -33,8 +33,8 @@ fun test(x) = test(AA(AA(10))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.aa⁰ +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.aa⁰ //│ deforest > AA⁰(10) -> //│ deforest > match: arg$AA$0$⁰ //│ deforest > fields: arg$AA$0$⁰.aa⁰ @@ -50,9 +50,9 @@ if x is B then 2 //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: x¹ //│ deforest > A⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: x¹ //│ deforest > <<< fusing <<< //│ = 1 @@ -63,11 +63,11 @@ if x is BB(x) then x //│ deforest > >>> fusing >>> //│ deforest > BB⁰(B⁰) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².bb⁰ +//│ deforest > match: x² +//│ deforest > fields: x².bb⁰ //│ deforest > AA⁰(A⁰) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².aa⁰ +//│ deforest > match: x² +//│ deforest > fields: x².aa⁰ //│ deforest > <<< fusing <<< //│ = A @@ -82,15 +82,15 @@ if x is BB(x) then f(x) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(B⁰) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.bb⁰ +//│ deforest > match: x³ +//│ deforest > fields: x³.bb⁰ //│ deforest > B⁰ -> -//│ deforest > match: scrut⁴ +//│ deforest > match: a⁰ //│ deforest > AA⁰(A⁰) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.aa⁰ +//│ deforest > match: x³ +//│ deforest > fields: x³.aa⁰ //│ deforest > A⁰ -> -//│ deforest > match: scrut⁴ +//│ deforest > match: a⁰ //│ deforest > <<< fusing <<< //│ = 1 @@ -110,6 +110,16 @@ if x is AA then f1(x.AA#aa) BB then f2(x.BB#bb) //│ deforest > >>> fusing >>> +//│ deforest > BB⁰(B⁰) -> +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.bb⁰ +//│ deforest > B⁰ -> +//│ deforest > match: a¹ +//│ deforest > AA⁰(A⁰) -> +//│ deforest > match: x⁴ +//│ deforest > fields: x⁴.aa⁰ +//│ deforest > A⁰ -> +//│ deforest > match: a² //│ deforest > <<< fusing <<< //│ = 1 @@ -122,11 +132,11 @@ fun c(x) = if x is c(g(true)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(11) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.aa⁰ +//│ deforest > match: x⁵ +//│ deforest > fields: x⁵.aa⁰ //│ deforest > BB⁰(22) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.bb⁰ +//│ deforest > match: x⁵ +//│ deforest > fields: x⁵.bb⁰ //│ deforest > <<< fusing <<< //│ = 11 @@ -157,9 +167,9 @@ if y is B then 2 //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: scrut⁶ +//│ deforest > match: y⁰ //│ deforest > A⁰ -> -//│ deforest > match: scrut⁷ +//│ deforest > match: x⁶ //│ deforest > <<< fusing <<< //│ = 2 @@ -174,9 +184,9 @@ fun c(a) = c(A) + c(B) //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: scrut⁸ +//│ deforest > match: a³ //│ deforest > A⁰ -> -//│ deforest > match: scrut⁸ +//│ deforest > match: a³ //│ deforest > <<< fusing <<< //│ > 1 //│ > 2 @@ -205,9 +215,9 @@ fun f(a, b) = if a is f(A, B) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut⁹ +//│ deforest > match: a⁴ //│ deforest > B⁰ -> -//│ deforest > match: scrut¹⁰ +//│ deforest > match: b⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -217,6 +227,14 @@ fun f(x) = if x is Some then if x.Some#value > 1 then f(Some(x.Some#value - 1)) else 0 f(Some(2)) //│ deforest > >>> fusing >>> +//│ deforest > Some⁰(tmp¹) -> +//│ deforest > match: x⁷ +//│ deforest > fields: x⁷.value⁰ +//│ deforest > fields: x⁷.value⁰ +//│ deforest > Some⁰(2) -> +//│ deforest > match: x⁷ +//│ deforest > fields: x⁷.value⁰ +//│ deforest > fields: x⁷.value⁰ //│ deforest > <<< fusing <<< //│ = 0 @@ -231,9 +249,9 @@ if y is B then 2 //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: scrut¹¹ +//│ deforest > match: y¹ //│ deforest > A⁰ -> -//│ deforest > match: scrut¹² +//│ deforest > match: x⁸ //│ deforest > <<< fusing <<< //│ = 2 @@ -248,21 +266,21 @@ fun f(x, y) = f(AAA(1, 3), 1) + f(BBB(2, 3), 2) + f(AAA(3, 2), 4) + f(BBB(4, 6), 0) //│ deforest > >>> fusing >>> //│ deforest > BBB⁰(4, 6) -> -//│ deforest > match: scrut¹³ -//│ deforest > fields: scrut¹³.x⁰ -//│ deforest > fields: scrut¹³.y⁰ +//│ deforest > match: x⁹ +//│ deforest > fields: x⁹.x¹⁰ +//│ deforest > fields: x⁹.y² //│ deforest > AAA⁰(3, 2) -> -//│ deforest > match: scrut¹³ -//│ deforest > fields: scrut¹³.x¹ -//│ deforest > fields: scrut¹³.y¹ +//│ deforest > match: x⁹ +//│ deforest > fields: x⁹.x¹¹ +//│ deforest > fields: x⁹.y³ //│ deforest > BBB⁰(2, 3) -> -//│ deforest > match: scrut¹³ -//│ deforest > fields: scrut¹³.x⁰ -//│ deforest > fields: scrut¹³.y⁰ +//│ deforest > match: x⁹ +//│ deforest > fields: x⁹.x¹⁰ +//│ deforest > fields: x⁹.y² //│ deforest > AAA⁰(1, 3) -> -//│ deforest > match: scrut¹³ -//│ deforest > fields: scrut¹³.x¹ -//│ deforest > fields: scrut¹³.y¹ +//│ deforest > match: x⁹ +//│ deforest > fields: x⁹.x¹¹ +//│ deforest > fields: x⁹.y³ //│ deforest > <<< fusing <<< //│ = 21 @@ -278,11 +296,11 @@ fun p(a) = c1(a) + c2(a) p(AA(1)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut¹⁴ +//│ deforest > match: scrut⁰ //│ deforest > A⁰ -> -//│ deforest > match: scrut¹⁴ +//│ deforest > match: scrut⁰ //│ deforest > A⁰ -> -//│ deforest > match: scrut¹⁴ +//│ deforest > match: scrut⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -296,10 +314,10 @@ fun c(x, y) = if x is c(AA(2), A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut¹⁵ +//│ deforest > match: y⁴ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut¹⁶ -//│ deforest > fields: scrut¹⁶.aa⁰ +//│ deforest > match: x¹² +//│ deforest > fields: x¹².aa⁰ //│ deforest > <<< fusing <<< //│ = 2 @@ -313,19 +331,19 @@ fun f(x, y) = f(AAA(1, 3), 1) + f(CCC(2), 2) + f(AAA(3, 2), 4) + f(CCC(4), 0) //│ deforest > >>> fusing >>> //│ deforest > CCC⁰(4) -> -//│ deforest > match: scrut¹⁷ -//│ deforest > fields: scrut¹⁷.c⁰ +//│ deforest > match: x¹³ +//│ deforest > fields: x¹³.c⁰ //│ deforest > AAA⁰(3, 2) -> -//│ deforest > match: scrut¹⁷ -//│ deforest > fields: scrut¹⁷.x¹ -//│ deforest > fields: scrut¹⁷.y¹ +//│ deforest > match: x¹³ +//│ deforest > fields: x¹³.x¹¹ +//│ deforest > fields: x¹³.y³ //│ deforest > CCC⁰(2) -> -//│ deforest > match: scrut¹⁷ -//│ deforest > fields: scrut¹⁷.c⁰ +//│ deforest > match: x¹³ +//│ deforest > fields: x¹³.c⁰ //│ deforest > AAA⁰(1, 3) -> -//│ deforest > match: scrut¹⁷ -//│ deforest > fields: scrut¹⁷.x¹ -//│ deforest > fields: scrut¹⁷.y¹ +//│ deforest > match: x¹³ +//│ deforest > fields: x¹³.x¹¹ +//│ deforest > fields: x¹³.y³ //│ deforest > <<< fusing <<< //│ = 24 @@ -340,6 +358,15 @@ fun f(x, y) = a + 3 f(AAA(1, 3), 1) + f(CCC(2), 2) + f(AAA(3, 2), 4) //│ deforest > >>> fusing >>> +//│ deforest > AAA⁰(3, 2) -> +//│ deforest > match: x¹⁴ +//│ deforest > fields: x¹⁴.y³ +//│ deforest > CCC⁰(2) -> +//│ deforest > match: x¹⁴ +//│ deforest > fields: x¹⁴.c⁰ +//│ deforest > AAA⁰(1, 3) -> +//│ deforest > match: x¹⁴ +//│ deforest > fields: x¹⁴.y³ //│ deforest > <<< fusing <<< //│ = 22 @@ -351,7 +378,7 @@ fun c(x, m) = if x is c(BB(3), 0) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut¹⁸ +//│ deforest > match: x¹⁵ //│ deforest > <<< fusing <<< //│ = 0 @@ -365,12 +392,12 @@ fun g(b) = if b is AA(a) then a + 1 f(AA(AA(0))) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(tmp¹) -> -//│ deforest > match: scrut¹⁹ -//│ deforest > fields: scrut¹⁹.aa⁰ +//│ deforest > AA⁰(tmp²) -> +//│ deforest > match: x¹⁶ +//│ deforest > fields: x¹⁶.aa⁰ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut²⁰ -//│ deforest > fields: scrut²⁰.aa⁰ +//│ deforest > match: b¹ +//│ deforest > fields: b¹.aa⁰ //│ deforest > <<< fusing <<< //│ = 1 @@ -380,7 +407,7 @@ f(AA(AA(0))) fun f(x) = x.AA#aa.AA#aa f(AA(AA(3))) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(tmp²) -> +//│ deforest > AA⁰(tmp³) -> //│ deforest > select: aa //│ deforest > AA⁰(3) -> //│ deforest > select: aa @@ -407,8 +434,8 @@ let p = AA(AA(3)) outer1(p, 1, 2) + outer2(p, 3, 4) + inner(AA(5), 6) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(5) -> -//│ deforest > match: scrut²¹ -//│ deforest > fields: scrut²¹.aa⁰ +//│ deforest > match: x¹⁷ +//│ deforest > fields: x¹⁷.aa⁰ //│ deforest > <<< fusing <<< //│ = 103 //│ p = AA(AA(3)) @@ -419,10 +446,10 @@ fun mapHead(f, x) = if x is AAA(h, t) then f(h) mapHead(x => x, AAA(1, AAA(2, None))) //│ deforest > >>> fusing >>> -//│ deforest > AAA⁰(1, tmp³) -> -//│ deforest > match: scrut²² -//│ deforest > fields: scrut²².x¹ -//│ deforest > fields: scrut²².y¹ +//│ deforest > AAA⁰(1, tmp⁴) -> +//│ deforest > match: x¹⁸ +//│ deforest > fields: x¹⁸.x¹¹ +//│ deforest > fields: x¹⁸.y³ //│ deforest > <<< fusing <<< //│ = 1 @@ -435,7 +462,7 @@ if A is k + 2 //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut²³ +//│ deforest > match: scrut¹ //│ deforest > <<< fusing <<< //│ = 5 //│ k = undefined @@ -447,9 +474,9 @@ fun test(a) = if a is test(B) + test(C) //│ deforest > >>> fusing >>> //│ deforest > C⁰ -> -//│ deforest > match: scrut²⁴ +//│ deforest > match: a⁵ //│ deforest > B⁰ -> -//│ deforest > match: scrut²⁴ +//│ deforest > match: a⁵ //│ deforest > <<< fusing <<< //│ = 4 @@ -467,11 +494,11 @@ fun test(x, y, z) = if x is test(AA(1), AA(2), AA(3)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut²⁵ -//│ deforest > fields: scrut²⁵.aa⁰ +//│ deforest > match: y⁵ +//│ deforest > fields: y⁵.aa⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut²⁶ -//│ deforest > fields: scrut²⁶.aa⁰ +//│ deforest > match: x¹⁹ +//│ deforest > fields: x¹⁹.aa⁰ //│ deforest > <<< fusing <<< //│ = "2AA(3)NaN" @@ -486,15 +513,15 @@ fun test(x) = let p = if true then AA(CCC(AA)) else BB(CCC(AA)) in... test(p) //│ deforest > >>> fusing >>> -//│ deforest > BB⁰(tmp⁴) -> -//│ deforest > match: scrut²⁷ -//│ deforest > fields: scrut²⁷.bb⁰ +//│ deforest > BB⁰(tmp⁵) -> +//│ deforest > match: x²⁰ +//│ deforest > fields: x²⁰.bb⁰ //│ deforest > CCC⁰(AA⁰) -> //│ deforest > match: arg$BB$0$⁰ //│ deforest > fields: arg$BB$0$⁰.c⁰ -//│ deforest > AA⁰(tmp⁵) -> -//│ deforest > match: scrut²⁷ -//│ deforest > fields: scrut²⁷.aa⁰ +//│ deforest > AA⁰(tmp⁶) -> +//│ deforest > match: x²⁰ +//│ deforest > fields: x²⁰.aa⁰ //│ deforest > CCC⁰(AA⁰) -> //│ deforest > match: arg$AA$0$¹ //│ deforest > fields: arg$AA$0$¹.c⁰ @@ -514,11 +541,11 @@ let p = if true then AA(CCC) else BB(CCC) in... test(p, id(CCC(123))) //│ deforest > >>> fusing >>> //│ deforest > BB⁰(CCC⁰) -> -//│ deforest > match: scrut²⁸ -//│ deforest > fields: scrut²⁸.bb⁰ +//│ deforest > match: x²¹ +//│ deforest > fields: x²¹.bb⁰ //│ deforest > AA⁰(CCC⁰) -> -//│ deforest > match: scrut²⁸ -//│ deforest > fields: scrut²⁸.aa⁰ +//│ deforest > match: x²¹ +//│ deforest > fields: x²¹.aa⁰ //│ deforest > <<< fusing <<< //│ = 141 @@ -530,28 +557,24 @@ fun f(x) = if x is f(id(A)) + f(A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut²⁹ +//│ deforest > match: x²² //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp, tmp1, tmp2, f_8$f, f_8$scrut_A, f_8$scrut_rest, deforest$lam; +//│ let tmp, tmp1, tmp2, f_8$f, f_8$x_A, f_8$x_rest, deforest$lam; //│ @private //│ define f_8$f as fun f_8$f⁰(x) { -//│ let scrut; -//│ set scrut = x; -//│ return scrut() +//│ return x() //│ }; //│ @affine(1) @private -//│ define f_8$scrut_A as fun f_8$scrut_A⁰()() { +//│ define f_8$x_A as fun f_8$x_A⁰()() { //│ return 1 //│ }; //│ @private -//│ define f_8$scrut_rest as fun f_8$scrut_rest⁰() { +//│ define f_8$x_rest as fun f_8$x_rest⁰() { //│ return null //│ }; //│ define f⁰ as fun f¹(x) { -//│ let scrut; -//│ set scrut = x; -//│ match scrut +//│ match x //│ A⁰ => //│ return 1 //│ else @@ -560,7 +583,7 @@ f(id(A)) + f(A) //│ }; //│ set tmp = Predef⁰.id⁰(A⁰); //│ set tmp1 = f¹(tmp); -//│ set deforest$lam = f_8$scrut_A⁰(); +//│ set deforest$lam = f_8$x_A⁰(); //│ set tmp2 = f_8$f⁰(deforest$lam); //│ return +⁰(tmp1, tmp2) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -578,11 +601,11 @@ fun g(x) = if x is f(g(AA(1))) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut³⁰ +//│ deforest > match: x²³ //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁰ +//│ deforest > match: x²³ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut³¹ +//│ deforest > match: x²⁴ //│ deforest > <<< fusing <<< //│ = 0 @@ -594,9 +617,9 @@ fun f(a, b) = if a is f(A, B) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut³² +//│ deforest > match: a⁶ //│ deforest > B⁰ -> -//│ deforest > match: scrut³³ +//│ deforest > match: b² //│ deforest > <<< fusing <<< //│ = 1 @@ -613,13 +636,13 @@ fun c2(x) = if x is c1(app(AA)) + c1(AA(2)) + c2(AA(3)) + c2(A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut³⁴ +//│ deforest > match: x²⁵ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut³⁴ -//│ deforest > fields: scrut³⁴.aa⁰ +//│ deforest > match: x²⁵ +//│ deforest > fields: x²⁵.aa⁰ //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut³⁵ -//│ deforest > fields: scrut³⁵.aa⁰ +//│ deforest > match: x1⁰ +//│ deforest > fields: x1⁰.aa⁰ //│ deforest > <<< fusing <<< //│ = 6 @@ -635,27 +658,27 @@ print(c(p(true)) + c(p(false))) c(B) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut³⁶ +//│ deforest > match: scrut² //│ deforest > A⁰ -> -//│ deforest > match: scrut³⁶ +//│ deforest > match: scrut² //│ deforest > B⁰ -> -//│ deforest > match: scrut³⁷ +//│ deforest > match: x²⁶ //│ deforest > A⁰ -> -//│ deforest > match: scrut³⁶ +//│ deforest > match: scrut² //│ deforest > new AA¹(2) -> -//│ deforest > match: scrut³⁷ -//│ deforest > fields: scrut³⁷.aa⁰ +//│ deforest > match: x²⁶ +//│ deforest > fields: x²⁶.aa⁰ //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut³⁷ -//│ deforest > fields: scrut³⁷.bb⁰ +//│ deforest > match: x²⁶ +//│ deforest > fields: x²⁶.bb⁰ //│ deforest > A⁰ -> -//│ deforest > match: scrut³⁶ +//│ deforest > match: scrut² //│ deforest > new AA¹(2) -> -//│ deforest > match: scrut³⁷ -//│ deforest > fields: scrut³⁷.aa⁰ +//│ deforest > match: x²⁶ +//│ deforest > fields: x²⁶.aa⁰ //│ deforest > BB⁰(3) -> -//│ deforest > match: scrut³⁷ -//│ deforest > fields: scrut³⁷.bb⁰ +//│ deforest > match: x²⁶ +//│ deforest > fields: x²⁶.bb⁰ //│ deforest > <<< fusing <<< //│ > 5 //│ ═══[RUNTIME ERROR] Error: e1 @@ -669,8 +692,8 @@ f(AA(1), BB(2)) //│ deforest > BB⁰(2) -> //│ deforest > select: bb //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut³⁸ -//│ deforest > fields: scrut³⁸.aa⁰ +//│ deforest > match: a⁷ +//│ deforest > fields: a⁷.aa⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -682,7 +705,7 @@ fun g(k, x) = if k is g(A, AA(3)) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut³⁹ +//│ deforest > match: k⁰ //│ deforest > <<< fusing <<< //│ = 9 @@ -708,8 +731,8 @@ let p = AA(AA(3)) outer1(p)(1, 2) + outer2(p, 3)(4) + inner(AA(5))(6) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(5) -> -//│ deforest > match: scrut⁴⁰ -//│ deforest > fields: scrut⁴⁰.aa⁰ +//│ deforest > match: x²⁷ +//│ deforest > fields: x²⁷.aa⁰ //│ deforest > <<< fusing <<< //│ = 103 //│ p = AA(AA(3)) @@ -724,6 +747,10 @@ fun f(x) = if x is AA then x.AA#aa.AA#aa f(AA(AA(3))) //│ deforest > >>> fusing >>> +//│ deforest > AA⁰(tmp⁷) -> +//│ deforest > match: x²⁸ +//│ deforest > fields: x²⁸.aa⁰ +//│ deforest > fields: x²⁸.aa⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -737,9 +764,9 @@ fun f2(a2) = if a2 is AA then 2 f1(id(AA(1))) + f2(id(AA(2))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut⁴¹ +//│ deforest > match: a2⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut⁴² +//│ deforest > match: a1⁰ //│ deforest > <<< fusing <<< //│ = 3 @@ -754,11 +781,11 @@ fun c1(x) = if x is c1(1 :: Nil) //│ deforest > >>> fusing >>> //│ deforest > Cons⁰(1, Nil⁰) -> -//│ deforest > match: scrut⁴³ -//│ deforest > fields: scrut⁴³.h⁰ -//│ deforest > fields: scrut⁴³.t⁰ +//│ deforest > match: x²⁹ +//│ deforest > fields: x²⁹.h⁰ +//│ deforest > fields: x²⁹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁴⁴ +//│ deforest > match: t¹ //│ deforest > <<< fusing <<< //│ = 0 @@ -776,6 +803,11 @@ fun apply(f) = f(AA(inner)) apply(f) //│ deforest > >>> fusing >>> +//│ deforest > AA⁰(inner⁰) -> +//│ deforest > match: x³⁰ +//│ deforest > fields: x³⁰.aa⁰ +//│ deforest > AA⁰(0) -> +//│ deforest > match: y1⁰ //│ deforest > <<< fusing <<< //│ = 0 @@ -788,10 +820,10 @@ fun f1(a1) = if a1 is AA then 1 fun f2(a2) = if a2 is AA then 2 f1(wrap(1)) + f2(wrap(2)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x²) -> -//│ deforest > match: scrut⁴⁵ -//│ deforest > AA⁰(x²) -> -//│ deforest > match: scrut⁴⁶ +//│ deforest > AA⁰(x³¹) -> +//│ deforest > match: a2¹ +//│ deforest > AA⁰(x³¹) -> +//│ deforest > match: a1¹ //│ deforest > <<< fusing <<< //│ = 3 @@ -802,9 +834,9 @@ fun p2(x) = if x == 0 then AA(x) else p1(x - 1) fun c(a) = if a is AA(x) then x c(p1(3)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x³) -> -//│ deforest > match: scrut⁴⁷ -//│ deforest > fields: scrut⁴⁷.aa⁰ +//│ deforest > AA⁰(x³²) -> +//│ deforest > match: a⁸ +//│ deforest > fields: a⁸.aa⁰ //│ deforest > <<< fusing <<< //│ = 0 @@ -825,25 +857,25 @@ fun g(b) = [f(3), g(true)] //│ deforest > >>> fusing >>> //│ deforest > BB⁰(0) -> -//│ deforest > match: scrut⁴⁸ +//│ deforest > match: scrut³ //│ deforest > D⁰ -> -//│ deforest > match: scrut⁴⁸ +//│ deforest > match: scrut³ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut⁴⁹ +//│ deforest > match: scrut⁴ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut⁴⁹ +//│ deforest > match: scrut⁴ //│ deforest > C⁰ -> -//│ deforest > match: scrut⁴⁹ +//│ deforest > match: scrut⁴ //│ deforest > BB⁰(0) -> -//│ deforest > match: scrut⁴⁸ +//│ deforest > match: scrut³ //│ deforest > D⁰ -> -//│ deforest > match: scrut⁴⁸ +//│ deforest > match: scrut³ //│ deforest > AA⁰(0) -> -//│ deforest > match: scrut⁴⁹ +//│ deforest > match: scrut⁴ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut⁴⁹ +//│ deforest > match: scrut⁴ //│ deforest > C⁰ -> -//│ deforest > match: scrut⁴⁹ +//│ deforest > match: scrut⁴ //│ deforest > <<< fusing <<< //│ = [D, AA(1)] @@ -859,7 +891,7 @@ fun f(ls) = if ls is [] then f([]) //│ deforest > >>> fusing >>> //│ deforest > [] -> -//│ deforest > match: scrut⁵⁰ +//│ deforest > match: ls⁰ //│ deforest > <<< fusing <<< //│ = 4 @@ -870,9 +902,9 @@ fun f(x, b) = if x is f(Nil, false) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁵¹ +//│ deforest > match: x³³ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁵¹ +//│ deforest > match: x³³ //│ deforest > <<< fusing <<< //│ = 0 @@ -898,9 +930,9 @@ fun c2(x) = if x is AA then 2 c1(p(1)) + c2(p(2)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut⁵² +//│ deforest > match: x³⁴ //│ deforest > AA⁰(3) -> -//│ deforest > match: scrut⁵³ +//│ deforest > match: x³⁵ //│ deforest > <<< fusing <<< //│ = 3 @@ -914,21 +946,21 @@ fun f4(a4) = if a4 is AA(aa) then aa fun f5(a5) = if a5 is AA(aa) then aa f1(p(1)) + f2(p(2)) + f3(p(3)) + f4(p(4)) + f5(p(5)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x⁴) -> -//│ deforest > match: scrut⁵⁴ -//│ deforest > fields: scrut⁵⁴.aa⁰ -//│ deforest > AA⁰(x⁴) -> -//│ deforest > match: scrut⁵⁵ -//│ deforest > fields: scrut⁵⁵.aa⁰ -//│ deforest > AA⁰(x⁴) -> -//│ deforest > match: scrut⁵⁶ -//│ deforest > fields: scrut⁵⁶.aa⁰ -//│ deforest > AA⁰(x⁴) -> -//│ deforest > match: scrut⁵⁷ -//│ deforest > fields: scrut⁵⁷.aa⁰ -//│ deforest > AA⁰(x⁴) -> -//│ deforest > match: scrut⁵⁸ -//│ deforest > fields: scrut⁵⁸.aa⁰ +//│ deforest > AA⁰(x³⁶) -> +//│ deforest > match: a5⁰ +//│ deforest > fields: a5⁰.aa⁰ +//│ deforest > AA⁰(x³⁶) -> +//│ deforest > match: a4⁰ +//│ deforest > fields: a4⁰.aa⁰ +//│ deforest > AA⁰(x³⁶) -> +//│ deforest > match: a3⁰ +//│ deforest > fields: a3⁰.aa⁰ +//│ deforest > AA⁰(x³⁶) -> +//│ deforest > match: a2² +//│ deforest > fields: a2².aa⁰ +//│ deforest > AA⁰(x³⁶) -> +//│ deforest > match: a1² +//│ deforest > fields: a1².aa⁰ //│ deforest > <<< fusing <<< //│ = 15 @@ -955,30 +987,30 @@ fun f4(ls4) = if ls4 is Nil then 5 f1(to(4)) + f2(to(5)) + f3(to(6)) + f4(to(7)) //│ deforest > >>> fusing >>> -//│ deforest > Cons⁰(n⁰, tmp⁶) -> -//│ deforest > match: scrut⁵⁹ -//│ deforest > fields: scrut⁵⁹.h⁰ -//│ deforest > fields: scrut⁵⁹.t⁰ +//│ deforest > Cons⁰(n⁰, tmp⁸) -> +//│ deforest > match: ls4⁰ +//│ deforest > fields: ls4⁰.h⁰ +//│ deforest > fields: ls4⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁵⁹ -//│ deforest > Cons⁰(n⁰, tmp⁶) -> -//│ deforest > match: scrut⁶⁰ -//│ deforest > fields: scrut⁶⁰.h⁰ -//│ deforest > fields: scrut⁶⁰.t⁰ +//│ deforest > match: ls4⁰ +//│ deforest > Cons⁰(n⁰, tmp⁸) -> +//│ deforest > match: ls3⁰ +//│ deforest > fields: ls3⁰.h⁰ +//│ deforest > fields: ls3⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁶⁰ -//│ deforest > Cons⁰(n⁰, tmp⁶) -> -//│ deforest > match: scrut⁶¹ -//│ deforest > fields: scrut⁶¹.h⁰ -//│ deforest > fields: scrut⁶¹.t⁰ +//│ deforest > match: ls3⁰ +//│ deforest > Cons⁰(n⁰, tmp⁸) -> +//│ deforest > match: ls2⁰ +//│ deforest > fields: ls2⁰.h⁰ +//│ deforest > fields: ls2⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁶¹ -//│ deforest > Cons⁰(n⁰, tmp⁶) -> -//│ deforest > match: scrut⁶² -//│ deforest > fields: scrut⁶².h⁰ -//│ deforest > fields: scrut⁶².t⁰ +//│ deforest > match: ls2⁰ +//│ deforest > Cons⁰(n⁰, tmp⁸) -> +//│ deforest > match: ls1⁰ +//│ deforest > fields: ls1⁰.h⁰ +//│ deforest > fields: ls1⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁶² +//│ deforest > match: ls1⁰ //│ deforest > <<< fusing <<< //│ = 88 @@ -994,18 +1026,18 @@ fun f1(p) = if p is AA(a) then a + 1 fun f2(p) = if p is AA(b) then b + 2 f1(wrap(true)) + f2(g2(false)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x⁵) -> -//│ deforest > match: scrut⁶³ -//│ deforest > fields: scrut⁶³.aa⁰ -//│ deforest > AA⁰(x⁵) -> -//│ deforest > match: scrut⁶³ -//│ deforest > fields: scrut⁶³.aa⁰ -//│ deforest > AA⁰(x⁵) -> -//│ deforest > match: scrut⁶⁴ -//│ deforest > fields: scrut⁶⁴.aa⁰ -//│ deforest > AA⁰(x⁵) -> -//│ deforest > match: scrut⁶⁴ -//│ deforest > fields: scrut⁶⁴.aa⁰ +//│ deforest > AA⁰(x³⁷) -> +//│ deforest > match: p⁰ +//│ deforest > fields: p⁰.aa⁰ +//│ deforest > AA⁰(x³⁷) -> +//│ deforest > match: p⁰ +//│ deforest > fields: p⁰.aa⁰ +//│ deforest > AA⁰(x³⁷) -> +//│ deforest > match: p¹ +//│ deforest > fields: p¹.aa⁰ +//│ deforest > AA⁰(x³⁷) -> +//│ deforest > match: p¹ +//│ deforest > fields: p¹.aa⁰ //│ deforest > <<< fusing <<< //│ = 8 @@ -1019,9 +1051,9 @@ fun f2(p) = if p is AA(b) then b + 2 fun f3(p) = if p is AA(c) then c + 3 f1(one) + f2(one) + f3(two) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x⁶) -> -//│ deforest > match: scrut⁶⁵ -//│ deforest > fields: scrut⁶⁵.aa⁰ +//│ deforest > AA⁰(x³⁸) -> +//│ deforest > match: p² +//│ deforest > fields: p².aa⁰ //│ deforest > <<< fusing <<< //│ = 10 @@ -1033,11 +1065,11 @@ fun f2(a) = if a is AA(i) then i + 1 f1(id(AA(1))) + f2(id(AA(2))) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut⁶⁶ -//│ deforest > fields: scrut⁶⁶.aa⁰ +//│ deforest > match: a⁹ +//│ deforest > fields: a⁹.aa⁰ //│ deforest > AA⁰(1) -> -//│ deforest > match: scrut⁶⁷ -//│ deforest > fields: scrut⁶⁷.aa⁰ +//│ deforest > match: a¹⁰ +//│ deforest > fields: a¹⁰.aa⁰ //│ deforest > <<< fusing <<< //│ = 4 @@ -1051,11 +1083,11 @@ fun f2(a2) = if a2 is AA(a) then a + 2 fun wrap(a) = a f1(wrap(p(2))) + f2(p(1)) //│ deforest > >>> fusing >>> -//│ deforest > AA⁰(x⁷) -> -//│ deforest > match: scrut⁶⁸ -//│ deforest > fields: scrut⁶⁸.aa⁰ -//│ deforest > AA⁰(x⁷) -> -//│ deforest > match: scrut⁶⁹ -//│ deforest > fields: scrut⁶⁹.aa⁰ +//│ deforest > AA⁰(x³⁹) -> +//│ deforest > match: a2³ +//│ deforest > fields: a2³.aa⁰ +//│ deforest > AA⁰(x³⁹) -> +//│ deforest > match: a1³ +//│ deforest > fields: a1³.aa⁰ //│ deforest > <<< fusing <<< //│ = 6 diff --git a/hkmc2/shared/src/test/mlscript/deforest/todos.mls b/hkmc2/shared/src/test/mlscript/deforest/todos.mls index 64e6c1356b..2b38ba103b 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/todos.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/todos.mls @@ -41,38 +41,36 @@ let p = AA(AA(AA(10))) test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ deforest > >>> fusing >>> //│ deforest > B⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: x⁰ //│ deforest > AA⁰(tmp⁰) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.x⁰ +//│ deforest > match: x⁰ +//│ deforest > fields: x⁰.x¹ //│ deforest > AA⁰(tmp¹) -> //│ deforest > match: arg$AA$0$⁰ -//│ deforest > fields: arg$AA$0$⁰.x⁰ +//│ deforest > fields: arg$AA$0$⁰.x¹ //│ deforest > AA⁰(10) -> //│ deforest > match: arg$AA$0$¹ -//│ deforest > fields: arg$AA$0$¹.x⁰ +//│ deforest > fields: arg$AA$0$¹.x¹ //│ deforest > <<< fusing <<< //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, test_28$test, test_25$test, test_28$scrut_dflt, test_28$scrut_rest, test_25$scrut_AA, test_25$scrut_rest, test_25$arg$AA$0$_AA, test_25$arg$AA$0$_rest, test_25$arg$AA$0$_AA1, test_25$arg$AA$0$_rest1, AA_x, AA_x1, AA_x2, deforest$lam; +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, test_28$test, test_25$test, test_28$x_dflt, test_28$x_rest, test_25$x_AA, test_25$x_rest, test_25$arg$AA$0$_AA, test_25$arg$AA$0$_rest, test_25$arg$AA$0$_AA1, test_25$arg$AA$0$_rest1, AA_x, AA_x1, AA_x2, deforest$lam; //│ @private //│ define test_28$test as fun test_28$test⁰(x) { -//│ let t, scrut, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; -//│ set scrut = x; -//│ return scrut(tmp11, t, tmp11, tmp12, tmp13, tmp14) +//│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; +//│ return x(tmp11, t, tmp11, tmp12, tmp13, tmp14) //│ }; //│ @private //│ define test_25$test as fun test_25$test⁰(x) { -//│ let t, scrut, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; -//│ set scrut = x; -//│ return scrut(t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14, t, tmp11, tmp12, tmp13, tmp14) +//│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; +//│ return x(t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14, t, tmp11, tmp12, tmp13, tmp14) //│ }; //│ @affine(1) @private -//│ define test_28$scrut_dflt as fun test_28$scrut_dflt⁰()(fv_tmp, fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) { +//│ define test_28$x_dflt as fun test_28$x_dflt⁰()(fv_tmp, fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) { //│ set fv_tmp1 = 4; -//│ return test_28$scrut_rest⁰(fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) +//│ return test_28$x_rest⁰(fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) //│ }; //│ @affine(1) @private -//│ define test_25$scrut_AA as fun test_25$scrut_AA⁰(AA_x3)(fv_t, fv_a, fv_arg$AA$0$, fv_arg$AA$0$1, fv_arg$AA$0$2, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3, fv_t1, fv_tmp4, fv_tmp5, fv_tmp6, fv_tmp7) { +//│ define test_25$x_AA as fun test_25$x_AA⁰(AA_x3)(fv_t, fv_a, fv_arg$AA$0$, fv_arg$AA$0$1, fv_arg$AA$0$2, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3, fv_t1, fv_tmp4, fv_tmp5, fv_tmp6, fv_tmp7) { //│ set fv_arg$AA$0$ = AA_x3; //│ return fv_arg$AA$0$(fv_t1, fv_a, fv_arg$AA$0$1, fv_arg$AA$0$2, fv_tmp4, fv_tmp5, fv_tmp6, fv_tmp7, fv_t1, fv_tmp4, fv_tmp5, fv_tmp6, fv_tmp7) //│ }; @@ -89,7 +87,7 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ return test_25$arg$AA$0$_rest⁰(fv_t, fv_tmp1, fv_tmp2, fv_tmp3, fv_tmp4) //│ }; //│ @private -//│ define test_28$scrut_rest as fun test_28$scrut_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { +//│ define test_28$x_rest as fun test_28$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { //│ set fv_t = fv_tmp; //│ set fv_tmp1 = +⁰(fv_t, 20); //│ set fv_tmp2 = -⁰(fv_tmp1, 3); @@ -97,7 +95,7 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ return -⁰(fv_tmp3, 1) //│ }; //│ @private -//│ define test_25$scrut_rest as fun test_25$scrut_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { +//│ define test_25$x_rest as fun test_25$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { //│ set fv_t = fv_tmp; //│ set fv_tmp1 = +⁰(fv_t, 20); //│ set fv_tmp2 = -⁰(fv_tmp1, 3); @@ -106,24 +104,23 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ }; //│ @private //│ define test_25$arg$AA$0$_rest as fun test_25$arg$AA$0$_rest¹(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { -//│ return test_25$scrut_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) +//│ return test_25$x_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) //│ }; //│ @private //│ define test_25$arg$AA$0$_rest1 as fun test_25$arg$AA$0$_rest⁰(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) { //│ return test_25$arg$AA$0$_rest¹(fv_t, fv_tmp, fv_tmp1, fv_tmp2, fv_tmp3) //│ }; //│ define test⁰ as fun test¹(x) { -//│ let t, scrut, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; -//│ set scrut = x; -//│ match scrut +//│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14; +//│ match x //│ AA¹ => -//│ set arg$AA$0$ = scrut.x⁰; +//│ set arg$AA$0$ = x.x¹; //│ match arg$AA$0$ //│ AA¹ => -//│ set arg$AA$0$1 = arg$AA$0$.x⁰; +//│ set arg$AA$0$1 = arg$AA$0$.x¹; //│ match arg$AA$0$1 //│ AA¹ => -//│ set arg$AA$0$2 = arg$AA$0$1.x⁰; +//│ set arg$AA$0$2 = arg$AA$0$1.x¹; //│ set a = arg$AA$0$2; //│ set tmp11 = a; //│ end @@ -145,11 +142,10 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ return -⁰(tmp14, 1) //│ }; //│ define f⁰ as fun f¹(a) { -//│ let scrut, arg$AA$0$; -//│ set scrut = a; -//│ match scrut +//│ let arg$AA$0$; +//│ match a //│ AA¹ => -//│ set arg$AA$0$ = scrut.x⁰; +//│ set arg$AA$0$ = a.x¹; //│ match arg$AA$0$ //│ AA¹ => //│ return 0 @@ -171,10 +167,10 @@ test(p) + f(p) + test(AA(AA(AA(10)))) + test(B) //│ set AA_x1 = tmp5; //│ set tmp6 = test_25$arg$AA$0$_AA⁰(AA_x1); //│ set AA_x2 = tmp6; -//│ set tmp7 = test_25$scrut_AA⁰(AA_x2); +//│ set tmp7 = test_25$x_AA⁰(AA_x2); //│ set tmp8 = test_25$test⁰(tmp7); //│ set tmp9 = +⁰(tmp4, tmp8); -//│ set deforest$lam = test_28$scrut_dflt⁰(); +//│ set deforest$lam = test_28$x_dflt⁰(); //│ set tmp10 = test_28$test⁰(deforest$lam); //│ return +⁰(tmp9, tmp10) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -193,17 +189,17 @@ fun test() = test() //│ deforest > >>> fusing >>> //│ deforest > Global⁰(1) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.x¹ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x² //│ deforest > Global⁰(1) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.x¹ +//│ deforest > match: scrut⁰ +//│ deforest > fields: scrut⁰.x² //│ deforest > <<< fusing <<< //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'Local' -//│ ║ l.192: Global(x) then Local(x + 1) +//│ ║ l.188: Global(x) then Local(x + 1) //│ ║ ^^^^^ //│ ╟── which references the symbol introduced here -//│ ║ l.190: data class Local(x) +//│ ║ l.186: data class Local(x) //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] ReferenceError: Local is not defined //│ ═══[RUNTIME ERROR] Expected: 'Local(2)', got: 'undefined' @@ -228,9 +224,9 @@ fun f(x) = if x is A then 1 f(A) + f(A) //│ deforest > >>> fusing >>> //│ deforest > A⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: x³ //│ deforest > A⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: x³ //│ deforest > <<< fusing <<< //│ = 2 @@ -244,7 +240,7 @@ module Test with Test.Test2.foo(AA(2)) //│ deforest > >>> fusing >>> //│ deforest > AA⁰(2) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.x⁰ +//│ deforest > match: a⁰ +//│ deforest > fields: a⁰.x¹ //│ deforest > <<< fusing <<< //│ ═══[RUNTIME ERROR] ReferenceError: Test2 is not defined diff --git a/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls b/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls index c1cd347eaf..213fa2ee2d 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls @@ -22,25 +22,25 @@ fun testUnzipZip(n) = unzip(zip(id(enumFromTo(1, n)), id(enumFromTo(2, n + 3)))) testUnzipZip(3) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: ls_unzip⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: ls_unzip⁰ //│ deforest > Cons⁰(tmp⁰, tmp¹) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h⁰ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: ls_unzip⁰ +//│ deforest > fields: ls_unzip⁰.h⁰ +//│ deforest > fields: ls_unzip⁰.t⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a⁰ //│ deforest > fields: arg$Cons$0$⁰.b⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: ls_unzip⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁰ +//│ deforest > match: ls_unzip⁰ //│ deforest > Cons⁰(tmp⁰, tmp¹) -> -//│ deforest > match: scrut⁰ -//│ deforest > fields: scrut⁰.h⁰ -//│ deforest > fields: scrut⁰.t⁰ +//│ deforest > match: ls_unzip⁰ +//│ deforest > fields: ls_unzip⁰.h⁰ +//│ deforest > fields: ls_unzip⁰.t⁰ //│ deforest > Pair⁰(x⁰, y⁰) -> //│ deforest > match: arg$Cons$0$⁰ //│ deforest > fields: arg$Cons$0$⁰.a⁰ @@ -63,29 +63,29 @@ fun testZipUnzip(n) = if unzip(id(makeZippedList(n))) is testZipUnzip(3) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: xs_zip⁰ //│ deforest > Cons⁰(a¹, atail⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: xs_zip⁰ +//│ deforest > fields: xs_zip⁰.h⁰ +//│ deforest > fields: xs_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: ys_zip⁰ //│ deforest > Cons⁰(b¹, btail⁰) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².h⁰ -//│ deforest > fields: scrut².t⁰ +//│ deforest > match: ys_zip⁰ +//│ deforest > fields: ys_zip⁰.h⁰ +//│ deforest > fields: ys_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut¹ +//│ deforest > match: xs_zip⁰ //│ deforest > Cons⁰(a¹, atail⁰) -> -//│ deforest > match: scrut¹ -//│ deforest > fields: scrut¹.h⁰ -//│ deforest > fields: scrut¹.t⁰ +//│ deforest > match: xs_zip⁰ +//│ deforest > fields: xs_zip⁰.h⁰ +//│ deforest > fields: xs_zip⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut² +//│ deforest > match: ys_zip⁰ //│ deforest > Cons⁰(b¹, btail⁰) -> -//│ deforest > match: scrut² -//│ deforest > fields: scrut².h⁰ -//│ deforest > fields: scrut².t⁰ +//│ deforest > match: ys_zip⁰ +//│ deforest > fields: ys_zip⁰.h⁰ +//│ deforest > fields: ys_zip⁰.t⁰ //│ deforest > <<< fusing <<< //│ = Cons(Pair(3, 4), Cons(Pair(2, 3), Cons(Pair(1, 2), Nil))) @@ -109,53 +109,53 @@ fun testZipMapBothUnzip(n) = if unzip(id(makeZippedList(n))) is testZipMapBothUnzip(4) //│ deforest > >>> fusing >>> //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: ls_map⁰ //│ deforest > Cons⁰(a², atail¹) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.h⁰ -//│ deforest > fields: scrut³.t⁰ +//│ deforest > match: ls_map⁰ +//│ deforest > fields: ls_map⁰.h⁰ +//│ deforest > fields: ls_map⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: ls_map⁰ //│ deforest > Cons⁰(b², btail¹) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.h⁰ -//│ deforest > fields: scrut³.t⁰ +//│ deforest > match: ls_map⁰ +//│ deforest > fields: ls_map⁰.h⁰ +//│ deforest > fields: ls_map⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁴ +//│ deforest > match: xs_zip¹ //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: xs_zip¹ +//│ deforest > fields: xs_zip¹.h⁰ +//│ deforest > fields: xs_zip¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁵ +//│ deforest > match: ys_zip¹ //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.h⁰ -//│ deforest > fields: scrut⁵.t⁰ +//│ deforest > match: ys_zip¹ +//│ deforest > fields: ys_zip¹.h⁰ +//│ deforest > fields: ys_zip¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: ls_map⁰ //│ deforest > Cons⁰(a², atail¹) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.h⁰ -//│ deforest > fields: scrut³.t⁰ +//│ deforest > match: ls_map⁰ +//│ deforest > fields: ls_map⁰.h⁰ +//│ deforest > fields: ls_map⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut³ +//│ deforest > match: ls_map⁰ //│ deforest > Cons⁰(b², btail¹) -> -//│ deforest > match: scrut³ -//│ deforest > fields: scrut³.h⁰ -//│ deforest > fields: scrut³.t⁰ +//│ deforest > match: ls_map⁰ +//│ deforest > fields: ls_map⁰.h⁰ +//│ deforest > fields: ls_map⁰.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁴ +//│ deforest > match: xs_zip¹ //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: scrut⁴ -//│ deforest > fields: scrut⁴.h⁰ -//│ deforest > fields: scrut⁴.t⁰ +//│ deforest > match: xs_zip¹ +//│ deforest > fields: xs_zip¹.h⁰ +//│ deforest > fields: xs_zip¹.t⁰ //│ deforest > Nil⁰ -> -//│ deforest > match: scrut⁵ +//│ deforest > match: ys_zip¹ //│ deforest > Cons⁰(tmp², tmp³) -> -//│ deforest > match: scrut⁵ -//│ deforest > fields: scrut⁵.h⁰ -//│ deforest > fields: scrut⁵.t⁰ +//│ deforest > match: ys_zip¹ +//│ deforest > fields: ys_zip¹.h⁰ +//│ deforest > fields: ys_zip¹.t⁰ //│ deforest > <<< fusing <<< //│ = Cons( //│ Pair(5, 25), diff --git a/hkmc2/shared/src/test/mlscript/interop/CtorBypass.mls b/hkmc2/shared/src/test/mlscript/interop/CtorBypass.mls index b7209e62e9..27c81a3d29 100644 --- a/hkmc2/shared/src/test/mlscript/interop/CtorBypass.mls +++ b/hkmc2/shared/src/test/mlscript/interop/CtorBypass.mls @@ -19,7 +19,7 @@ A!prototype // * This can be used for deserialization. The constructor has not initialized its // * fields, so use a dynamic receiver to retain runtime checks for missing fields. -val b: dyn = Object.create(A!prototype) +let b = Object.create(A!prototype) as dyn //│ b = A :sjs diff --git a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls index c600616795..fbda2542c9 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls @@ -59,11 +59,11 @@ fun foo(y) = set y = 2 [new C, new mut C] +// Exercise writes to undeclared fields with checked dynamic access. let cc = foo(10) -// Exercise writes to undeclared fields with checked dynamic access. -val c0: dyn = cc.0 -val c1: dyn = cc.1 + c0 = cc.0 as dyn + c1 = cc.1 as dyn //│ c0 = C //│ c1 = C //│ cc = [C, C] @@ -91,7 +91,7 @@ fun foo(y) = //│ ║ l.86: object O with //│ ╙── ^ -val o: dyn = foo(10) +let o = foo(10) as dyn //│ o = O :re diff --git a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls index 9fbbc8d864..c827c4e6a5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatMat.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatMat.mls @@ -35,7 +35,7 @@ let x = Foo(Bar) if x is Foo(y) then y.b //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ if { let scrut⁰ = x⁰; scrut⁰ is Foo¹(_ as y⁰) then y⁰.b⁰ } +//│ if { x⁰ is Foo¹(_ as y⁰) then y⁰.b⁰ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 123 @@ -61,7 +61,7 @@ let x2 = Foo(bz) if x2 is Foo(y) then y.b //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ if { let scrut¹ = x2⁰; scrut¹ is Foo¹(_ as y¹) then y¹.b² } +//│ if { x2⁰ is Foo¹(_ as y¹) then y¹.b² } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = Bar { b: 123, class: object Bar } diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 383da64d4b..3516720fbb 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -172,12 +172,11 @@ fun foo(x) = else 3 //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ fun foo⁰::foo¹(x¹) ‹flow:foo⁰› = if { -//│ let scrut⁰ = x¹; -//│ scrut⁰ is [_ as y⁰] and { -//│ let scrut¹ = >(y⁰, 0)‹app¹›; -//│ scrut¹ is true then 1 +//│ x¹ is [_ as y⁰] and { +//│ let scrut⁰ = >(y⁰, 0)‹app¹›; +//│ scrut⁰ is true then 1 //│ }; -//│ scrut⁰ is 0 then 2; +//│ x¹ is 0 then 2; //│ else 3 //│ } //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -202,13 +201,13 @@ fun foo(x) = fun foo[A](x: A) = x.a foo({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.202: fun foo[A](x: A) = x.a +//│ ║ l.201: fun foo[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ║ l.202: fun foo[A](x: A) = x.a +//│ ║ l.201: fun foo[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.202: fun foo[A](x: A) = x.a +//│ ║ l.201: fun foo[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -216,13 +215,13 @@ foo({a: 1}) private fun foo[A](x: A) = x.a foo({a: 1}) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.216: private fun foo[A](x: A) = x.a +//│ ║ l.215: private fun foo[A](x: A) = x.a //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ║ l.216: private fun foo[A](x: A) = x.a +//│ ║ l.215: private fun foo[A](x: A) = x.a //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.216: private fun foo[A](x: A) = x.a +//│ ║ l.215: private fun foo[A](x: A) = x.a //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/ScrutineeReferences.mls b/hkmc2/shared/src/test/mlscript/newres/ScrutineeReferences.mls new file mode 100644 index 0000000000..cfd37164fe --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ScrutineeReferences.mls @@ -0,0 +1,140 @@ +:. +:js + +// Local references stay direct before optimization, so field selections and +// pattern matches remain visibly attached to the same value for deforestation. +:sir +class Item(val value: Int) +fun read(item) = if item is Item(_) then item.Item#value else 0 +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define read⁰ as fun read¹(item) { +//│ let arg$Item$0$; +//│ match item +//│ Item⁰ => +//│ set arg$Item$0$ = item.value⁰; +//│ return item.value⁰ +//│ else +//│ return 0 +//│ end +//│ }; +//│ define Item¹ as class Item⁰ { +//│ val value¹; +//│ constructor Item²(value) { define value¹ as val value⁰ = value; end } +//│ }; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:expect 7 +read(Item(7)) +//│ = 7 + + +// Captured references need their lexical scope information for extraction, +// even though their runtime match does not need a scrutinee temporary. +:sir +class Box[out A](val value: A) +private fun reader[A](value: Box[A] | null) = + fun read() = + if value is ~null as box then box.value else throw "empty" + read +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ @private +//│ define reader⁰ as fun reader¹(value) { +//│ let read; +//│ define read as fun read²() { +//│ let box, output; +//│ match value +//│ null => +//│ throw "empty" +//│ else +//│ set output = value; +//│ set box = output; +//│ return box.value² +//│ end +//│ }; +//│ return read² +//│ }; +//│ define Box⁰ as class Box² { +//│ val value³; +//│ constructor Box¹(value) { define value³ as val value² = value; end } +//│ }; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:expect [3, 4] +[reader[Int](Box(3))(), reader[Str](Box("four"))().length] +//│ = [3, 4] + + +// Reading a captured loop condition again must observe assignments in the body. +:sir +fun drain(flag) = + fun run() = + while flag do + set flag = false + run() + flag +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define drain⁰ as fun drain¹(flag) { +//│ let run; +//│ define run as fun run⁰() { +//│ loop lbl: +//│ match flag +//│ true => +//│ set flag = false; +//│ continue lbl +//│ else +//│ end +//│ end +//│ return runtime⁰.Unit⁰ +//│ }; +//│ do run⁰(); +//│ return flag +//│ }; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:expect [false, false] +[drain(true), drain(false)] +//│ = [false, false] + + +// A by-name reference invokes a getter. Matching several alternatives must +// evaluate it once and retain the result, rather than reusing the reference. +:sir +:expect [7, 1] +let reads = 0 +fun source = + set reads += 1 + reads +let result = if source is + 0 then 0 + 1 then 7 + else 9 +[result, reads] +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let scrut, tmp; +//│ define source⁰ as fun source¹() { +//│ let tmp1; +//│ set tmp1 = +⁰(reads⁰, 1); +//│ set reads⁰ = tmp1; +//│ return reads⁰ +//│ }; +//│ set reads⁰ = 0; +//│ set scrut = source¹(); +//│ match scrut +//│ 0 => +//│ set tmp = 0; +//│ end +//│ 1 => +//│ set tmp = 7; +//│ end +//│ else +//│ set tmp = 9; +//│ end +//│ set result⁰ = tmp; +//│ return [result⁰, reads⁰] +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = [7, 1] +//│ reads = 1 +//│ result = 7 diff --git a/hkmc2/shared/src/test/mlscript/opt/LoopOpt.mls b/hkmc2/shared/src/test/mlscript/opt/LoopOpt.mls index d7c3934cec..1cd397a416 100644 --- a/hkmc2/shared/src/test/mlscript/opt/LoopOpt.mls +++ b/hkmc2/shared/src/test/mlscript/opt/LoopOpt.mls @@ -143,9 +143,7 @@ fun loopReturnDoesNotPolluteExit(flag) = //│ let x; //│ set x = 1; //│ loop lbl: -//│ let scrut; -//│ set scrut = flag; -//│ match scrut +//│ match flag //│ true => //│ set x = 2; //│ return x diff --git a/hkmc2/shared/src/test/mlscript/opt/NoCaseOfCase.mls b/hkmc2/shared/src/test/mlscript/opt/NoCaseOfCase.mls index 8ec56002e5..dd7253edb9 100644 --- a/hkmc2/shared/src/test/mlscript/opt/NoCaseOfCase.mls +++ b/hkmc2/shared/src/test/mlscript/opt/NoCaseOfCase.mls @@ -36,7 +36,7 @@ fun foo(x) = false //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ define foo⁰ as fun foo¹(x) { -//│ let y, f, scrut, lambda; +//│ let y, f, lambda; //│ define lambda as fun lambda⁰(w) { //│ set y = w; //│ return runtime⁰.Unit⁰ @@ -46,15 +46,14 @@ fun foo(x) = //│ true => //│ set y = "ok"; //│ do f("nope"); +//│ match y +//│ "ok" => +//│ return true +//│ else +//│ return false //│ end //│ else //│ set y = "nope"; -//│ end -//│ set scrut = y; -//│ match scrut -//│ "ok" => -//│ return true -//│ else //│ return false //│ end //│ }; diff --git a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls index 6a95715a08..430cb58600 100644 --- a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls @@ -100,13 +100,13 @@ let xs = FingerTreeList.mk(1, 2, 3) if xs is [a, ...ls] then [a, ls] //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut, element0$; -//│ scrut = xs2; -//│ if (runtime.Tuple.isArrayLike(scrut) && scrut.length >= 1) { -//│ let inlinedVal38, tmp16; -//│ element0$ = runtime.Tuple.get(scrut, 0); -//│ tmp16 = scrut.length - 0; -//│ inlinedVal38 = runtime.safeCall(scrut.slice(1, tmp16)); +//│ let element0$; +//│ if (runtime.Tuple.isArrayLike(xs2) && xs2.length >= 1) { +//│ let xs3, inlinedVal38, tmp16; +//│ element0$ = runtime.Tuple.get(xs2, 0); +//│ xs3 = xs2; +//│ tmp16 = xs3.length - 0; +//│ inlinedVal38 = runtime.safeCall(xs3.slice(1, tmp16)); //│ return globalThis.Object.freeze([ //│ element0$, //│ inlinedVal38 @@ -140,9 +140,9 @@ fun popByIndex(start, end, acc, lft: Indexable) = //│ let popByIndex; //│ popByIndex = function popByIndex(start, end, acc, lft) { //│ loopLabel: while (true) { -//│ let scrut1, tmp16, tmp17, tmp18; -//│ scrut1 = start >= end; -//│ if (scrut1 === true) { +//│ let scrut, tmp16, tmp17, tmp18; +//│ scrut = start >= end; +//│ if (scrut === true) { //│ return acc //│ } //│ tmp16 = start + 1; diff --git a/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls b/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls index 479b73264f..0d03a10d32 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls @@ -78,14 +78,12 @@ fun interesting(x) = if x is Box( Int then 1, Bool then 2 ) //│ Split with nested patterns: //│ > if -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is member:Box⁰^term:interesting⁰(member:Int⁰^term:interesting⁰) then 1 -//│ > tmp:scrut is member:Box⁰^term:interesting⁰(member:Bool⁰^term:interesting⁰) then 2 +//│ > x is member:Box⁰^term:interesting⁰(member:Int⁰^term:interesting⁰) then 1 +//│ > x is member:Box⁰^term:interesting⁰(member:Bool⁰^term:interesting⁰) then 2 //│ Expanded split with flattened patterns: //│ > ‹if|while› -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is member:Box⁰^term:interesting⁰(arg$Box$0$) and tmp:arg$Box$0$ is member:Int⁰^term:interesting⁰ then 1 -//│ > tmp:scrut is member:Box⁰^term:interesting⁰(arg$Box$0$) and tmp:arg$Box$0$ is member:Bool⁰^term:interesting⁰ then 2 +//│ > x is member:Box⁰^term:interesting⁰(arg$Box$0$) and tmp:arg$Box$0$ is member:Int⁰^term:interesting⁰ then 1 +//│ > x is member:Box⁰^term:interesting⁰(arg$Box$0$) and tmp:arg$Box$0$ is member:Bool⁰^term:interesting⁰ then 2 interesting of Box(42) //│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls b/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls index c55d5c88e4..34cc193987 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls @@ -41,27 +41,16 @@ fun h1(a) = a is None then 0 //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = a⁰ -//│ > let-split σ = -//│ > let tmp:scrut = a⁰ -//│ > tmp:scrut is member:Option⁰.Some^term:h1⁰(arg$Some$0$) and -//│ > let y = tmp:arg$Some$0$⁰ -//│ > let tmp:scrut = y⁰ -//│ > tmp:scrut is member:Right⁰^term:h1⁰(arg$Right$0$) and -//│ > let z = tmp:arg$Right$0$⁰ -//│ > else z⁰ -//│ > let tmp:scrut = a⁰ -//│ > tmp:scrut is member:Option⁰.None^term:h1⁰ then 0 -//│ > let tmp:scrut = a⁰ -//│ > tmp:scrut is member:Option⁰.None^term:h1⁰ then 0 -//│ > tmp:scrut is member:Option⁰.Some^term:h1⁰(arg$Some$0$) and -//│ > let x = tmp:arg$Some$0$¹ -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is member:Left⁰^term:h1⁰(arg$Left$0$) and +//│ > a is member:Option⁰.Some^term:h1⁰(arg$Some$0$) and +//│ > let x = tmp:arg$Some$0$⁰ +//│ > x is member:Left⁰^term:h1⁰(arg$Left$0$) and //│ > let y = tmp:arg$Left$0$⁰ -//│ > else y¹ -//│ > $σ -//│ > $σ +//│ > else y⁰ +//│ > let y = tmp:arg$Some$0$⁰ +//│ > y is member:Right⁰^term:h1⁰(arg$Right$0$) and +//│ > let z = tmp:arg$Right$0$⁰ +//│ > else z⁰ +//│ > a is member:Option⁰.None^term:h1⁰ then 0 h1(Some(Left(42))) //│ = 42 @@ -84,30 +73,18 @@ fun h2(a) = a is None then 0 //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = a¹ -//│ > let-split σ = -//│ > let tmp:scrut = a¹ -//│ > tmp:scrut is member:Option⁰.Some^term:h2⁰(arg$Some$0$) and -//│ > let y = tmp:arg$Some$0$² -//│ > let y' = y² -//│ > let tmp:scrut = y'⁰ -//│ > tmp:scrut is member:Right⁰^term:h2⁰(arg$Right$0$) and -//│ > let z = tmp:arg$Right$0$¹ -//│ > else z¹ -//│ > let tmp:scrut = a¹ -//│ > tmp:scrut is member:Option⁰.None^term:h2⁰ then 0 -//│ > let tmp:scrut = a¹ -//│ > tmp:scrut is member:Option⁰.None^term:h2⁰ then 0 -//│ > tmp:scrut is member:Option⁰.Some^term:h2⁰(arg$Some$0$) and -//│ > let x = tmp:arg$Some$0$³ -//│ > let tmp:scrut = x¹ -//│ > let x' = tmp:scrut⁰ -//│ > let tmp:scrut = x'⁰ -//│ > tmp:scrut is member:Left⁰^term:h2⁰(arg$Left$0$) and +//│ > a is member:Option⁰.Some^term:h2⁰(arg$Some$0$) and +//│ > let x = tmp:arg$Some$0$¹ +//│ > let x' = x⁰ +//│ > x' is member:Left⁰^term:h2⁰(arg$Left$0$) and //│ > let y = tmp:arg$Left$0$¹ -//│ > else y³ -//│ > $σ -//│ > $σ +//│ > else y¹ +//│ > let y = tmp:arg$Some$0$¹ +//│ > let y' = y² +//│ > y' is member:Right⁰^term:h2⁰(arg$Right$0$) and +//│ > let z = tmp:arg$Right$0$¹ +//│ > else z¹ +//│ > a is member:Option⁰.None^term:h2⁰ then 0 // :w fun h3(x, y, f, p) = diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls index febe2ccb32..b67bca66db 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls @@ -59,15 +59,11 @@ if x is 1 then "1" else "" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let scrut, scrut1, scrut2; -//│ set scrut = x⁰; -//│ match scrut +//│ match x⁰ //│ 0 => -//│ set scrut1 = y⁰; -//│ match scrut1 +//│ match y⁰ //│ 0 => -//│ set scrut2 = z⁰; -//│ match scrut2 +//│ match z⁰ //│ 0 => //│ return "000" //│ 1 => @@ -99,15 +95,11 @@ if x is 1 then "1-------------------------------------------------------------------------" else "-------------------------------------------------------------------------" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let scrut, scrut1, scrut2; -//│ set scrut = x⁰; -//│ match scrut +//│ match x⁰ //│ 0 => -//│ set scrut1 = y⁰; -//│ match scrut1 +//│ match y⁰ //│ 0 => -//│ set scrut2 = z⁰; -//│ match scrut2 +//│ match z⁰ //│ 0 => //│ return "000-------------------------------------------------------------------------" //│ 1 => @@ -138,15 +130,12 @@ let qqq = if x is 1 then "1" else "" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let scrut, scrut1, scrut2, tmp; -//│ set scrut = x⁰; -//│ match scrut +//│ let tmp; +//│ match x⁰ //│ 0 => -//│ set scrut1 = y⁰; -//│ match scrut1 +//│ match y⁰ //│ 0 => -//│ set scrut2 = z⁰; -//│ match scrut2 +//│ match z⁰ //│ 0 => //│ set tmp = "000"; //│ end @@ -185,15 +174,12 @@ print of if x is 1 then "1" else "" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let scrut, scrut1, scrut2, tmp; -//│ set scrut = x⁰; -//│ match scrut +//│ let tmp; +//│ match x⁰ //│ 0 => -//│ set scrut1 = y⁰; -//│ match scrut1 +//│ match y⁰ //│ 0 => -//│ set scrut2 = z⁰; -//│ match scrut2 +//│ match z⁰ //│ 0 => //│ set tmp = "000"; //│ return Predef⁰.print⁰(tmp) @@ -233,15 +219,11 @@ fun foo(x, y, z) = else "" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define foo⁰ as fun foo¹(x, y, z) { -//│ let scrut, scrut1, scrut2; -//│ set scrut = x; -//│ match scrut +//│ match x //│ 0 => -//│ set scrut1 = y; -//│ match scrut1 +//│ match y //│ 0 => -//│ set scrut2 = z; -//│ match scrut2 +//│ match z //│ 0 => //│ return "000" //│ 1 => @@ -277,66 +259,54 @@ print of if y is 2 then "_2_" else "___" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let scrut, scrut1, scrut2, scrut3, scrut4, scrut5, scrut6, tmp, tmp1, tmp2; -//│ set scrut = x⁰; -//│ block σ$x: -//│ block σ: -//│ match scrut +//│ let tmp; +//│ match x⁰ +//│ 0 => +//│ match y⁰ //│ 0 => -//│ set scrut1 = y⁰; -//│ block σ$x1: -//│ block σ1: -//│ match scrut1 -//│ 0 => -//│ set scrut2 = z⁰; -//│ match scrut2 -//│ 0 => -//│ set tmp1 = "000"; -//│ break σ$x1 -//│ else -//│ break σ1 -//│ end +//│ match z⁰ +//│ 0 => +//│ set tmp = "000"; +//│ return Predef⁰.print⁰(tmp) +//│ 1 => +//│ set tmp = "0_1"; +//│ return Predef⁰.print⁰(tmp) +//│ else +//│ set tmp = "___"; +//│ return Predef⁰.print⁰(tmp) +//│ end +//│ 1 => +//│ set tmp = "01_"; +//│ return Predef⁰.print⁰(tmp) +//│ else +//│ match z⁰ +//│ 1 => +//│ set tmp = "0_1"; +//│ return Predef⁰.print⁰(tmp) +//│ else +//│ match y⁰ +//│ 2 => +//│ set tmp = "_2_"; +//│ return Predef⁰.print⁰(tmp) //│ else -//│ break σ1 +//│ set tmp = "___"; +//│ return Predef⁰.print⁰(tmp) //│ end -//│ set scrut3 = y⁰; -//│ match scrut3 -//│ 1 => -//│ set tmp1 = "01_"; -//│ break σ$x1 -//│ else -//│ set scrut4 = z⁰; -//│ match scrut4 -//│ 1 => -//│ set tmp1 = "0_1"; -//│ break σ$x1 -//│ else -//│ break σ -//│ end -//│ end -//│ set tmp = tmp1; -//│ break σ$x +//│ end +//│ end +//│ 1 => +//│ set tmp = "1__"; +//│ return Predef⁰.print⁰(tmp) +//│ else +//│ match y⁰ +//│ 2 => +//│ set tmp = "_2_"; +//│ return Predef⁰.print⁰(tmp) //│ else -//│ break σ +//│ set tmp = "___"; +//│ return Predef⁰.print⁰(tmp) //│ end -//│ set scrut5 = x⁰; -//│ match scrut5 -//│ 1 => -//│ set tmp = "1__"; -//│ break σ$x -//│ else -//│ set scrut6 = y⁰; -//│ match scrut6 -//│ 2 => -//│ set tmp = "_2_"; -//│ break σ$x -//│ else -//│ set tmp = "___"; -//│ break σ$x -//│ end -//│ end -//│ set tmp2 = tmp; -//│ return Predef⁰.print⁰(tmp2) +//│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > 000 @@ -353,27 +323,18 @@ fun foo(x, y, z) = if else "___" //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x¹ -//│ > let-split σ = -//│ > let tmp:scrut = x¹ -//│ > tmp:scrut is 1 then "1__" -//│ > let tmp:scrut = y¹ -//│ > tmp:scrut is 2 then "_2_" +//│ > x is 0 and +//│ > y is 0 and +//│ > z is 0 then "000" +//│ > z is 1 then "0_1" +//│ > else "___" +//│ > y is 1 then "01_" +//│ > z is 1 then "0_1" +//│ > y is 2 then "_2_" //│ > else "___" -//│ > tmp:scrut is 0 and -//│ > let tmp:scrut = y¹ -//│ > let-split σ = -//│ > let tmp:scrut = y¹ -//│ > tmp:scrut is 1 then "01_" -//│ > let tmp:scrut = z¹ -//│ > tmp:scrut is 1 then "0_1" -//│ > $σ -//│ > tmp:scrut is 0 and -//│ > let tmp:scrut = z¹ -//│ > tmp:scrut is 0 then "000" -//│ > $σ -//│ > $σ -//│ > $σ +//│ > x is 1 then "1__" +//│ > y is 2 then "_2_" +//│ > else "___" fun expensive_call(str: Str) = Math.pow(2, str.length) @@ -390,15 +351,12 @@ fun foo(x, y, z) = if x is expensive_call(value) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define foo² as fun foo³(x, y, z) { -//│ let scrut, scrut1, scrut2, value; -//│ set scrut = x; -//│ match scrut +//│ let value; +//│ match x //│ 0 => -//│ set scrut1 = y; -//│ match scrut1 +//│ match y //│ 0 => -//│ set scrut2 = z; -//│ match scrut2 +//│ match z //│ 0 => //│ return "000" //│ 1 => @@ -437,17 +395,7 @@ fun foo(x, y, z) = if x is A then "Hello" else "Goodbye" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define foo⁴ as fun foo⁵(x, y, z) { -//│ let scrut; -//│ set scrut = x; -//│ match scrut -//│ A⁰ => -//│ return "Hello" -//│ else -//│ return "Goodbye" -//│ end -//│ }; -//│ end +//│ define foo⁴ as fun foo⁵(x, y, z) { match x A⁰ => return "Hello" else return "Goodbye" end }; end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :sir @@ -467,15 +415,11 @@ fun foo(x, y, z) = else "Goodbye" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define foo⁶ as fun foo⁷(x, y, z) { -//│ let scrut, scrut1, scrut2; -//│ set scrut = x; -//│ match scrut +//│ match x //│ A⁰ => -//│ set scrut2 = y; -//│ match scrut2 +//│ match y //│ B⁰ => -//│ set scrut1 = z; -//│ match scrut1 +//│ match z //│ C⁰ => //│ return "Hello" //│ else @@ -497,15 +441,11 @@ fun foo(x, y, z) = if x is A and y is B and z is C then "Hello" //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define foo⁸ as fun foo⁹(x, y, z) { -//│ let scrut, scrut1, scrut2; -//│ set scrut = x; -//│ match scrut +//│ match x //│ A⁰ => -//│ set scrut2 = y; -//│ match scrut2 +//│ match y //│ B⁰ => -//│ set scrut1 = z; -//│ match scrut1 +//│ match z //│ C⁰ => //│ return "Hello" //│ else @@ -545,7 +485,7 @@ let y = if true then 1 else 2 //│ else //│ set tmp = 2; //│ end -//│ set y² = tmp; +//│ set y¹ = tmp; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ y = 1 @@ -560,13 +500,11 @@ fun foo1(x, y, z) = if else print of 3 //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x² //│ > let-split σ = -//│ > let tmp:scrut = z² -//│ > tmp:scrut is member:A¹^term:foo1⁰ then member:Predef¹.print^term:foo1⁰(2) +//│ > z is member:A¹^term:foo1⁰ then member:Predef¹.print^term:foo1⁰(2) //│ > else member:Predef¹.print^term:foo1⁰(3) -//│ > tmp:scrut is member:A¹^term:foo1⁰ and -//│ > let tmp:scrut = member:pred⁰^term:foo1⁰(x²) +//│ > x is member:A¹^term:foo1⁰ and +//│ > let tmp:scrut = member:pred⁰^term:foo1⁰(x¹) //│ > tmp:scrut is true then member:Predef¹.print^term:foo1⁰(1) //│ > $σ //│ > $σ @@ -580,20 +518,17 @@ fun foo2(x, y, z) = if else print of 4 //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x³ //│ > let-split σ = -//│ > let tmp:scrut = y³ //│ > let-split σ = -//│ > let tmp:scrut = z³ -//│ > tmp:scrut is member:A¹^term:foo2⁰ then member:Predef¹.print^term:foo2⁰(3) +//│ > z is member:A¹^term:foo2⁰ then member:Predef¹.print^term:foo2⁰(3) //│ > else member:Predef¹.print^term:foo2⁰(4) -//│ > tmp:scrut is member:A¹^term:foo2⁰ and -//│ > let tmp:scrut = member:pred⁰^term:foo2⁰(y³) +//│ > y is member:A¹^term:foo2⁰ and +//│ > let tmp:scrut = member:pred⁰^term:foo2⁰(y²) //│ > tmp:scrut is true then member:Predef¹.print^term:foo2⁰(2) //│ > $σ //│ > $σ -//│ > tmp:scrut is member:A¹^term:foo2⁰ and -//│ > let tmp:scrut = member:pred⁰^term:foo2⁰(x³) +//│ > x is member:A¹^term:foo2⁰ and +//│ > let tmp:scrut = member:pred⁰^term:foo2⁰(x²) //│ > tmp:scrut is true then member:Predef¹.print^term:foo2⁰(1) //│ > $σ //│ > $σ diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/DeduplicationWhile.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/DeduplicationWhile.mls index 870974b442..216670c0d3 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/DeduplicationWhile.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/DeduplicationWhile.mls @@ -71,31 +71,30 @@ while x > 0 and x === //│ @private //│ define lambda as fun lambda¹(x) { //│ loop lbl: -//│ let scrut, scrut1, scrut2, scrut3, scrut4, scrut5; +//│ let scrut, scrut1, scrut2, scrut3, scrut4; //│ set scrut = >⁰(x, 0); //│ match scrut //│ true => -//│ set scrut1 = x; -//│ set scrut2 = ===⁰(scrut1, 1); -//│ match scrut2 +//│ set scrut1 = ===⁰(x, 1); +//│ match scrut1 //│ true => //│ do Predef⁰.print⁰("A"); //│ continue lbl //│ else -//│ set scrut3 = ===⁰(scrut1, 2); -//│ match scrut3 +//│ set scrut2 = ===⁰(x, 2); +//│ match scrut2 //│ true => //│ do Predef⁰.print⁰("A"); //│ continue lbl //│ else -//│ set scrut4 = ===⁰(scrut1, 3); -//│ match scrut4 +//│ set scrut3 = ===⁰(x, 3); +//│ match scrut3 //│ true => //│ do Predef⁰.print⁰("B"); //│ continue lbl //│ else -//│ set scrut5 = ===⁰(scrut1, 4); -//│ match scrut5 +//│ set scrut4 = ===⁰(x, 4); +//│ match scrut4 //│ true => //│ do Predef⁰.print⁰("B"); //│ continue lbl @@ -124,21 +123,20 @@ while x > 0 and x === //│ @private //│ define lambda as fun lambda²(x) { //│ loop lbl: -//│ let scrut, scrut1, scrut2, scrut3, scrut4, scrut5, scrut6, tmp; +//│ let scrut, scrut1, scrut2, scrut3, scrut4, scrut5, tmp; //│ set scrut = >⁰(x, 0); //│ match scrut //│ true => -//│ set scrut1 = x; -//│ set scrut2 = ===⁰(scrut1, 1); +//│ set scrut1 = ===⁰(x, 1); //│ block σ$x: //│ block σ: -//│ match scrut2 +//│ match scrut1 //│ true => -//│ set scrut4 = false; -//│ match scrut4 +//│ set scrut3 = false; +//│ match scrut3 //│ true => -//│ set scrut3 = false; -//│ match scrut3 +//│ set scrut2 = false; +//│ match scrut2 //│ true => //│ set tmp = Predef⁰.print⁰("A"); //│ break σ$x @@ -151,11 +149,11 @@ while x > 0 and x === //│ else //│ break σ //│ end -//│ set scrut5 = ===⁰(scrut1, 2); -//│ match scrut5 +//│ set scrut4 = ===⁰(x, 2); +//│ match scrut4 //│ true => -//│ set scrut6 = false; -//│ match scrut6 +//│ set scrut5 = false; +//│ match scrut5 //│ true => //│ set tmp = Predef⁰.print⁰("B"); //│ break σ$x diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls index fda62c0ce0..c7cd8af007 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls @@ -20,15 +20,10 @@ if x then 0 y then 0 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut, scrut1; -//│ scrut = x; -//│ if (scrut === true) { -//│ return 0 -//│ } -//│ scrut1 = y; -//│ if (scrut1 === true) { +//│ if (x === true) { //│ return 0 //│ } +//│ if (y === true) { return 0 } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 0 @@ -39,15 +34,10 @@ if x then 0 y then 1 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut2, scrut3; -//│ scrut2 = x; -//│ if (scrut2 === true) { +//│ if (x === true) { //│ return 0 //│ } -//│ scrut3 = y; -//│ if (scrut3 === true) { -//│ return 1 -//│ } +//│ if (y === true) { return 1 } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 0 @@ -63,35 +53,28 @@ if y is [b] then let x = 1 in x + b //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x⁰ -//│ > let-split σ = -//│ > let tmp:scrut = y⁰ -//│ > tmp:scrut is []=1 and -//│ > let tmp:element0$ = ((tmp:runtime⁰.)Tuple‹module:Tuple›.)get‹term:Tuple/get›(tmp:scrut⁰, 0) -//│ > let b = tmp:element0$⁰ -//│ > else -//│ > let x -//│ > x = 1 -//│ > builtin:+⁰(x¹, b⁰) -//│ > tmp:scrut is true and -//│ > let tmp:scrut = z⁰ -//│ > tmp:scrut is true then 1 +//│ > let-split σ = y is []=1 and +//│ > let tmp:element0$ = ((tmp:runtime⁰.)Tuple‹module:Tuple›.)get‹term:Tuple/get›(y⁰, 0) +//│ > let b = tmp:element0$⁰ +//│ > else +//│ > let x +//│ > x = 1 +//│ > builtin:+⁰(x⁰, b⁰) +//│ > x is true and +//│ > z is true then 1 //│ > $σ //│ > $σ //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ let scrut4, scrut5, scrut6, element0$, tmp; -//│ scrut4 = x; +//│ let element0$, tmp; //│ σ$x: { -//│ if (scrut4 === true) { -//│ scrut5 = z; -//│ if (scrut5 === true) { +//│ if (x === true) { +//│ if (z === true) { //│ tmp = 1; //│ break σ$x //│ } //│ } -//│ scrut6 = y; -//│ if (runtime.Tuple.isArrayLike(scrut6) && scrut6.length === 1) { -//│ element0$ = runtime.checkCall(runtime.Tuple.get(scrut6, 0)); +//│ if (runtime.Tuple.isArrayLike(y) && y.length === 1) { +//│ element0$ = runtime.checkCall(runtime.Tuple.get(y, 0)); //│ tmp = 1 + element0$; //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ } diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls index 765608e89c..a2736e4939 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls @@ -6,9 +6,7 @@ fun test(x) = if x is Int then "Int" 0 then "Zero" //│ Normalized: -//│ > ‹if|while› -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is member:Int⁰^term:test⁰ then "Int" +//│ > ‹if|while› x is member:Int⁰^term:test⁰ then "Int" test(0) //│ = "Int" @@ -19,11 +17,10 @@ fun test(x, p) = if x is else "qax" //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x¹ -//│ > tmp:scrut is refined member:Int⁰^term:test¹ and -//│ > let tmp:scrut = p⁰(x¹) +//│ > x is refined member:Int⁰^term:test¹ and +//│ > let tmp:scrut = p⁰(x⁰) //│ > tmp:scrut is true then "foo" -//│ > tmp:scrut is 0 then "bar" +//│ > x is 0 then "bar" //│ > else "qax" //│ > else "qax" @@ -33,11 +30,10 @@ fun test(x, p) = if x is else "qax" //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x² -//│ > tmp:scrut is refined member:Str⁰^term:test² and -//│ > let tmp:scrut = p¹(x²) +//│ > x is refined member:Str⁰^term:test² and +//│ > let tmp:scrut = p¹(x¹) //│ > tmp:scrut is true then "foo" -//│ > tmp:scrut is "lol" then "bar" +//│ > x is "lol" then "bar" //│ > else "qax" //│ > else "qax" @@ -60,12 +56,11 @@ fun test(x, p) = if x is else "other" //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x³ -//│ > tmp:scrut is refined member:Num⁰^term:test³ and -//│ > let tmp:scrut = p²(x³) +//│ > x is refined member:Num⁰^term:test³ and +//│ > let tmp:scrut = p²(x²) //│ > tmp:scrut is true then "great" -//│ > tmp:scrut is 2.71828 then "E" -//│ > tmp:scrut is 3.14159 then "PI" +//│ > x is 2.71828 then "E" +//│ > x is 3.14159 then "PI" //│ > else "other" //│ > else "other" @@ -74,13 +69,11 @@ fun test(x, p) = if x is true then "false" false then "true" //│ Normalized: -//│ > ‹if|while› -//│ > let tmp:scrut = x⁴ -//│ > tmp:scrut is refined member:Bool⁰^term:test⁴ and -//│ > let tmp:scrut = p³(x⁴) -//│ > tmp:scrut is true then "great" -//│ > tmp:scrut is true then "false" -//│ > tmp:scrut is false then "true" +//│ > ‹if|while› x is refined member:Bool⁰^term:test⁴ and +//│ > let tmp:scrut = p³(x³) +//│ > tmp:scrut is true then "great" +//│ > x is true then "false" +//│ > x is false then "true" fun test(x, p) = if x is Object and p(x) then "great" @@ -89,15 +82,14 @@ fun test(x, p) = if x is false then "true" //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x⁵ -//│ > tmp:scrut is member:Object⁰^term:test⁵ and -//│ > let tmp:scrut = p⁴(x⁵) +//│ > x is member:Object⁰^term:test⁵ and +//│ > let tmp:scrut = p⁴(x⁴) //│ > tmp:scrut is true then "great" -//│ > tmp:scrut is refined member:Bool⁰^term:test⁵ and -//│ > let tmp:scrut = p⁴(x⁵) +//│ > x is refined member:Bool⁰^term:test⁵ and +//│ > let tmp:scrut = p⁴(x⁴) //│ > tmp:scrut is true then "great, again" -//│ > tmp:scrut is true then "false" -//│ > tmp:scrut is false then "true" +//│ > x is true then "false" +//│ > x is false then "true" object Foo @@ -108,10 +100,9 @@ fun f(arg) = if Object then String(arg) //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = arg⁰ -//│ > tmp:scrut is undefined then "undefined" -//│ > tmp:scrut is null then "null" -//│ > tmp:scrut is member:Object⁰^term:f⁰ then member:String⁰^term:f⁰(arg⁰) +//│ > arg is undefined then "undefined" +//│ > arg is null then "null" +//│ > arg is member:Object⁰^term:f⁰ then member:String⁰^term:f⁰(arg⁰) f of undefined //│ = "undefined" @@ -129,10 +120,9 @@ fun f(arg) = if null then "null" //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = arg¹ -//│ > tmp:scrut is member:Object⁰^term:f¹ then member:String⁰^term:f¹(arg¹) -//│ > tmp:scrut is undefined then "undefined" -//│ > tmp:scrut is null then "null" +//│ > arg is member:Object⁰^term:f¹ then member:String⁰^term:f¹(arg¹) +//│ > arg is undefined then "undefined" +//│ > arg is null then "null" f of undefined //│ = "undefined" @@ -147,9 +137,7 @@ fun f(arg) = if arg is Object then String(arg) Foo then "just Foo" //│ Normalized: -//│ > ‹if|while› -//│ > let tmp:scrut = arg² -//│ > tmp:scrut is member:Object⁰^term:f² then member:String⁰^term:f²(arg²) +//│ > ‹if|while› arg is member:Object⁰^term:f² then member:String⁰^term:f²(arg²) import "../../../mlscript-compile/Runtime.mls" @@ -157,33 +145,28 @@ fun f(arg) = if arg is Object then String(arg) Runtime.MatchSuccess then "just Foo" //│ Normalized: -//│ > ‹if|while› -//│ > let tmp:scrut = arg³ -//│ > tmp:scrut is member:Object⁰^term:f³ then member:String⁰^term:f³(arg³) +//│ > ‹if|while› arg is member:Object⁰^term:f³ then member:String⁰^term:f³(arg³) fun f(arg) = if arg is Foo then "just Foo" Object then String(arg) //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = arg⁴ -//│ > tmp:scrut is member:Foo⁰^term:f⁴ then "just Foo" -//│ > tmp:scrut is member:Object⁰^term:f⁴ then member:String⁰^term:f⁴(arg⁴) +//│ > arg is member:Foo⁰^term:f⁴ then "just Foo" +//│ > arg is member:Object⁰^term:f⁴ then member:String⁰^term:f⁴(arg⁴) fun f(arg) = if arg is Int then "Int" Object then String(arg) //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = arg⁵ -//│ > tmp:scrut is member:Int⁰^term:f⁵ then "Int" -//│ > tmp:scrut is member:Object⁰^term:f⁵ then member:String⁰^term:f⁵(arg⁵) +//│ > arg is member:Int⁰^term:f⁵ then "Int" +//│ > arg is member:Object⁰^term:f⁵ then member:String⁰^term:f⁵(arg⁵) fun f(arg) = if arg is Object then String(arg) Int then "Int" //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = arg⁶ -//│ > tmp:scrut is member:Object⁰^term:f⁶ then member:String⁰^term:f⁶(arg⁶) -//│ > tmp:scrut is member:Int⁰^term:f⁶ then "Int" +//│ > arg is member:Object⁰^term:f⁶ then member:String⁰^term:f⁶(arg⁶) +//│ > arg is member:Int⁰^term:f⁶ then "Int" diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/RecordImpliedByClass.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/RecordImpliedByClass.mls index d14c5b86b8..e704eedf0c 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/RecordImpliedByClass.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/RecordImpliedByClass.mls @@ -10,10 +10,8 @@ fun f(x) = else 0 //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is member:Foo⁰^term:f⁰ and -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is { a: tmp:field_a$, b: tmp:field_b$ } and +//│ > x is member:Foo⁰^term:f⁰ and +//│ > x is { a: tmp:field_a$, b: tmp:field_b$ } and //│ > let bv = tmp:field_b$⁰ //│ > let av = tmp:field_a$⁰ //│ > else builtin:+⁰(av⁰, bv⁰) diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/SpecializedSplitSharing.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/SpecializedSplitSharing.mls index b253e274c5..d8ddb29822 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/SpecializedSplitSharing.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/SpecializedSplitSharing.mls @@ -27,15 +27,10 @@ fun f1(x, y) = if else 3 //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is member:Cat⁰^term:f1⁰() and -//│ > let tmp:scrut = y⁰ -//│ > tmp:scrut is 0 then 1 -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is member:Dog⁰^term:f1⁰() then 2 +//│ > x is member:Cat⁰^term:f1⁰() and +//│ > y is 0 then 1 //│ > else 3 -//│ > let tmp:scrut = x⁰ -//│ > tmp:scrut is member:Dog⁰^term:f1⁰() then 2 +//│ > x is member:Dog⁰^term:f1⁰() then 2 //│ > else 3 // * Same shape but with an inner refining test on the `Dog()` branch — the @@ -47,19 +42,13 @@ fun f2(x, y, z) = if else 3 //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x¹ -//│ > let-split σ = -//│ > let tmp:scrut = x¹ -//│ > tmp:scrut is member:Dog⁰^term:f2⁰() and -//│ > let tmp:scrut = z⁰ -//│ > tmp:scrut is 0 then 2 -//│ > else 3 +//│ > x is member:Cat⁰^term:f2⁰() and +//│ > y is 0 then 1 //│ > else 3 -//│ > tmp:scrut is member:Cat⁰^term:f2⁰() and -//│ > let tmp:scrut = y¹ -//│ > tmp:scrut is 0 then 1 -//│ > $σ -//│ > $σ +//│ > x is member:Dog⁰^term:f2⁰() and +//│ > z is 0 then 2 +//│ > else 3 +//│ > else 3 // === Specialized split duplication — must remain === @@ -79,11 +68,9 @@ fun g1(x) = if x is S(0) then "zero" S(_) then "any" //│ Normalized: -//│ > ‹if|while› -//│ > let tmp:scrut = x² -//│ > tmp:scrut is member:S⁰^term:g1⁰(arg$S$0$) and -//│ > tmp:arg$S$0$ is 0 then "zero" -//│ > else "any" +//│ > ‹if|while› x is member:S⁰^term:g1⁰(arg$S$0$) and +//│ > tmp:arg$S$0$ is 0 then "zero" +//│ > else "any" // * Three branches on the same scrutinee with progressively coarser tests: // * each specialization produces a distinct simplified split. @@ -93,9 +80,7 @@ fun g2(x) = if x is S(1) then "one" S(_) then "other" //│ Normalized: -//│ > ‹if|while› -//│ > let tmp:scrut = x³ -//│ > tmp:scrut is member:S⁰^term:g2⁰(arg$S$0$) and -//│ > tmp:arg$S$0$ is 0 then "zero" -//│ > tmp:arg$S$0$ is 1 then "one" -//│ > else "other" +//│ > ‹if|while› x is member:S⁰^term:g2⁰(arg$S$0$) and +//│ > tmp:arg$S$0$ is 0 then "zero" +//│ > tmp:arg$S$0$ is 1 then "one" +//│ > else "other" diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledClassPatterns.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledClassPatterns.mls index 334671738f..ab27ca1643 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledClassPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/CompiledClassPatterns.mls @@ -170,8 +170,7 @@ fun compiledInner(module Inner: Outer.Inner, x) = if x is @compile Inner.Wrapped(3) then true else false //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define compiledInner⁰ as fun compiledInner¹(Inner, x) { -//│ let scrut, matcher__Wrappedʿ3ʾ$, matcher__3$, matchSuccess, lambda, lambda1; -//│ set scrut = x; +//│ let matcher__Wrappedʿ3ʾ$, matcher__3$, matchSuccess, lambda, lambda1; //│ define lambda as fun lambda⁵(input) { //│ let value, value1, p_0$, result1$, p_0$1, tmp, tmp1; //│ match input @@ -209,7 +208,7 @@ fun compiledInner(module Inner: Outer.Inner, x) = //│ end //│ }; //│ set matcher__3$ = lambda⁶; -//│ set matchSuccess = matcher__Wrappedʿ3ʾ$(scrut); +//│ set matchSuccess = matcher__Wrappedʿ3ʾ$(x); //│ match matchSuccess //│ true => //│ return true diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls index cd2605a06e..1ffa0c6818 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls @@ -13,7 +13,7 @@ x => if x is //│ ║ l.11: refined(None) then x //│ ╙── ^^^^^^^^^^^^^ //│ Normalized: -//│ > ‹if|while› let tmp:scrut = x⁰ +//│ > ‹if|while› // OK x => if x is @@ -22,7 +22,7 @@ x => if x is //│ ║ l.20: refined(Some) then x //│ ╙── ^^^^^^^^^^^^^ //│ Normalized: -//│ > ‹if|while› let tmp:scrut = x¹ +//│ > ‹if|while› // Should warn about the inconsistent refinement x => if x is @@ -32,18 +32,16 @@ x => if x is //│ ║ l.29: refined(None) then x //│ ╙── ^^^^^^^^^^^^^ //│ Normalized: -//│ > ‹if|while› -//│ > let tmp:scrut = x² -//│ > tmp:scrut is Some‹open› then x² +//│ > ‹if|while› x is Some‹open› then x⁰ x => if x is refined(None) then x refined(Some) then x //│ ╔══[COMPILATION ERROR] Cannot use this wildcard-open reference as a pattern. -//│ ║ l.40: refined(None) then x +//│ ║ l.38: refined(None) then x //│ ╙── ^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot use this wildcard-open reference as a pattern. -//│ ║ l.41: refined(Some) then x +//│ ║ l.39: refined(Some) then x //│ ╙── ^^^^^^^^^^^^^ //│ Normalized: -//│ > ‹if|while› let tmp:scrut = x³ +//│ > ‹if|while› diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls index 532a89807e..99fe0cd4f5 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls @@ -10,16 +10,14 @@ fun f(x) = _ then 1 //│ Split with nested patterns: //│ > if -//│ > let tmp:scrut = x⁰ //│ > let tmp:scrut = 1 -//│ > let tmp:scrut = member:Predef⁰.equals^term:f⁰(tmp:scrut⁰, builtin:+⁰(tmp:scrut¹, 2)) +//│ > let tmp:scrut = member:Predef⁰.equals^term:f⁰(x⁰, builtin:+⁰(tmp:scrut⁰, 2)) //│ > tmp:scrut is true then 0 //│ > else 1 //│ Expanded split with flattened patterns: //│ > ‹if|while› -//│ > let tmp:scrut = x⁰ //│ > let tmp:scrut = 1 -//│ > let tmp:scrut = member:Predef⁰.equals^term:f⁰(tmp:scrut⁰, builtin:+⁰(tmp:scrut¹, 2)) +//│ > let tmp:scrut = member:Predef⁰.equals^term:f⁰(x⁰, builtin:+⁰(tmp:scrut⁰, 2)) //│ > tmp:scrut is true then 0 //│ > else 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls index 59ba9d0b8a..eef6e08b8e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls @@ -94,7 +94,7 @@ fun foo(x) = x is Pair(a, b) | Int data class A[T](arg: T) let foo = x => x is A(_) -//│ foo = fun +//│ foo = fun foo :expect [false, true] [foo(0), foo(A(0))] diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls index 701dd7ccc0..019303aeb4 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls @@ -24,9 +24,8 @@ import "../../../mlscript-compile/Char.mls" fun foo(x) = x is Nullable(Char.Letter) //│ Normalized: //│ > ‹if|while› -//│ > let tmp:scrut = x⁰ //│ > let tmp:patternArgument0$ = member:Char⁰^term:foo⁰.Letter -//│ > let tmp:unapplyResult = (member:Nullable⁰^term:foo⁰.)unapply(tmp:patternArgument0$¹, tmp:scrut¹) +//│ > let tmp:unapplyResult = (member:Nullable⁰^term:foo⁰.)unapply(tmp:patternArgument0$¹, x⁰) //│ > tmp:unapplyResult is (tmp:runtime⁰.)MatchSuccess‹class:MatchSuccess›(output, bindings) then true //│ > else false diff --git a/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls b/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls index be964a5f2b..01c75c069a 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls @@ -99,7 +99,6 @@ c.Parent#x + (c is Parent) //│ (return //│ (local.get $this)))) //│ (func $entry (export "entry") (type $entry) (result (ref null any)) -//│ (local $scrut (ref null any)) //│ (local $matchRes (ref null any)) //│ (local $currentTypeInfo (ref null any)) //│ (local $targetTypeInfo (ref null any)) @@ -109,21 +108,19 @@ c.Parent#x + (c is Parent) //│ (call $Child_ctor //│ (ref.i31 //│ (i32.const 2)))) -//│ (local.set $scrut -//│ (global.get $c)) //│ (block $match //│ (local.set $matchRes //│ (ref.null any)) //│ (if //│ (ref.test (ref $Object) -//│ (local.get $scrut)) +//│ (global.get $c)) //│ (then //│ (if //│ (block (result i32) //│ (local.set $currentTypeInfo //│ (struct.get $Object $$typeinfo //│ (ref.cast (ref $Object) -//│ (local.get $scrut)))) +//│ (global.get $c)))) //│ (local.set $targetTypeInfo //│ (global.get $Parent_typeinfo)) //│ (local.set $typeInfoMatch @@ -269,7 +266,7 @@ class B class A extends B class B extends A //│ ╔══[COMPILATION ERROR] Inheritance cycles are not supported. -//│ ║ l.269: class A extends B +//│ ║ l.266: class A extends B //│ ╙── ^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/wasm/Matching.mls b/hkmc2/shared/src/test/mlscript/wasm/Matching.mls index c35167dcad..b1b366209d 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Matching.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Matching.mls @@ -280,9 +280,7 @@ fun isBool(v) = if v is Bool then 1 else 0 isInt(1) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define isInt⁰ as fun isInt¹(v: ?): ? { -//│ let scrut: ?; -//│ set scrut = v; -//│ match scrut +//│ match v //│ Int⁰ => //│ return 1 //│ else @@ -290,9 +288,7 @@ isInt(1) //│ end //│ }; //│ define isBool⁰ as fun isBool¹(v: ?): ? { -//│ let scrut: ?; -//│ set scrut = v; -//│ match scrut +//│ match v //│ Bool⁰ => //│ return 1 //│ else From 3d71d4d70c8e1c83a0b1aa8d829194931ca3d876 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 2 Oct 2026 16:59:33 +0800 Subject: [PATCH 183/228] Add old disclaimer back --- doc/new-resolution-scopes.md | 5 +++++ doc/new-resolution-suite-migration.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/doc/new-resolution-scopes.md b/doc/new-resolution-scopes.md index 0eb4ddb44d..e131704d1c 100644 --- a/doc/new-resolution-scopes.md +++ b/doc/new-resolution-scopes.md @@ -1,3 +1,8 @@ +NOTE: This document was written by Codex Astra and has not been deeply reviewed; +it is not meant to be official documentation and +is in fact likely to contain parts that are unintelligible to readers who lack sufficient context. + + # Lexical paths in resolution This note gives the scope calculus used by `Shape.scala`, the invariant needed diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 7e1e538b6b..2fd4125716 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -1,3 +1,8 @@ +NOTE: This document was written by Codex Astra and has not been deeply reviewed; +it is not meant to be official documentation and +is in fact likely to contain parts that are unintelligible to readers who lack sufficient context. + + # New-resolution suite migration This document must describe only the **current state** of the migration: active From ff957e82970c70b38497a3b3ce5ebefd1cd27a28 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 19:14:21 +0800 Subject: [PATCH 184/228] Defer member resolution errors until no target is found --- .../src/test/scala/hkmc2/CompilerTest.scala | 4 +- .../shared/src/main/scala/hkmc2/Config.scala | 6 + .../scala/hkmc2/semantics/NewResolver.scala | 17 +- .../hkmc2/semantics/NewResolverState.scala | 19 ++ .../main/scala/hkmc2/semantics/Shape.scala | 4 +- .../mlscript-compile/LenientSelections.mls | 7 + .../mlscript/basics/MemberProjections.mls | 12 +- hkmc2/shared/src/test/mlscript/newres/.mls | 1 + .../src/test/mlscript/newres/loose/.mls | 1 + .../test/mlscript/newres/loose/Leniency.mls | 218 ++++++++++++++++++ 10 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript-compile/LenientSelections.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/loose/Leniency.mls diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index 12dc9e4eca..b5727908a1 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -471,7 +471,7 @@ class CompilerTest extends AnyFunSuite: assert(errors.exists(_.allMsgs.exists(_._1.show.contains("Type parameter 'A'")))) assert(!errors.exists(_.allMsgs.exists(_._1.show.contains("exposed by this compilation unit")))) - test("non-strict files still check their exported interfaces"): + test("eager resolution checks exported interfaces in non-strict files"): val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) given DebugPrinter = new DebugPrinter @@ -479,7 +479,7 @@ class CompilerTest extends AnyFunSuite: override def doTrace = false val errors = scala.collection.mutable.ArrayBuffer.empty[Diagnostic] given Raise = errors += _ - fs.write("/Unsafe.mls", """#lang(0.3.x, strictResolution: false) + fs.write("/Unsafe.mls", """#lang(0.3.x, strictResolution: false, eagerResolution: true) |module Unsafe with | fun foo(x) = x.a |Unsafe.foo({a: 1}) diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index e9502cde05..290ad2d35d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -96,6 +96,7 @@ object Config: allowUnresolvedAccesses: Bool, useNewResolution: Bool, strictResolution: Bool, // Reject multiple resolved selection targets as well as missing targets + eagerResolution: Bool, // Reject selections whose prefixes have shapes that do not support them, even though the selection might have other valid targets typeCheck: Opt[TypeChecking], )(val versionName: Str) @@ -105,6 +106,7 @@ object Config: typeCheck = N, useNewResolution = false, strictResolution = false, + eagerResolution = false, allowUnresolvedAccesses = true, )( versionName = "0.2.x", @@ -114,6 +116,7 @@ object Config: typeCheck = N, useNewResolution = true, strictResolution = false, + eagerResolution = false, allowUnresolvedAccesses = false, )( versionName = "0.3.x", @@ -413,6 +416,9 @@ object ConfigParser: case NamedArg("strictResolution", value) => parsedLanguageModifier(value)(parseBool): v => lang => lang.copy(strictResolution = v)(lang.versionName) + case NamedArg("eagerResolution", value) => + parsedLanguageModifier(value)(parseBool): v => + lang => lang.copy(eagerResolution = v)(lang.versionName) case NamedArg("typeCheck", value) => parsedLanguageModifier(value)(tree => parseOpt(tree)(parseTypeChecking)): v => lang => lang.copy(typeCheck = v)(lang.versionName) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 93378216e4..59557afc5d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1199,8 +1199,10 @@ class NewResolver: // selecting it does not inspect an instance field or method body. fromBMSAt(member, flow, Nil, listener, source, selected, receiver, specialization) - def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])])(using rs: NewResolverState): Unit = rs.report: + private def resolutionError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])]): ErrorReport = ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) + def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])])(using rs: NewResolverState): Unit = + rs.report(resolutionError(src, msgs)) def registerTypeParameters(owner: AnyDefinitionSymbol, params: Ls[VarSymbol])(using State, NewResolverState): Unit = if params.nonEmpty then @@ -2520,13 +2522,18 @@ class NewResolver: case NoShape => () private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, provenance: ShapeProvenance)(using NewResolverState): Unit = if !rstate.hasError(host) then - rstate.markError(host) val message = reason match case MemberLookup.Uncertainty.ValueShape => msg"Cannot resolve member '$name' of a value with unknown shape." case MemberLookup.Uncertainty.RecordOverwrite => msg"Cannot resolve member '$name' across a computed key or unknown record spread." - resolError(host, provenance.diagnostic(message, N)) + host match + case sel: NewSel => + rstate.missingMember(sel, config.language.eagerResolution): + resolutionError(sel, provenance.diagnostic(message, N)) + case _ => + rstate.markError(host) + resolError(host, provenance.diagnostic(message, N)) def unresolvedRef(ref: UnresolvedRef)(using NewResolverState): Unit = ref.prefixes.foreach: prefix => @@ -2646,8 +2653,8 @@ class NewResolver: rstate.recordResolution(sel, sel.hasDynamicTarget)(sel.hasDynamicTarget = true) publishDynamic(sel, marks) case MemberLookup.Missing if rstate.canResolve(sel) => - rstate.markError(sel) - resolError(sel, diagnostic(msg"$description does not contain member '${sel.id.name}'")) + rstate.missingMember(sel, config.language.eagerResolution): + resolutionError(sel, diagnostic(msg"$description does not contain member '${sel.id.name}'")) case MemberLookup.Unknown(reason, provenance) => unknownMember(sel, sel.id.name, reason, provenance) // Later activations still transport field values through the compiled // selection, but cannot choose a different member for that old syntax. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index dfe526ad7e..c2711cda6c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -132,6 +132,20 @@ final class NewResolverState private ( private val completedNodes = mutable.Set.empty[Identity[Publisher[?]]] + // A receiver shape can lack a member that another shape supplies later in + // inference. Keep its diagnostic lazy so successful selections do not expand + // provenance paths. Only this block's owned selections are queued; imported + // and previously completed selections can be checked against their fixed targets. + private val missingMembers = mutable.ListBuffer.empty[(NewSel, () => Diagnostic)] + private def hasSelectionTarget(selection: NewSel): Bool = + selection.resolvedTargets.nonEmpty || selection.hasDynamicTarget || selection.tupleIndex.nonEmpty + def missingMember(selection: NewSel, eager: Bool)(diagnostic: => Diagnostic): Unit = + if eager || !canResolve(selection) then + if eager || !hasSelectionTarget(selection) then + markError(selection) + report(diagnostic) + else root.missingMembers += ((selection, () => diagnostic)) + /** Seal the decisions and original host data read by erasure/lowering. The * inference graph keeps private, live host data and all its listeners, so * later calls can still transport arguments and results through these nodes. @@ -179,6 +193,11 @@ final class NewResolverState private ( case _ => () statement.subStatements.foreach(visit) visit(block) + root.missingMembers.foreach: (selection, diagnostic) => + if !hasSelectionTarget(selection) then + markError(selection) + report(diagnostic()) + root.missingMembers.clear() root.pending.foreach: (key, value) => if !key.value.isInstanceOf[Symbol] && root.completedNodes.add(key) then value.completed = true root.pending.clear() diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 6ef5821767..b6cdac906a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -765,8 +765,8 @@ object ShapeProvenance: val empty = ShapeProvenance(Nil) /** An unknown input or the element of an opaque or widened spread can be any - * value. Keep this alternative in the flow graph so known candidates cannot - * silently make an unresolved operation appear to have a static target. + * value. Retain this alternative to explain unresolved operations and let eager + * member resolution reject unknown prefixes even when other candidates have targets. * Provenance is outside case-class equality: another diagnostic witness must * not turn the same unknown into a new inference candidate. */ diff --git a/hkmc2/shared/src/test/mlscript-compile/LenientSelections.mls b/hkmc2/shared/src/test/mlscript-compile/LenientSelections.mls new file mode 100644 index 0000000000..c3ffa714f9 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/LenientSelections.mls @@ -0,0 +1,7 @@ +#lang(0.3.x) + +// The local call supplies a target even though exported parameters also admit +// unknown inputs. Importers must preserve that target and its runtime access. +module LenientSelections with + fun read(x, enabled) = if enabled then x.value else 0 + val observed = read({value: 7}, true) diff --git a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls index efeea33267..2af6a31173 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls @@ -89,18 +89,18 @@ foo.Foo#m() :e :re foo.M.Foo::m() -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.91: foo.M.Foo::m() -//│ ║ ^^^^^ -//│ ╟── Instance of class 'Foo' does not contain member 'M' -//│ ║ l.11: let foo = M.Foo(123) -//│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.91: foo.M.Foo::m() //│ ║ ^^^^^ //│ ╟── Function literal expected 1 argument, but got 0 //│ ║ l.91: foo.M.Foo::m() //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.91: foo.M.Foo::m() +//│ ║ ^^^^^ +//│ ╟── Instance of class 'Foo' does not contain member 'M' +//│ ║ l.11: let foo = M.Foo(123) +//│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot resolve the projection class //│ ║ l.91: foo.M.Foo::m() //│ ╙── ^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/.mls b/hkmc2/shared/src/test/mlscript/newres/.mls index 6525534110..f246a84b72 100644 --- a/hkmc2/shared/src/test/mlscript/newres/.mls +++ b/hkmc2/shared/src/test/mlscript/newres/.mls @@ -1,3 +1,4 @@ :.. :js #lang(strictResolution: true) +#lang(eagerResolution: true) diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/.mls b/hkmc2/shared/src/test/mlscript/newres/loose/.mls index f84908e26a..7e0f4b5167 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/.mls @@ -1,2 +1,3 @@ :.. #lang(strictResolution: false) +#lang(eagerResolution: false) diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Leniency.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Leniency.mls new file mode 100644 index 0000000000..8d32688075 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Leniency.mls @@ -0,0 +1,218 @@ +:. + + +module M1 { val u = 1 } +module M2 { val v = 2 } + + +fun foo(x: M1 | M2) = x.u + +:expect [1, undefined] +fun bar(x, flag) = if flag then x.u else 0 +tuple of + bar(M1, true) + bar(M2, true) +//│ = [1, undefined] + + +// A miss can arrive before the candidate that resolves the selection. +:expect [0, 1] +fun later(x, flag) = if flag then x.u else 0 +[later(M2, false), later(M1, true)] +//│ = [0, 1] + +// Forward declarations can supply the target after the selection is visited. +:expect 3 +fun forward(x: Before | After) = x.u +module Before { val v = 0 } +module After { val u = 3 } +forward(After) +//│ = 3 + +// When no candidate supplies the member, retain both annotation witnesses. +:e +fun missing(x: M1 | M2) = x.absent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.34: fun missing(x: M1 | M2) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Value of type 'M1' does not contain member 'absent' +//│ ║ l.34: fun missing(x: M1 | M2) = x.absent +//│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.34: fun missing(x: M1 | M2) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Value of type 'M2' does not contain member 'absent' +//│ ║ l.34: fun missing(x: M1 | M2) = x.absent +//│ ╙── ^^^^^^^ + +// Indexed and dynamic selections have targets without nominal member symbols. +:expect 4 +let tuples = if hide(true) then [4] else [] +tuples.0 +//│ = 4 +//│ tuples = [4] + +:expect 5 +let dynamic = if hide(true) then ({value: 5} as dyn) else {other: 0} +dynamic.value +//│ = 5 +//│ dynamic = {value: 5} + +// Deferred misses must still be reported for a wholly unresolved record access. +:e +let records = if hide(true) then {left: 1} else {right: 2} +records.absent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.64: records.absent +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'absent' +//│ ║ l.63: let records = if hide(true) then {left: 1} else {right: 2} +//│ ╙── ^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.64: records.absent +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'absent' +//│ ║ l.63: let records = if hide(true) then {left: 1} else {right: 2} +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ records = {left: 1} + +// A successful selection in the next worksheet block must not replay old misses. +:expect 1 +foo(M1) +//│ = 1 + +// Resetting the language preset enables leniency without an explicit override. +:expect 1 +#lang(0.3.x) +fun defaultLeniency(x: M2 | M1) = x.u +defaultLeniency(M1) +//│ = 1 + +// Eager checking rejects a missing alternative even with a resolved target. +:e +#lang(eagerResolution: true) +fun eager(x: M1 | M2) = x.u +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.95: fun eager(x: M1 | M2) = x.u +//│ ║ ^^^ +//│ ╟── Value of type 'M2' does not contain member 'u' +//│ ║ l.95: fun eager(x: M1 | M2) = x.u +//│ ╙── ^^^^^^^ + +// Requiring a unique target is independent of requiring every shape to match. +:expect 1 +#lang(eagerResolution: false, strictResolution: true) +fun unique(x: M1 | M2) = x.u +unique(M1) +//│ = 1 + +:e +module M3 { val u = 3 } +fun ambiguous(x: M1 | M2 | M3) = x.u +//│ ╔══[COMPILATION ERROR] This selection of member 'u' is ambiguous, as it has multiple resolved targets +//│ ║ l.112: fun ambiguous(x: M1 | M2 | M3) = x.u +//│ ║ ^^^ +//│ ╟── target: value 'u' in module 'M3' +//│ ║ l.111: module M3 { val u = 3 } +//│ ║ ^ +//│ ╟── target: value 'u' in module 'M1' +//│ ║ l.4: module M1 { val u = 1 } +//│ ╙── ^ + + +// Unknown inputs are tolerated when another input resolves the selection. +:expect [0, 1] +#lang(0.3.x) +fun withUnknown(x, flag) = if flag then x.u else 0 +[withUnknown(M2 as Any, false), withUnknown(M1, true)] +//│ = [0, 1] + +// Later calls use the completed selection's target without replaying its misses. +:expect 0 +withUnknown(M2 as Any, false) +//│ = 0 + +// Unresolved unknown inputs keep the path back to the annotation hiding their shape. +:e +private fun discard(f: Function) = () +discard(x => x.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.139: discard(x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.139: discard(x => x.value) +//│ ║ ^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.138: private fun discard(f: Function) = () +//│ ║ ^^^^^^^^ +//│ ╙── The 'Function' type does not specify parameter types. + +// Preserve both known misses and unknown provenance when neither supplies a target. +:e +fun noCandidate(x) = x.absent +[noCandidate(M2 as Any), noCandidate(M1)] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.153: fun noCandidate(x) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Cannot resolve member 'absent' of a value with unknown shape. +//│ ║ l.154: [noCandidate(M2 as Any), noCandidate(M1)] +//│ ║ ^^^ +//│ ╟── Type 'Any' is declared without a member interface. +//│ ║ in ../../decls/Prelude.mls +//│ ║ l.2: declare type Any +//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.153: fun noCandidate(x) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Module 'M1' does not contain member 'absent' +//│ ║ l.4: module M1 { val u = 1 } +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A computed key is another source of uncertainty about the selected member. +:expect 6 +let key = hide("value") +let computed = if hide(true) then {value: 6} else {(key): 0} +computed.value +//│ = 6 +//│ computed = {value: 6} +//│ key = "value" + +:e +{(key): 0}.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.183: {(key): 0}.value +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' across a computed key or unknown record spread. +//│ ╟── This computed key can overwrite the selected member. +//│ ║ l.183: {(key): 0}.value +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +#lang(eagerResolution: true) +let eagerUnknown = if hide(true) then M1 else (M2 as Any) +eagerUnknown.u +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.196: eagerUnknown.u +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'u' of a value with unknown shape. +//│ ║ l.195: let eagerUnknown = if hide(true) then M1 else (M2 as Any) +//│ ║ ^^^ +//│ ╟── Type 'Any' is declared without a member interface. +//│ ║ in ../../decls/Prelude.mls +//│ ║ l.2: declare type Any +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ eagerUnknown = class M1 { u: 1 } + + +// Compiled selections retain their targets when imports supply new receiver shapes. +:expect [7, 8, 0] +#lang(0.3.x) +import "../../../mlscript-compile/LenientSelections.mls" +[LenientSelections.observed, + LenientSelections.read({value: 8}, true), + LenientSelections.read(M2 as Any, false)] +//│ = [7, 8, 0] From f00a5fa6aab0277d987b13ca2b67b7ca61f54588 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 20:02:17 +0800 Subject: [PATCH 185/228] Preserve public-interface checks and combine selection diagnostics --- .../src/test/scala/hkmc2/CompilerTest.scala | 4 +- .../src/test/scala/hkmc2/PublisherTest.scala | 6 +- .../shared/src/main/scala/hkmc2/Config.scala | 1 + .../hkmc2/semantics/InterfaceExposure.scala | 6 +- .../scala/hkmc2/semantics/NewResolver.scala | 92 +++--- .../hkmc2/semantics/NewResolverState.scala | 37 ++- .../main/scala/hkmc2/semantics/Shape.scala | 35 +-- .../mlscript-compile/LenientSelections.mls | 10 +- .../mlscript/newres/ConstructorInstances.mls | 22 +- .../mlscript/newres/InferenceHoleScopes.mls | 15 +- .../src/test/mlscript/newres/Records.mls | 52 ++-- .../src/test/mlscript/newres/ScopePaths.mls | 6 - .../src/test/mlscript/newres/SpreadCalls.mls | 34 ++- .../test/mlscript/newres/loose/Leniency.mls | 56 ++-- .../newres/loose/PublicInterfaces.mls | 265 ++++++++++++++++++ .../newres/loose/SelectionDiagnostics.mls | 170 +++++++++++ .../test/mlscript/newres/loose/Targets.mls | 15 +- 17 files changed, 659 insertions(+), 167 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/loose/PublicInterfaces.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/loose/SelectionDiagnostics.mls diff --git a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala index b5727908a1..12dc9e4eca 100644 --- a/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala +++ b/hkmc2/js/src/test/scala/hkmc2/CompilerTest.scala @@ -471,7 +471,7 @@ class CompilerTest extends AnyFunSuite: assert(errors.exists(_.allMsgs.exists(_._1.show.contains("Type parameter 'A'")))) assert(!errors.exists(_.allMsgs.exists(_._1.show.contains("exposed by this compilation unit")))) - test("eager resolution checks exported interfaces in non-strict files"): + test("non-strict files still check their exported interfaces"): val fs = new InMemoryFileSystem(loadStandardLibrary()) given cctx: CompilerCtx = CompilerCtx.fresh(fs, paths, Config.default(io.Path("/"))) given DebugPrinter = new DebugPrinter @@ -479,7 +479,7 @@ class CompilerTest extends AnyFunSuite: override def doTrace = false val errors = scala.collection.mutable.ArrayBuffer.empty[Diagnostic] given Raise = errors += _ - fs.write("/Unsafe.mls", """#lang(0.3.x, strictResolution: false, eagerResolution: true) + fs.write("/Unsafe.mls", """#lang(0.3.x, strictResolution: false) |module Unsafe with | fun foo(x) = x.a |Unsafe.foo({a: 1}) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala index 88d80a8c76..8e17022e6a 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/PublisherTest.scala @@ -175,10 +175,10 @@ class PublisherTest extends AnyFunSuite: Nil } val source = Term.UnitVal() - val first = UnknownValueShape(source)(provenance) - val second = UnknownValueShape(source)(provenance) + val first = UnknownValueShape(source, fromPublicInterface = false)(provenance) + val second = UnknownValueShape(source, fromPublicInterface = false)(provenance) assert(Set(first, second).size == 1) - assert(RecordShape.Unknown(source)(provenance) == RecordShape.Unknown(source)(provenance)) + assert(RecordShape.Unknown(first) == RecordShape.Unknown(second)) assert(evaluated == 0) first.provenance.diagnostic(msg"Operation failed", None) first.provenance.diagnostic(msg"Operation failed", None) diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index 290ad2d35d..b3d1ef2b4a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -96,6 +96,7 @@ object Config: allowUnresolvedAccesses: Bool, useNewResolution: Bool, strictResolution: Bool, // Reject multiple resolved selection targets as well as missing targets + // Unknown inputs admitted by a public interface are checked regardless of this flag. eagerResolution: Bool, // Reject selections whose prefixes have shapes that do not support them, even though the selection might have other valid targets typeCheck: Opt[TypeChecking], )(val versionName: Str) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index bff048a2da..45b0343644 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -76,7 +76,7 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) private def parameter(param: Param, marks: Ls[Marks], path: Path)(using NewResolverState): Unit = val ref = SimpleRef(param.sym)(param.sym.id) - val unknown = UnknownValueShape(ref)(path.via( + val unknown = UnknownValueShape(ref, fromPublicInterface = true)(path.via( msg"Parameter '${param.sym.nme}' admits values of unknown shape." -> param.toLoc)) resolver.parameterSignature(param) match case N => resolver.constrainParameter(param, unknown.enter(marks), marks) @@ -89,7 +89,7 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) params.params.foreach(parameter(_, marks, path)) params.restParam.foreach: rest => val ref = SimpleRef(rest.sym)(rest.sym.id) - val unknown = UnknownValueShape(ref)(path.via( + val unknown = UnknownValueShape(ref, fromPublicInterface = true)(path.via( msg"Rest parameter '${rest.sym.nme}' admits elements of unknown shape." -> rest.toLoc)) val tuple = TupleShape(ref, TupleShape.Unknown(ref, TupleShape.ValueField(unknown, Nil)) :: Nil)(resolver) resolver.constrainParameter(rest, tuple.enter(marks), marks) @@ -168,7 +168,7 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) case callable: CallableTypeShape => // Written structure constrains external values; missing output parts // still admit unknown shapes even when local callers supplied evidence. - val unknown = UnknownValueShape(callable.source)(path) + val unknown = UnknownValueShape(callable.source, fromPublicInterface = true)(path) callable.paramLists.foreach: params => (params.params.flatten ::: params.rest.toList).foreach: input => unknown.enter(marks) match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 59557afc5d..01ed999924 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -939,39 +939,43 @@ class NewResolver: val host = new TermShapeHost typeViews(tpe) = host host.listen(observe) - def follow(current: DeclaredType, args: TypeApplication, aliases: Set[TypeResolution], publish: Listener)(using NewResolverState): Unit = + def follow(current: DeclaredType, args: TypeApplication, aliases: Set[TypeResolution], source: Term, + publish: Listener)(using NewResolverState): Unit = current.resolution.listen: shape => def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = typeBindings(current, args, params) - def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), noTypeArguments, aliases, publish) + def next(res: TypeResolution)(using NewResolverState): Unit = + follow(declaredType(res, current), noTypeArguments, aliases, res.source, publish) // Each type can expose several union alternatives. Form products // within a conjunction, never across separate union clauses. + // Each component is also its own diagnostic witness. Aliases and + // applications preserve that witness while following their definition. def conjunction(remaining: Ls[DeclaredType], values: Ls[TermShape])(using NewResolverState): Unit = remaining match - case head :: tail => follow(head, noTypeArguments, aliases, + case head :: tail => follow(head, noTypeArguments, aliases, head.resolution.source, value => conjunction(tail, value :: values)) case Nil => intersectViews(values.reverse)(publish) shape match case TypeShape.Dynamic => publish(DynShape()) - case TypeShape.Top => publish(OpaqueTypeShape(tpe.resolution.source)( - current.origin.getOrElse(TypeInterfaceReason.Unrestricted(tpe.resolution.source)))) + case TypeShape.Top => publish(OpaqueTypeShape(source)( + current.origin.getOrElse(TypeInterfaceReason.Unrestricted(source)))) case TypeShape.Bottom => () case TypeShape.Wildcard(_, output) => - output.fold(publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.MissingOutput(current.resolution.source))))(next) - case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances).withOrigin(current.origin), noTypeArguments, aliases, publish) + output.fold(publish(OpaqueTypeShape(source)(TypeInterfaceReason.MissingOutput(current.resolution.source))))(next) + case TypeShape.Argument(parts) => follow(parts.output.instantiate(current.instances).withOrigin(current.origin), noTypeArguments, aliases, source, publish) case TypeShape.SelectedArgument(argument, positive) => // An argument cycle can forward this selection without exposing // an interface. Keep the subscription for later parts, but stop // traversing the same unproductive reference in this observation. if !aliases(current.resolution) then listenArgumentParts(argument.instantiate(current.instances)): parts => - follow(if positive then parts.output else parts.input, args, aliases + current.resolution, publish) + follow(if positive then parts.output else parts.input, args, aliases + current.resolution, source, publish) case TypeShape.Inferred(value) => listenInstanceViews(instantiateShape(value, current.instances))(publish) case TypeShape.Hole(host) => host.subscribe: value => listenInstanceViews(instantiateShape(value, current.instances))(publish) case TypeShape.Nominal(defn) => val bindings = bind(defn.tparams) defn.ext match - case N => publish(NominalInstanceView(defn, bindings, implicitParent(defn))(S(tpe.resolution.source))(this)) + case N => publish(NominalInstanceView(defn, bindings, implicitParent(defn))(S(source))(this)) case S(parent) => // Only the parent's declared type is relevant here. Evaluating // its constructor arguments would reintroduce implementation flow. @@ -982,20 +986,20 @@ class NewResolver: withTypeDependencies(parentType): dependencies => val local = captureNominalBindings(defn, bindings.filter((symbol, _) => dependencies(symbol))) listenTypeViews(declaredType(parentType, local)): ext => - publish(NominalInstanceView(defn, bindings, S(ext))(S(tpe.resolution.source))(this)) + publish(NominalInstanceView(defn, bindings, S(ext))(S(source))(this)) case TypeShape.Alias(symbol, rhs) => // Revisiting the same alias reference without reaching a structural // shape supplies no additional interface. Track // references, not symbols: Id[Id[T]] has two distinct references. - if aliases(current.resolution) then publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.RecursiveAlias(symbol))) + if aliases(current.resolution) then publish(OpaqueTypeShape(source)(TypeInterfaceReason.RecursiveAlias(symbol))) else rhs match case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams), current.positive), noTypeArguments, - aliases + current.resolution, publish) - case N => publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.AbstractDeclaration(symbol, tpe.resolution.source))) + aliases + current.resolution, source, publish) + case N => publish(OpaqueTypeShape(source)(TypeInterfaceReason.AbstractDeclaration(symbol, source))) case TypeShape.Applied(base, params) => - follow(declaredType(base, current), TypeApplication(params.map(declaredType(_, current))), aliases, publish) + follow(declaredType(base, current), TypeApplication(params.map(declaredType(_, current))), aliases, source, publish) case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match - case S(bound) => follow(selectArgument(bound.instantiate(current.instances), current.positive), noTypeArguments, aliases, publish) + case S(bound) => follow(selectArgument(bound.instantiate(current.instances), current.positive), noTypeArguments, aliases, source, publish) case N => val target = current.instances.get(symbol).fold(host)(_.inferenceHost) listenTypeArgument(target): value => @@ -1004,20 +1008,20 @@ class NewResolver: // generic members and nested tuple or function annotations. val annotated = value match case Marked(rigid: RigidTypeShape, marks) => - UnknownValueShape(rigid.source)(rigid.provenance.withTypeOrigin(tpe.resolution.source)).exit(marks) + UnknownValueShape(rigid.source, fromPublicInterface = false)(rigid.provenance.withTypeOrigin(source)).exit(marks) case Marked(unknown: UnknownValueShape, marks) => - UnknownValueShape(unknown.source)(unknown.provenance.withTypeOrigin(tpe.resolution.source)).exit(marks) + unknown.copy()(unknown.provenance.withTypeOrigin(source)).exit(marks) case _ => value annotated match case value: TermShape => listenInstanceViews(instantiateShape(value, current.instances))(publish) case NoShape => () case reference: TypeShape.Reference => - follow(referenceType(reference, current), args, aliases, publish) + follow(referenceType(reference, current), args, aliases, source, publish) case TypeShape.Contextual(reference) => // Arguments belong to the application scope, whereas the // template belongs to the reference's source scope. Move the // arguments back before substitution, then transport the result. - follow(reference.tpe.instantiate(current.instances).withOrigin(current.origin), args.move(inverseMarks(reference.marks)), aliases, + follow(reference.tpe.instantiate(current.instances).withOrigin(current.origin), args.move(inverseMarks(reference.marks)), aliases, source, shape => shape.exit(reference.marks) match case value: TermShape => publish(value) case NoShape => ()) @@ -1027,7 +1031,7 @@ class NewResolver: case Fld(_, sign, _) => S(declaredType(typeResolution(sign), current.copy(positive = !current.positive)(current.origin))) , fields.exists(!_.isInstanceOf[Fld]), N) case single => DeclaredParams(S(declaredType(typeResolution(single), current.copy(positive = !current.positive)(current.origin))) :: Nil, false, N) - publish(CallableTypeShape(tpe.resolution.source, ps ne_:: Nil, + publish(CallableTypeShape(source, ps ne_:: Nil, S(declaredType(ret, current)), N, N, N)) case TypeShape.Polymorphic(params, _, body) => val symbols = params.map(_.parameter.symbol) @@ -1036,7 +1040,7 @@ class NewResolver: DeclaredTypeParameter(param.parameter, param.lower.map(declaredType(_, lexical)), param.upper.map(declaredType(_, lexical))) - follow(declaredType(body, lexical), args, aliases, shape => shape match + follow(declaredType(body, lexical), args, aliases, source, shape => shape match case Marked(callable: CallableTypeShape, marks) => callable.copy(scheme = S(TypeScheme(current.resolution, binders ::: callable.tparams))).exit(marks) match case value: TermShape => publish(value) @@ -1057,10 +1061,10 @@ class NewResolver: formula.orderedAtoms.find(atom => left(atom) != right(atom)).exists(left)) clauses.foreach: clause => conjunction(formula.orderedAtoms.filter(clause).map(_.instantiate(current.instances)).toList, Nil) - case TypeShape.Negation(_) => publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.Negated(current.resolution.source))) + case TypeShape.Negation(_) => publish(OpaqueTypeShape(source)(TypeInterfaceReason.Negated(current.resolution.source))) case TypeShape.Unit | TypeShape.Abstract => - publish(OpaqueTypeShape(tpe.resolution.source)(TypeInterfaceReason.Unavailable(current.resolution.source))) - withCanonicalType(tpe)(follow(_, noTypeArguments, Set.empty, host.publish)) + publish(OpaqueTypeShape(source)(TypeInterfaceReason.Unavailable(current.resolution.source))) + withCanonicalType(tpe)(follow(_, noTypeArguments, Set.empty, tpe.resolution.source, host.publish)) // A separate full signature also annotates the implementation's parameters. // Register these before its body is elaborated, so observed calls can never @@ -1188,7 +1192,7 @@ class NewResolver: // Keep the missing constructor annotation on the unknown value so // later selections and calls can explain why argument flow is hidden. val unknown = td.tsym.decl match - case S(param: Param) => UnknownValueShape(source)(ShapeProvenance( + case S(param: Param) => UnknownValueShape(source, fromPublicInterface = false)(ShapeProvenance( (msg"Constructor parameter '${param.sym.nme}' has no type annotation." -> param.toLoc) :: annotation.toList.map(sign => msg"This type annotation does not provide a type for field '${member.nme}'." -> sign.toLoc))) @@ -1199,10 +1203,8 @@ class NewResolver: // selecting it does not inspect an instance field or method body. fromBMSAt(member, flow, Nil, listener, source, selected, receiver, specialization) - private def resolutionError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])]): ErrorReport = - ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc ::msgs, source = Diagnostic.Source.Compilation) def resolError(src: Term | Pattern, msgs: Ls[(Message, Opt[Loc])])(using rs: NewResolverState): Unit = - rs.report(resolutionError(src, msgs)) + rs.reportResolutionError(src, msgs) def registerTypeParameters(owner: AnyDefinitionSymbol, params: Ls[VarSymbol])(using State, NewResolverState): Unit = if params.nonEmpty then @@ -1735,7 +1737,7 @@ class NewResolver: if !listenArrayElements(receiver)(listener) then receiver match case Marked(_: DynShape, _) => listener(receiver) case Marked(unknown: UnknownValueShape, marks) => - UnknownValueShape(sel)(unknown.provenance.via(msg"This array index has no known element shape." -> sel.toLoc)) + unknown.copy(source = sel)(unknown.provenance.via(msg"This array index has no known element shape." -> sel.toLoc)) .exit(marks) match case value: TermShape => listener(value) case NoShape => () @@ -2012,7 +2014,7 @@ class NewResolver: def checkPatternDefinition(definition: PatternDef)(using NewResolverState): Unit = val source = Term.MemberRef(definition.bsym)(new syntax.Tree.Ident(definition.bsym.nme).withLocOf(definition), FlowSymbol.pat()(using rstate.owner)) - val unknown = UnknownValueShape(source)(ShapeProvenance( + val unknown = UnknownValueShape(source, fromPublicInterface = false)(ShapeProvenance( msg"Pattern '${definition.sym.nme}' accepts values of unknown shape." -> definition.toLoc :: Nil)) matchShapePat(unknown, definition.pattern)(_ => ()) @@ -2027,7 +2029,7 @@ class NewResolver: case _ => () // Some pattern outputs (notably concatenations and rebuilt constructors) // need a separate output interface. Do not reuse the input's fields for them. - def unknownOutput: UnknownValueShape = UnknownValueShape(Term.Error().withLocOf(pattern))(ShapeProvenance( + def unknownOutput: UnknownValueShape = UnknownValueShape(Term.Error().withLocOf(pattern), fromPublicInterface = false)(ShapeProvenance( msg"The output shape of this pattern is not yet known." -> pattern.toLoc :: Nil)) def isNominal(ctor: Pattern.Constructor): Bool = ctor.resolvedTargets.nonEmpty && ctor.resolvedTargets.forall: @@ -2160,7 +2162,7 @@ class NewResolver: case value: TermShape => val field = (value.applicationHead._1, unknown) match case (_: UnknownValueShape, S(Marked(origin: UnknownValueShape, marks))) => - UnknownValueShape(origin.source)(origin.provenance.via( + origin.copy()(origin.provenance.via( msg"This constructor field may contain a value of unknown shape." -> member.toLoc)).exit(marks) case _ => value field match @@ -2216,7 +2218,7 @@ class NewResolver: }) atPattern(refined)(check(_, S(sh))) case Marked(opaque: OpaqueTypeShape, marks) => - UnknownValueShape(opaque.source)(opaque.provenance).exit(marks) match + UnknownValueShape(opaque.source, fromPublicInterface = false)(opaque.provenance).exit(marks) match case value: TermShape => narrow(value) case NoShape => () case Marked(nominal: NominalInstanceView, _) if !nominal.isInstanceOfClass(cls) => @@ -2371,7 +2373,7 @@ class NewResolver: // explains its missing interface. Preserve that distinction so subsequent // selections and applications both diagnose the restricting type once. val provenance = ShapeProvenance(msg"The 'Function' type does not specify a return type." -> N :: Nil) - publish(UnknownValueShape(res)(nominal.annotation.fold(provenance)(provenance.withTypeOrigin))) + publish(UnknownValueShape(res, fromPublicInterface = false)(nominal.annotation.fold(provenance)(provenance.withTypeOrigin))) return case Marked(original: CallableTypeShape, context) => val viewed = instantiateShape(original, instances) match @@ -2521,7 +2523,8 @@ class NewResolver: publishActivated(host, shape) case NoShape => () - private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, provenance: ShapeProvenance)(using NewResolverState): Unit = if !rstate.hasError(host) then + private def unknownMember(host: NewResolvable, name: Str, reason: MemberLookup.Uncertainty, + fromPublicInterface: Bool, provenance: ShapeProvenance)(using NewResolverState): Unit = val message = reason match case MemberLookup.Uncertainty.ValueShape => msg"Cannot resolve member '$name' of a value with unknown shape." @@ -2529,11 +2532,12 @@ class NewResolver: msg"Cannot resolve member '$name' across a computed key or unknown record spread." host match case sel: NewSel => - rstate.missingMember(sel, config.language.eagerResolution): - resolutionError(sel, provenance.diagnostic(message, N)) - case _ => + rstate.missingMember(sel, config.language.eagerResolution || fromPublicInterface): + provenance.diagnostic(message, N) + case _ if !rstate.hasError(host) => rstate.markError(host) resolError(host, provenance.diagnostic(message, N)) + case _ => () def unresolvedRef(ref: UnresolvedRef)(using NewResolverState): Unit = ref.prefixes.foreach: prefix => @@ -2561,7 +2565,8 @@ class NewResolver: // A known miss in one wildcard source is not an error: another may // provide the name. Lowering diagnoses references with no candidates. () - case MemberLookup.Unknown(reason, provenance) => unknownMember(ref, ref.id.name, reason, provenance) + case MemberLookup.Unknown(reason, fromPublicInterface, provenance) => + unknownMember(ref, ref.id.name, reason, fromPublicInterface, provenance) case _ => () // Completed references only route their selected members. member(shape.getMember(ref.id.name), TypeSubstitution.empty) @@ -2654,8 +2659,9 @@ class NewResolver: publishDynamic(sel, marks) case MemberLookup.Missing if rstate.canResolve(sel) => rstate.missingMember(sel, config.language.eagerResolution): - resolutionError(sel, diagnostic(msg"$description does not contain member '${sel.id.name}'")) - case MemberLookup.Unknown(reason, provenance) => unknownMember(sel, sel.id.name, reason, provenance) + diagnostic(msg"$description does not contain member '${sel.id.name}'") + case MemberLookup.Unknown(reason, fromPublicInterface, provenance) => + unknownMember(sel, sel.id.name, reason, fromPublicInterface, provenance) // Later activations still transport field values through the compiled // selection, but cannot choose a different member for that old syntax. case _ => () @@ -3240,11 +3246,11 @@ class NewResolver: // Bound recursive record producers just as for tuple spreads. // Keep surrounding explicit fields even when the spread widens. val spread = if shape.containsSpread(shape.source, marks) - then RecordShape(shape.source, RecordShape.Unknown(term)(UnknownValueShape.spread(term, shape).provenance) :: Nil) + then RecordShape(shape.source, RecordShape.Unknown(UnknownValueShape.spread(term, shape)) :: Nil) else shape expand(rest, RecordShape.Spread(spread, marks) :: reversed) case Marked(_: DynShape, marks) => expand(rest, RecordShape.Dynamic(marks :: Nil) :: reversed) - case _ => expand(rest, RecordShape.Unknown(term)(UnknownValueShape.spread(term, shape).provenance) :: reversed) + case _ => expand(rest, RecordShape.Unknown(UnknownValueShape.spread(term, shape)) :: reversed) case _ :: rest => expand(rest, reversed) expand(record.stats, Nil) case intro: IntroTerm => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index c2711cda6c..43d8187d15 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -3,6 +3,7 @@ package semantics import scala.collection.mutable import hkmc2.utils.*, shorthands.* +import hkmc2.Message.MessageContext import Term.* inline def rstate(using state: NewResolverState): NewResolverState = state @@ -61,6 +62,8 @@ final class NewResolverState private ( def report(diagnostic: Diagnostic): Unit = assert(root.reporter != null, "Resolution requires a diagnostic collector") root.reporter.nn(diagnostic) + def reportResolutionError(src: Term | Pattern, messages: Ls[(Message, Opt[Loc])]): Unit = + report(ErrorReport(msg"Resolution error in ${src.describe}" -> src.toLoc :: messages, source = Diagnostic.Source.Compilation)) def isOwnedSym(symbol: Symbol): Bool = symbol.getState is owner @@ -133,18 +136,21 @@ final class NewResolverState private ( private val completedNodes = mutable.Set.empty[Identity[Publisher[?]]] // A receiver shape can lack a member that another shape supplies later in - // inference. Keep its diagnostic lazy so successful selections do not expand - // provenance paths. Only this block's owned selections are queued; imported - // and previously completed selections can be checked against their fixed targets. - private val missingMembers = mutable.ListBuffer.empty[(NewSel, () => Diagnostic)] + // inference. Retain lazy witnesses by selection, including those from later + // activations of completed syntax, and report them together at block completion. + // Eager problems invalidate the selection immediately, regardless of its targets; + // delaying their report lets the same error include every relevant witness. + private val missingMembers = mutable.LinkedHashMap.empty[Identity[NewSel], + mutable.ListBuffer[(Bool, () => Ls[(Message, Opt[Loc])])]] private def hasSelectionTarget(selection: NewSel): Bool = selection.resolvedTargets.nonEmpty || selection.hasDynamicTarget || selection.tupleIndex.nonEmpty - def missingMember(selection: NewSel, eager: Bool)(diagnostic: => Diagnostic): Unit = - if eager || !canResolve(selection) then - if eager || !hasSelectionTarget(selection) then - markError(selection) - report(diagnostic) - else root.missingMembers += ((selection, () => diagnostic)) + def missingMember(selection: NewSel, eager: Bool)(messages: => Ls[(Message, Opt[Loc])]): Unit = + val key = new Identity(selection) + // Accumulate witnesses for this block's error without replaying errors on + // completed selections or adding follow-on errors to unrelated failures. + if !hasError(selection) || root.missingMembers.contains(key) then + if eager then markError(selection) + root.missingMembers.getOrElseUpdate(key, mutable.ListBuffer.empty) += ((eager, () => messages)) /** Seal the decisions and original host data read by erasure/lowering. The * inference graph keeps private, live host data and all its listeners, so @@ -193,10 +199,15 @@ final class NewResolverState private ( case _ => () statement.subStatements.foreach(visit) visit(block) - root.missingMembers.foreach: (selection, diagnostic) => - if !hasSelectionTarget(selection) then + root.missingMembers.foreach: (key, problems) => + val selection = key.value + val unresolved = !hasSelectionTarget(selection) + val messages = problems.iterator.collect: + case (eager, messages) if eager || unresolved => messages() + .flatten.toList.distinct + if messages.nonEmpty then markError(selection) - report(diagnostic()) + reportResolutionError(selection, messages) root.missingMembers.clear() root.pending.foreach: (key, value) => if !key.value.isInstanceOf[Symbol] && root.completedNodes.add(key) then value.completed = true diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index b6cdac906a..f5bb465551 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -336,7 +336,7 @@ enum MemberLookup: case Indexed(fields: Ls[TupleShape.Fixed], marks: Ls[Marks]) case Dynamic(marks: Ls[Marks]) case Missing - case Unknown(reason: MemberLookup.Uncertainty, provenance: ShapeProvenance) + case Unknown(reason: MemberLookup.Uncertainty, fromPublicInterface: Bool, provenance: ShapeProvenance) def withMarks(marks: Ls[Marks]): MemberLookup = this match case Contextual(source, instances) => Contextual(source.withMarks(marks), instances) @@ -356,7 +356,7 @@ enum MemberLookup: def instantiate(instances: TypeSubstitution): MemberLookup = if instances.isEmpty then this else this match case Contextual(source, previous) => Contextual(source, instances.withOverrides(previous)) - case Missing | Unknown(_, _) | Dynamic(_) => this + case Missing | Unknown(_, _, _) | Dynamic(_) => this case _ => Contextual(this, instances) object MemberLookup: @@ -617,7 +617,7 @@ final case class OpaqueTypeShape(source: Term)(val reason: TypeInterfaceReason) def toLoc: Opt[Loc] = source.toLoc def provenance: ShapeProvenance = reason.provenance protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, provenance) + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, false, provenance) class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends CoreHeadShape: /** Instance lookup is shared by constructor calls and explicit `new`. @@ -765,16 +765,17 @@ object ShapeProvenance: val empty = ShapeProvenance(Nil) /** An unknown input or the element of an opaque or widened spread can be any - * value. Retain this alternative to explain unresolved operations and let eager - * member resolution reject unknown prefixes even when other candidates have targets. + * value. Inputs admitted by an exposed interface always require checking, even + * when local calls resolve the same selection. Keep that distinction in shape + * equality so an ordinary unknown cannot hide the public input's obligation. * Provenance is outside case-class equality: another diagnostic witness must * not turn the same unknown into a new inference candidate. */ -final case class UnknownValueShape(source: Term)(val provenance: ShapeProvenance) extends CoreHeadShape: +final case class UnknownValueShape(source: Term, fromPublicInterface: Bool)(val provenance: ShapeProvenance) extends CoreHeadShape: def describe: Str = "value of unknown shape" def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, provenance) + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, fromPublicInterface, provenance) /** A generic definition must be valid without choosing a caller's type. This * checking witness is not an inferred bound on any call-site parameter. @@ -785,16 +786,16 @@ final case class RigidTypeShape(parameter: VarSymbol, source: Term) extends Core def provenance: ShapeProvenance = ShapeProvenance( msg"Type parameter '${parameter.nme}' does not specify a member interface." -> parameter.toLoc :: Nil) protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = - MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, provenance) + MemberLookup.Unknown(MemberLookup.Uncertainty.ValueShape, false, provenance) object UnknownValueShape: def at(source: Term): UnknownValueShape = - UnknownValueShape(source)(ShapeProvenance(msg"The shape of this value is unknown." -> source.toLoc :: Nil)) + UnknownValueShape(source, fromPublicInterface = false)(ShapeProvenance(msg"The shape of this value is unknown." -> source.toLoc :: Nil)) def spread(source: Term, value: TermShape): UnknownValueShape = - val provenance = value.applicationHead._1 match - case unknown: UnknownValueShape => unknown.provenance - case _ => ShapeProvenance.empty - UnknownValueShape(source)(provenance.via(msg"This spread has no known element shape." -> source.toLoc)) + val origin = value.applicationHead._1 match + case unknown: UnknownValueShape => unknown + case _ => UnknownValueShape(source, fromPublicInterface = false)(ShapeProvenance.empty) + origin.copy(source = source)(origin.provenance.via(msg"This spread has no known element shape." -> source.toLoc)) object TupleShape: def apply(source: Term, elements: Ls[Element])(resolver: NewResolver): TupleShape = @@ -867,7 +868,7 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] def toLoc: Opt[Loc] = source.toLoc protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = import MemberLookup.* - def unknown(source: Located) = Unknown(Uncertainty.RecordOverwrite, + def unknown(source: Located) = Unknown(Uncertainty.RecordOverwrite, false, ShapeProvenance(msg"This computed key can overwrite the selected member." -> source.toLoc :: Nil)) def loop(rest: Ls[RecordShape.Element]): MemberLookup = rest match case Nil => Missing @@ -875,13 +876,13 @@ final case class RecordShape(source: Term.Rcd, elements: Ls[RecordShape.Element] case Term.Lit(Tree.StrLit(key)) => if key == name then Found(RecordMember(field, source.mut), Nil) else loop(rest) case _ => unknown(field.field) - case (unknown: RecordShape.Unknown) :: _ => Unknown(Uncertainty.RecordOverwrite, unknown.provenance) + case RecordShape.Unknown(shape) :: _ => Unknown(Uncertainty.RecordOverwrite, shape.fromPublicInterface, shape.provenance) case RecordShape.Dynamic(marks) :: _ => Dynamic(marks) case RecordShape.Spread(shape, marks) :: rest => def spread(info: MemberLookup): Opt[MemberLookup] = info match case Contextual(source, instances) => spread(source).map(_.instantiate(instances)) case Dynamic(inner) => S(Dynamic(inner ::: marks :: Nil)) - case Unknown(_, provenance) => S(Unknown(Uncertainty.RecordOverwrite, provenance)) + case Unknown(_, fromPublicInterface, provenance) => S(Unknown(Uncertainty.RecordOverwrite, fromPublicInterface, provenance)) case Missing => N case Found(member: RecordMember, inner) => S(Found(member.copy(mutable = member.mutable || source.mut), inner ::: marks :: Nil)) @@ -900,7 +901,7 @@ object RecordShape: enum Element: case Field(field: RcdField) case Spread(shape: RecordShape, marks: Marks) - case Unknown(source: Term)(val provenance: ShapeProvenance) + case Unknown(shape: UnknownValueShape) case Dynamic(marks: Ls[Marks]) export Element.* diff --git a/hkmc2/shared/src/test/mlscript-compile/LenientSelections.mls b/hkmc2/shared/src/test/mlscript-compile/LenientSelections.mls index c3ffa714f9..dbab3d42a5 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LenientSelections.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LenientSelections.mls @@ -1,7 +1,9 @@ #lang(0.3.x) -// The local call supplies a target even though exported parameters also admit -// unknown inputs. Importers must preserve that target and its runtime access. +// The declared union supplies a target while admitting another known shape +// without that member. Importers preserve the target and its runtime access. module LenientSelections with - fun read(x, enabled) = if enabled then x.value else 0 - val observed = read({value: 7}, true) + class WithValue(val value: Int) + class WithoutValue + fun read(x: WithValue | WithoutValue, enabled: Bool) = if enabled then x.value else 0 + val observed = read(WithValue(7), true) diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls index 0bd5099386..50226713e4 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls @@ -220,13 +220,31 @@ let second = new Box(Second(22)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'First' does not contain member 'second' //│ ║ l.215: let first = new Box(First(21)) -//│ ╙── ^^^^^^^^^ +//│ ║ ^^^^^^^^^ +//│ ╟── Cannot resolve member 'second' of a value with unknown shape. +//│ ╟── Parameter 'item' admits values of unknown shape. +//│ ║ l.213: class Box(val item) with +//│ ║ ^^^^^^^^ +//│ ╟── 'Box' is exposed by this compilation unit. +//│ ║ l.213: class Box(val item) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.214: fun copy() = new Box(item) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.217: [first.copy().item.first, second.copy().item.second] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Second' does not contain member 'first' //│ ║ l.216: let second = new Box(Second(22)) -//│ ╙── ^^^^^^^^^^ +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'first' of a value with unknown shape. +//│ ╟── Parameter 'item' admits values of unknown shape. +//│ ║ l.213: class Box(val item) with +//│ ║ ^^^^^^^^ +//│ ╟── 'Box' is exposed by this compilation unit. +//│ ║ l.213: class Box(val item) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.214: fun copy() = new Box(item) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[21, 22]', got: 'undefined' //│ first = Box(First(21)) diff --git a/hkmc2/shared/src/test/mlscript/newres/InferenceHoleScopes.mls b/hkmc2/shared/src/test/mlscript/newres/InferenceHoleScopes.mls index 4c3a090cf8..d790b5b214 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InferenceHoleScopes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InferenceHoleScopes.mls @@ -73,7 +73,20 @@ call(Item(2)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ║ l.60: class Box[T](val item: T) -//│ ╙── ^ +//│ ║ ^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'box' admits values of unknown shape. +//│ ║ l.62: fun read(box: Box) = box.item.value +//│ ║ ^^^^^^^^ +//│ ╟── 'read' is exposed by this compilation unit. +//│ ║ l.62: fun read(box: Box) = box.item.value +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.63: fun call(x) = read(make(x)) +//│ ║ ^ +//│ ╟── 'call' is exposed by this compilation unit. +//│ ║ l.63: fun call(x) = read(make(x)) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Inference through an explicit callback signature also transports bounds back diff --git a/hkmc2/shared/src/test/mlscript/newres/Records.mls b/hkmc2/shared/src/test/mlscript/newres/Records.mls index 0199158bc5..23be18a5a6 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Records.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Records.mls @@ -172,13 +172,27 @@ nestedWrap({second: 23}).value.second //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'first' //│ ║ l.169: nestedWrap({second: 23}).value.second -//│ ╙── ^^^^^^^^^^ +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'first' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.167: fun nestedWrap(x) = {...wrap(x)} +//│ ║ ^ +//│ ╟── 'nestedWrap' is exposed by this compilation unit. +//│ ║ l.167: fun nestedWrap(x) = {...wrap(x)} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.169: nestedWrap({second: 23}).value.second //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'second' //│ ║ l.168: nestedWrap({first: 22}).value.first -//│ ╙── ^^^^^^^^^ +//│ ║ ^^^^^^^^^ +//│ ╟── Cannot resolve member 'second' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.167: fun nestedWrap(x) = {...wrap(x)} +//│ ║ ^ +//│ ╟── 'nestedWrap' is exposed by this compilation unit. +//│ ║ l.167: fun nestedWrap(x) = {...wrap(x)} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -193,11 +207,11 @@ fun unknownOpen() = a unknownOpen() //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.193: a +//│ ║ l.207: a //│ ║ ^ //│ ╟── Cannot resolve member 'a' across a computed key or unknown record spread. //│ ╟── This computed key can overwrite the selected member. -//│ ║ l.188: let unknownKeys = {(key): 0} +//│ ║ l.202: let unknownKeys = {(key): 0} //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ unknownKeys = {a: 0} @@ -220,13 +234,13 @@ forwardRecord.answer fun choose(flag) = if flag then {answer: 1} else {answer: 2} choose(true).answer //│ ╔══[COMPILATION ERROR] This selection of member 'answer' is ambiguous, as it has multiple resolved targets -//│ ║ l.221: choose(true).answer +//│ ║ l.235: choose(true).answer //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── target: record field 'answer' -//│ ║ l.220: fun choose(flag) = if flag then {answer: 1} else {answer: 2} +//│ ║ l.234: fun choose(flag) = if flag then {answer: 1} else {answer: 2} //│ ║ ^^^^^^ //│ ╟── target: record field 'answer' -//│ ║ l.220: fun choose(flag) = if flag then {answer: 1} else {answer: 2} +//│ ║ l.234: fun choose(flag) = if flag then {answer: 1} else {answer: 2} //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -238,11 +252,11 @@ fun recursiveRecord(n) = else {...recursiveRecord(n - 1)} recursiveRecord(2).answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.239: recursiveRecord(2).answer +//│ ║ l.253: recursiveRecord(2).answer //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' across a computed key or unknown record spread. //│ ╟── This spread has no known element shape. -//│ ║ l.238: else {...recursiveRecord(n - 1)} +//│ ║ l.252: else {...recursiveRecord(n - 1)} //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -258,11 +272,11 @@ fun unknownValueOpen() = answer3 unknownValueOpen() //│ ╔══[COMPILATION ERROR] Resolution error in wildcard-open reference -//│ ║ l.258: answer3 +//│ ║ l.272: answer3 //│ ║ ^^^^^^^ //│ ╟── Cannot resolve member 'answer3' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.253: let unknownValue = mut {value: {answer3: 0}} +//│ ║ l.267: let unknownValue = mut {value: {answer3: 0}} //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -286,7 +300,7 @@ fun allMissingOpen() = nonexistent allMissingOpen() //│ ╔══[COMPILATION ERROR] Name not found in wildcard opens: nonexistent -//│ ║ l.286: nonexistent +//│ ║ l.300: nonexistent //│ ╙── ^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -332,11 +346,11 @@ let mutableNested = mut {...stableNested} set mutableNested.value = {after: 34} [stableNested.value.before, mutableNested.value.after] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.333: [stableNested.value.before, mutableNested.value.after] +//│ ║ l.347: [stableNested.value.before, mutableNested.value.after] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'after' of a value with unknown shape. //│ ╟── The shape of this value is unknown. -//│ ║ l.330: let stableNested = {value: {before: 33}} +//│ ║ l.344: let stableNested = {value: {before: 33}} //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ mutableNested = {value: {after: 34}} @@ -353,18 +367,18 @@ fun ambiguousRecordOpen() = shared ambiguousRecordOpen() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'shared' is ambiguous -//│ ║ l.353: shared +//│ ║ l.367: shared //│ ║ ^^^^^^ //│ ╟── candidate: record field 'shared' defined here -//│ ║ l.349: let firstRecord = {shared: 1} +//│ ║ l.363: let firstRecord = {shared: 1} //│ ║ ^^^^^^ //│ ╟── Opened here -//│ ║ l.351: open firstRecord +//│ ║ l.365: open firstRecord //│ ║ ^^^^^^^^^^^ //│ ╟── candidate: record field 'shared' defined here -//│ ║ l.350: let secondRecord = {shared: 2} +//│ ║ l.364: let secondRecord = {shared: 2} //│ ║ ^^^^^^ //│ ╟── Opened here -//│ ║ l.352: open secondRecord +//│ ║ l.366: open secondRecord //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/ScopePaths.mls b/hkmc2/shared/src/test/mlscript/newres/ScopePaths.mls index c197db069a..92b0a94bc8 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ScopePaths.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ScopePaths.mls @@ -63,10 +63,4 @@ make(First(9)).value.second //│ ╟── Instance of class 'First' does not contain member 'second' //│ ║ l.59: make(First(9)).value.second //│ ╙── ^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.59: make(First(9)).value.second -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── Instance of class 'First' does not contain member 'second' -//│ ║ l.59: make(First(9)).value.second -//│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index bfda3672d4..6524eb0324 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -257,14 +257,22 @@ inspectUnknown(...opaque([Box(96)])) //│ ╟── Type 'Any' is declared without a member interface. //│ ║ in ../decls/Prelude.mls //│ ║ l.2: declare type Any -//│ ╙── ^^^ +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ in SpreadCalls.mls:248 +//│ ║ l.248: fun inspectUnknown(x) = x.value +//│ ║ ^ +//│ ╟── 'inspectUnknown' is exposed by this compilation unit. +//│ ║ l.248: fun inspectUnknown(x) = x.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Too many known fields remain a proven mismatch even with an unknown spread. :e four(...opaque([]), 1, 2, 3, 4, 5) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.265: four(...opaque([]), 1, 2, 3, 4, 5) +//│ ║ l.273: four(...opaque([]), 1, 2, 3, 4, 5) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'four' expected 4 arguments, but got at least 5 //│ ║ l.5: fun four(a, b, c, d) = [a, b, c, d] @@ -276,7 +284,7 @@ four(...opaque([]), 1, 2, 3, 4, 5) fun invokeRest(f, x, ...tail) = f() invokeRest(...opaque([() => 97]), Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.276: fun invokeRest(f, x, ...tail) = f() +//│ ║ l.284: fun invokeRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value with no known interface cannot be called like a function. //│ ║ l.173: fun opaque(values): Array[Any] = values @@ -318,16 +326,16 @@ invokeTypedRest(...callbacks, Box(0)) fun invokePublicRest(f, x, ...tail) = f() invokePublicRest(...callbacks, Box(0)) //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.318: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.326: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^^^ //│ ╟── Value of unknown shape cannot be called like a function. -//│ ║ l.318: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.326: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── Parameter 'f' admits values of unknown shape. -//│ ║ l.318: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.326: fun invokePublicRest(f, x, ...tail) = f() //│ ║ ^ //│ ╟── 'invokePublicRest' is exposed by this compilation unit. -//│ ║ l.318: fun invokePublicRest(f, x, ...tail) = f() +//│ ║ l.326: fun invokePublicRest(f, x, ...tail) = f() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 97 @@ -363,10 +371,10 @@ class Child extends Base with fun readBase(xs: Array[Base]) = first(...xs).extra readBase([new Child]) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.363: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ║ l.371: fun readBase(xs: Array[Base]) = first(...xs).extra //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'extra' -//│ ║ l.363: fun readBase(xs: Array[Base]) = first(...xs).extra +//│ ║ l.371: fun readBase(xs: Array[Base]) = first(...xs).extra //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -404,16 +412,16 @@ invokeUnannotatedArray(...unknownCallbacks) private fun firstInArray(xs) = first(...xs) [firstInArray(lefts).left, firstInArray(rights).right] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.405: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.413: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Right' does not contain member 'left' -//│ ║ l.384: let rights = new mut Array[Right](0) +//│ ║ l.392: let rights = new mut Array[Right](0) //│ ╙── ^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.405: [firstInArray(lefts).left, firstInArray(rights).right] +//│ ║ l.413: [firstInArray(lefts).left, firstInArray(rights).right] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Left' does not contain member 'right' -//│ ║ l.383: let lefts = new mut Array[Left](0) +//│ ║ l.391: let lefts = new mut Array[Left](0) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '[43, 44]', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Leniency.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Leniency.mls index 8d32688075..051ee54bea 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/Leniency.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Leniency.mls @@ -37,13 +37,10 @@ fun missing(x: M1 | M2) = x.absent //│ ║ ^^^^^^^^ //│ ╟── Value of type 'M1' does not contain member 'absent' //│ ║ l.34: fun missing(x: M1 | M2) = x.absent -//│ ╙── ^^^^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.34: fun missing(x: M1 | M2) = x.absent -//│ ║ ^^^^^^^^ +//│ ║ ^^ //│ ╟── Value of type 'M2' does not contain member 'absent' //│ ║ l.34: fun missing(x: M1 | M2) = x.absent -//│ ╙── ^^^^^^^ +//│ ╙── ^^ // Indexed and dynamic selections have targets without nominal member symbols. :expect 4 @@ -63,16 +60,13 @@ dynamic.value let records = if hide(true) then {left: 1} else {right: 2} records.absent //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.64: records.absent +//│ ║ l.61: records.absent //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'absent' -//│ ║ l.63: let records = if hide(true) then {left: 1} else {right: 2} -//│ ╙── ^^^^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.64: records.absent -//│ ║ ^^^^^^^^^^^^^^ +//│ ║ l.60: let records = if hide(true) then {left: 1} else {right: 2} +//│ ║ ^^^^^^^ //│ ╟── Record literal does not contain member 'absent' -//│ ║ l.63: let records = if hide(true) then {left: 1} else {right: 2} +//│ ║ l.60: let records = if hide(true) then {left: 1} else {right: 2} //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ records = {left: 1} @@ -94,11 +88,11 @@ defaultLeniency(M1) #lang(eagerResolution: true) fun eager(x: M1 | M2) = x.u //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.95: fun eager(x: M1 | M2) = x.u +//│ ║ l.89: fun eager(x: M1 | M2) = x.u //│ ║ ^^^ //│ ╟── Value of type 'M2' does not contain member 'u' -//│ ║ l.95: fun eager(x: M1 | M2) = x.u -//│ ╙── ^^^^^^^ +//│ ║ l.89: fun eager(x: M1 | M2) = x.u +//│ ╙── ^^ // Requiring a unique target is independent of requiring every shape to match. :expect 1 @@ -111,10 +105,10 @@ unique(M1) module M3 { val u = 3 } fun ambiguous(x: M1 | M2 | M3) = x.u //│ ╔══[COMPILATION ERROR] This selection of member 'u' is ambiguous, as it has multiple resolved targets -//│ ║ l.112: fun ambiguous(x: M1 | M2 | M3) = x.u +//│ ║ l.106: fun ambiguous(x: M1 | M2 | M3) = x.u //│ ║ ^^^ //│ ╟── target: value 'u' in module 'M3' -//│ ║ l.111: module M3 { val u = 3 } +//│ ║ l.105: module M3 { val u = 3 } //│ ║ ^ //│ ╟── target: value 'u' in module 'M1' //│ ║ l.4: module M1 { val u = 1 } @@ -138,13 +132,13 @@ withUnknown(M2 as Any, false) private fun discard(f: Function) = () discard(x => x.value) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.139: discard(x => x.value) +//│ ║ l.133: discard(x => x.value) //│ ║ ^^^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.139: discard(x => x.value) +//│ ║ l.133: discard(x => x.value) //│ ║ ^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. -//│ ║ l.138: private fun discard(f: Function) = () +//│ ║ l.132: private fun discard(f: Function) = () //│ ║ ^^^^^^^^ //│ ╙── The 'Function' type does not specify parameter types. @@ -153,19 +147,17 @@ discard(x => x.value) fun noCandidate(x) = x.absent [noCandidate(M2 as Any), noCandidate(M1)] //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.153: fun noCandidate(x) = x.absent +//│ ║ l.147: fun noCandidate(x) = x.absent //│ ║ ^^^^^^^^ //│ ╟── Cannot resolve member 'absent' of a value with unknown shape. -//│ ║ l.154: [noCandidate(M2 as Any), noCandidate(M1)] +//│ ║ l.148: [noCandidate(M2 as Any), noCandidate(M1)] //│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. //│ ║ in ../../decls/Prelude.mls //│ ║ l.2: declare type Any -//│ ╙── ^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.153: fun noCandidate(x) = x.absent -//│ ║ ^^^^^^^^ +//│ ║ ^^^ //│ ╟── Module 'M1' does not contain member 'absent' +//│ ║ in Leniency.mls:4 //│ ║ l.4: module M1 { val u = 1 } //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -182,11 +174,11 @@ computed.value :e {(key): 0}.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.183: {(key): 0}.value +//│ ║ l.175: {(key): 0}.value //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' across a computed key or unknown record spread. //│ ╟── This computed key can overwrite the selected member. -//│ ║ l.183: {(key): 0}.value +//│ ║ l.175: {(key): 0}.value //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -195,10 +187,10 @@ computed.value let eagerUnknown = if hide(true) then M1 else (M2 as Any) eagerUnknown.u //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.196: eagerUnknown.u +//│ ║ l.188: eagerUnknown.u //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'u' of a value with unknown shape. -//│ ║ l.195: let eagerUnknown = if hide(true) then M1 else (M2 as Any) +//│ ║ l.187: let eagerUnknown = if hide(true) then M1 else (M2 as Any) //│ ║ ^^^ //│ ╟── Type 'Any' is declared without a member interface. //│ ║ in ../../decls/Prelude.mls @@ -213,6 +205,6 @@ eagerUnknown.u #lang(0.3.x) import "../../../mlscript-compile/LenientSelections.mls" [LenientSelections.observed, - LenientSelections.read({value: 8}, true), - LenientSelections.read(M2 as Any, false)] + LenientSelections.read(LenientSelections.WithValue(8), true), + LenientSelections.read((new LenientSelections.WithoutValue) as Any, false)] //│ = [7, 8, 0] diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/PublicInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/loose/PublicInterfaces.mls new file mode 100644 index 0000000000..4a00678180 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/loose/PublicInterfaces.mls @@ -0,0 +1,265 @@ +:. +#lang(strictResolution: true, eagerResolution: false) + +// Local targets cannot discharge the inputs admitted by a public interface. +:e +fun read(x) = x.value +read({value: 1}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.6: fun read(x) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.6: fun read(x) = x.value +//│ ║ ^ +//│ ╟── 'read' is exposed by this compilation unit. +//│ ║ l.6: fun read(x) = x.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Rest parameters retain the same obligation through tuple element lookup. +:e +fun rest(...xs) = xs.0.value +rest({value: 2}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.22: fun rest(...xs) = xs.0.value +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Rest parameter 'xs' admits elements of unknown shape. +//│ ║ l.22: fun rest(...xs) = xs.0.value +//│ ║ ^^^^^ +//│ ╟── 'rest' is exposed by this compilation unit. +//│ ║ l.22: fun rest(...xs) = xs.0.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Returned closures are reachable from the public interface too. +:e +fun escaped() = x => x.value +escaped()({value: 3}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.38: fun escaped() = x => x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.38: fun escaped() = x => x.value +//│ ║ ^ +//│ ╟── This value is returned here. +//│ ║ l.38: fun escaped() = x => x.value +//│ ║ ^^^^^^^^^^^^ +//│ ╟── 'escaped' is exposed by this compilation unit. +//│ ║ l.38: fun escaped() = x => x.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A completed private helper must still be checked when it later escapes. +:expect 4 +private fun hidden(x) = x.value +hidden({value: 4}) +//│ = 4 + +:e +val exported = {callback: hidden} +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.57: private fun hidden(x) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.57: private fun hidden(x) = x.value +//│ ║ ^ +//│ ╟── This value is stored in this record field. +//│ ║ l.62: val exported = {callback: hidden} +//│ ║ ^^^^^^^^ +//│ ╟── 'exported' is exposed by this compilation unit. +//│ ║ l.62: val exported = {callback: hidden} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ exported = {callback: fun hidden} + +// Record spreads preserve the public origin across nested copies. +:e +fun recordSpread(x) = ({...{...x}}).value +recordSpread({value: 5}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.80: fun recordSpread(x) = ({...{...x}}).value +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' across a computed key or unknown record spread. +//│ ╟── This spread has no known element shape. +//│ ║ l.80: fun recordSpread(x) = ({...{...x}}).value +//│ ║ ^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.80: fun recordSpread(x) = ({...{...x}}).value +//│ ║ ^ +//│ ╟── 'recordSpread' is exposed by this compilation unit. +//│ ║ l.80: fun recordSpread(x) = ({...{...x}}).value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Tuple spreads preserve it on their unknown elements. +:e +fun tupleSpread(xs) = [...xs].0.value +tupleSpread([{value: 6}]) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.99: fun tupleSpread(xs) = [...xs].0.value +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This spread has no known element shape. +//│ ║ l.99: fun tupleSpread(xs) = [...xs].0.value +//│ ║ ^^ +//│ ╟── Parameter 'xs' admits values of unknown shape. +//│ ║ l.99: fun tupleSpread(xs) = [...xs].0.value +//│ ║ ^^ +//│ ╟── 'tupleSpread' is exposed by this compilation unit. +//│ ║ l.99: fun tupleSpread(xs) = [...xs].0.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Checked indexing carries the origin and its diagnostic path to the result. +:e +fun indexed(xs) = xs.[0].value +indexed([{value: 7}]) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.118: fun indexed(xs) = xs.[0].value +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This array index has no known element shape. +//│ ║ l.118: fun indexed(xs) = xs.[0].value +//│ ║ ^^^^^ +//│ ╟── Parameter 'xs' admits values of unknown shape. +//│ ║ l.118: fun indexed(xs) = xs.[0].value +//│ ║ ^^ +//│ ╟── 'indexed' is exposed by this compilation unit. +//│ ║ l.118: fun indexed(xs) = xs.[0].value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Type substitution must keep the public input distinct from ordinary unknowns. +:e +private fun identity[A](x: A): A = x +fun throughType(x) = identity(x).value +throughType({value: 8}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.138: fun throughType(x) = identity(x).value +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.138: fun throughType(x) = identity(x).value +//│ ║ ^ +//│ ╟── 'throughType' is exposed by this compilation unit. +//│ ║ l.138: fun throughType(x) = identity(x).value +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.137: private fun identity[A](x: A): A = x +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Omitted type arguments admit external values despite a known local element. +:e +fun hole(xs: Array) = xs.0.value +hole([{value: 9}]) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.156: fun hole(xs: Array) = xs.0.value +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'xs' admits values of unknown shape. +//│ ║ l.156: fun hole(xs: Array) = xs.0.value +//│ ║ ^^^^^^^^^ +//│ ╟── 'hole' is exposed by this compilation unit. +//│ ║ l.156: fun hole(xs: Array) = xs.0.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Constructor refinement must preserve the input's origin on unknown fields. +:e +private class Box(val item) +fun refined(x) = if x is Box(item) then item.value else 0 +refined(Box({value: 10})) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.173: fun refined(x) = if x is Box(item) then item.value else 0 +//│ ║ ^^^^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── This constructor field may contain a value of unknown shape. +//│ ║ l.172: private class Box(val item) +//│ ║ ^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.173: fun refined(x) = if x is Box(item) then item.value else 0 +//│ ║ ^ +//│ ╟── 'refined' is exposed by this compilation unit. +//│ ║ l.173: fun refined(x) = if x is Box(item) then item.value else 0 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Private helpers that do not escape retain local inference. +:expect 11 +private fun local(x) = x.value +local({value: 11}) +//│ = 11 + +// Explicitly described alternatives still use lenient missing-member checking. +:expect 12 +module Known { val value = 12 } +module Other { val other = 0 } +fun declared(x: Known | Other) = x.value +declared(Known) +//│ = 12 + +// A dynamic target does not exempt the function's other possible public inputs. +:e +fun dynamic(x) = x.value +dynamic({value: 13} as dyn) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.206: fun dynamic(x) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.206: fun dynamic(x) = x.value +//│ ║ ^ +//│ ╟── 'dynamic' is exposed by this compilation unit. +//│ ║ l.206: fun dynamic(x) = x.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A tuple target likewise cannot close an unannotated public parameter. +:e +fun first(xs) = xs.0 +first([14]) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.222: fun first(xs) = xs.0 +//│ ║ ^^^^ +//│ ╟── Cannot resolve member '0' of a value with unknown shape. +//│ ╟── Parameter 'xs' admits values of unknown shape. +//│ ║ l.222: fun first(xs) = xs.0 +//│ ║ ^^ +//│ ╟── 'first' is exposed by this compilation unit. +//│ ║ l.222: fun first(xs) = xs.0 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// An annotated callback interface must be checked in a non-strict worksheet too. +:e +#lang(strictResolution: false) +private fun erase(f: Function) = () +private fun callback(x) = x.value +callback({value: 15}) +erase(callback) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.240: private fun callback(x) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.240: private fun callback(x) = x.value +//│ ║ ^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ║ l.239: private fun erase(f: Function) = () +//│ ║ ^^^^^^^^ +//│ ╙── The 'Function' type does not specify parameter types. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Recursive spread widening must not hide public inputs behind a local target. +// FIXME: Preserve public-input provenance in the bounded tuple widening representation. +:breakme +:e +#lang(strictResolution: true) +private fun padded(x, n: Int) = if n == 0 then [x] else [x, ...padded(x, n - 1)] +fun deep(x) = + let value = if hide(true) then padded(x, 4).4 else {value: 0} + value.value +deep({value: 16}) +//│ = 16 diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/SelectionDiagnostics.mls b/hkmc2/shared/src/test/mlscript/newres/loose/SelectionDiagnostics.mls new file mode 100644 index 0000000000..48db40a7ae --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/loose/SelectionDiagnostics.mls @@ -0,0 +1,170 @@ +:. + +module First { val value = 1 } +module Second { val other = 2 } +module Third { val another = 3 } + +// Each nested union component has its own witness in one selection error. +:e +fun nested(x: First | (Second | Third)) = x.absent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.9: fun nested(x: First | (Second | Third)) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Value of type 'First' does not contain member 'absent' +//│ ║ l.9: fun nested(x: First | (Second | Third)) = x.absent +//│ ║ ^^^^^ +//│ ╟── Value of type 'Second' does not contain member 'absent' +//│ ║ l.9: fun nested(x: First | (Second | Third)) = x.absent +//│ ║ ^^^^^^ +//│ ╟── Value of type 'Third' does not contain member 'absent' +//│ ║ l.9: fun nested(x: First | (Second | Third)) = x.absent +//│ ╙── ^^^^^ + +// Following aliases preserves the component's written use, not its definition. +:e +type Left = First +type Right = Second +fun aliases(x: Left | Right) = x.absent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.27: fun aliases(x: Left | Right) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Value of type 'First' does not contain member 'absent' +//│ ║ l.27: fun aliases(x: Left | Right) = x.absent +//│ ║ ^^^^ +//│ ╟── Value of type 'Second' does not contain member 'absent' +//│ ║ l.27: fun aliases(x: Left | Right) = x.absent +//│ ╙── ^^^^^ + +// Applied components retain the whole application as their witness. +:e +class Box[A] +fun applied(x: Box[Int] | Box[Str]) = x.absent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.41: fun applied(x: Box[Int] | Box[Str]) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Value of type 'Box' does not contain member 'absent' +//│ ║ l.41: fun applied(x: Box[Int] | Box[Str]) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Value of type 'Box' does not contain member 'absent' +//│ ║ l.41: fun applied(x: Box[Int] | Box[Str]) = x.absent +//│ ╙── ^^^^^^^^ + +// A union inside an alias supplies witnesses at its component declarations. +:e +type Alternatives = First | Second +fun aliasedUnion(x: Alternatives) = x.absent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.55: fun aliasedUnion(x: Alternatives) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Value of type 'First' does not contain member 'absent' +//│ ║ l.54: type Alternatives = First | Second +//│ ║ ^^^^^ +//│ ╟── Value of type 'Second' does not contain member 'absent' +//│ ║ l.54: type Alternatives = First | Second +//│ ╙── ^^^^^^ + +// Uncertain components retain both the use and the declaration explaining them. +:e +declare type Hidden +fun unknowns(x: Any | Hidden) = x.absent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.69: fun unknowns(x: Any | Hidden) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Cannot resolve member 'absent' of a value with unknown shape. +//│ ║ l.69: fun unknowns(x: Any | Hidden) = x.absent +//│ ║ ^^^ +//│ ╟── Type 'Any' is declared without a member interface. +//│ ║ in ../../decls/Prelude.mls +//│ ║ l.2: declare type Any +//│ ║ ^^^ +//│ ╟── Cannot resolve member 'absent' of a value with unknown shape. +//│ ║ in SelectionDiagnostics.mls:68 +//│ ║ l.69: fun unknowns(x: Any | Hidden) = x.absent +//│ ║ ^^^^^^ +//│ ╟── Type 'Hidden' is declared without a member interface. +//│ ║ l.68: declare type Hidden +//│ ╙── ^^^^^^ + +// Repeated calls must not repeat the same witness within the error. +:e +private fun repeated(x) = x.absent +[repeated(First), repeated(First)] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.90: private fun repeated(x) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Module 'First' does not contain member 'absent' +//│ ║ l.3: module First { val value = 1 } +//│ ╙── ^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Two selections retain separate errors, each with both witnesses. +:e +fun separate(x: First | Second) = [x.left, x.right] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.102: fun separate(x: First | Second) = [x.left, x.right] +//│ ║ ^^^^^^ +//│ ╟── Value of type 'First' does not contain member 'left' +//│ ║ l.102: fun separate(x: First | Second) = [x.left, x.right] +//│ ║ ^^^^^ +//│ ╟── Value of type 'Second' does not contain member 'left' +//│ ║ l.102: fun separate(x: First | Second) = [x.left, x.right] +//│ ╙── ^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.102: fun separate(x: First | Second) = [x.left, x.right] +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'First' does not contain member 'right' +//│ ║ l.102: fun separate(x: First | Second) = [x.left, x.right] +//│ ║ ^^^^^ +//│ ╟── Value of type 'Second' does not contain member 'right' +//│ ║ l.102: fun separate(x: First | Second) = [x.left, x.right] +//│ ╙── ^^^^^^ + +// Eager checking still combines the misses when another alternative has a target. +:e +#lang(eagerResolution: true) +fun eager(x: First | Second | Third) = x.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.125: fun eager(x: First | Second | Third) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'Second' does not contain member 'value' +//│ ║ l.125: fun eager(x: First | Second | Third) = x.value +//│ ║ ^^^^^^ +//│ ╟── Value of type 'Third' does not contain member 'value' +//│ ║ l.125: fun eager(x: First | Second | Third) = x.value +//│ ╙── ^^^^^ + +// Combine deferred misses with the eager public-input obligation when unresolved. +:e +#lang(eagerResolution: false, strictResolution: true) +fun exposed(x) = x.absent +exposed(First) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.139: fun exposed(x) = x.absent +//│ ║ ^^^^^^^^ +//│ ╟── Module 'First' does not contain member 'absent' +//│ ║ l.3: module First { val value = 1 } +//│ ║ ^^^^^ +//│ ╟── Cannot resolve member 'absent' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.139: fun exposed(x) = x.absent +//│ ║ ^ +//│ ╟── 'exposed' is exposed by this compilation unit. +//│ ║ l.139: fun exposed(x) = x.absent +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A resolved public selection reports the unknown input, not other known misses. +:e +fun resolved(x) = x.value +[resolved(First), resolved(Second)] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.158: fun resolved(x) = x.value +//│ ║ ^^^^^^^ +//│ ╟── Cannot resolve member 'value' of a value with unknown shape. +//│ ╟── Parameter 'x' admits values of unknown shape. +//│ ║ l.158: fun resolved(x) = x.value +//│ ║ ^ +//│ ╟── 'resolved' is exposed by this compilation unit. +//│ ║ l.158: fun resolved(x) = x.value +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls index bc68d708ee..6b5fa7eb72 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls @@ -17,10 +17,7 @@ receiver.missing //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Module 'Left' does not contain member 'missing' //│ ║ l.6: module Left { fun get() = 1 } -//│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.14: receiver.missing -//│ ║ ^^^^^^^^^^^^^^^^ +//│ ║ ^^^^ //│ ╟── Module 'Right' does not contain member 'missing' //│ ║ l.7: module Right { fun get() = 2 } //│ ╙── ^^^^^ @@ -29,10 +26,10 @@ receiver.missing :e 1() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.30: 1() +//│ ║ l.27: 1() //│ ║ ^^^ //│ ╟── Integer literal cannot be called like a function. -//│ ║ l.30: 1() +//│ ║ l.27: 1() //│ ╙── ^ //│ ═══[RUNTIME ERROR] TypeError: 1 is not a function @@ -40,7 +37,7 @@ receiver.missing :e missingCallee() //│ ╔══[COMPILATION ERROR] Name not found: missingCallee -//│ ║ l.41: missingCallee() +//│ ║ l.38: missingCallee() //│ ╙── ^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -67,7 +64,7 @@ call() :e fun unknownReceiver(x) = x.missing //│ ╔══[COMPILATION ERROR] This selection of member 'missing' has no resolved target -//│ ║ l.68: fun unknownReceiver(x) = x.missing +//│ ║ l.65: fun unknownReceiver(x) = x.missing //│ ╙── ^^^^^^^^^ // Allowing multiple targets does not make class definitions assignable. @@ -77,7 +74,7 @@ module ClassRight { class Item } let classes = if hide(true) then ClassLeft else ClassRight set classes.Item = 0 //│ ╔══[COMPILATION ERROR] Assignment requires a term member -//│ ║ l.78: set classes.Item = 0 +//│ ║ l.75: set classes.Item = 0 //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ classes = class ClassLeft { Item: class Item } From 3f70a2c92789c61830d3133b8511be5f0669643a Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 20:23:28 +0800 Subject: [PATCH 186/228] Remove obsolete eager-resolution casts and rebindings --- .../src/test/mlscript-compile/LazyArray.mls | 8 +- .../test/mlscript-compile/LazyFingerTree.mls | 5 +- .../src/test/mlscript-compile/MutMap.mls | 2 +- .../src/test/mlscript-compile/Rendering.mls | 24 +++--- .../src/test/mlscript-compile/Runtime.mjs | 82 +++++++++--------- .../src/test/mlscript-compile/Runtime.mls | 83 +++++++++---------- .../src/test/mlscript-compile/Shape.mls | 2 +- .../shared/src/test/mlscript-compile/Term.mls | 6 +- .../shared/src/test/mlscript-compile/XML.mls | 2 +- .../src/test/mlscript-compile/apps/CSV.mls | 12 +-- .../apps/parsing-web-demo/main.mls | 5 +- .../shared/src/test/mlscript/codegen/Pwd.mls | 2 +- .../test/mlscript/std/FingerTreeListTest.mls | 14 ++-- 13 files changed, 120 insertions(+), 127 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls index 3f1f45870c..f8ee7e3b8a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls @@ -10,7 +10,7 @@ fun normIdx(i, len) = else i fun enforceArray = case - (LazyArr | Array | String | Str | TypedArray) as a then a as Indexable + (LazyArr | Array | String | Str | TypedArray) as a then a ow then throw Error("Expected an Array, got: " + String(ow)) fun concatHelper(a: Array, l: Int, ...args: Array[Indexable]) = @@ -97,7 +97,7 @@ class Splice(val bits: Array, val len: Int) extends LazyArr with SpliceMarker then if uitr === null do set uitr = bits.[idx + 1].(Symbol.iterator)() - let n = (uitr as Iterator).next() + let n = uitr.next() if n.done then set uitr = null set idx += 2 @@ -117,7 +117,7 @@ class Splice(val bits: Array, val len: Int) extends LazyArr with val stack: Array[[Int, Indexable]] = mut [] stack.push([0, bits]) while stack.length > 0 do - let e = stack.pop() as [Int, Indexable] + let e = stack.pop() let idx = e.0 let vals = e.1 @@ -132,7 +132,7 @@ class Splice(val bits: Array, val len: Int) extends LazyArr with if (idx < vals.length and vals.at(idx) is SpliceMarker) do stack.push([idx + 2, vals]) - stack.push([0, vals.at(idx + 1) as Indexable]) + stack.push([0, vals.at(idx + 1)]) arr fun materialize(): Array = diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls index fd93bfe4ba..7f44ae8b41 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls @@ -91,8 +91,7 @@ class Splice(val bits: Array, val len: Int) extends LazyFT with val stack: Array[[Int, Indexable]] = mut [] stack.push([0, bits]) while stack.length > 0 do - // The loop condition excludes the undefined result of popping an empty stack. - let e = stack.pop() as [Int, Indexable] + let e = stack.pop() let idx = e.0 let vals = e.1 @@ -110,7 +109,7 @@ class Splice(val bits: Array, val len: Int) extends LazyFT with if FingerTreeList.isFingerTree(next) then set acc = acc ++ next // SpliceMarker is always followed by an indexable sequence. - else stack.push([0, next as Indexable]) + else stack.push([0, next]) acc fun materialize(): IterableIndexable = diff --git a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls index 4c9977fee7..b9c63688e9 100644 --- a/hkmc2/shared/src/test/mlscript-compile/MutMap.mls +++ b/hkmc2/shared/src/test/mlscript-compile/MutMap.mls @@ -17,7 +17,7 @@ class MutMap[A, B](val underlying: Map[A, B]) with fun get[A, B](key: A)(m: MutMap[A, B]): Option[B] = // Map.get also admits undefined for absent keys; has excludes that case. if m.underlying.has(key) - then Some of (m.underlying.get(key) as B) + then Some of m.underlying.get(key) else None fun insert[A, B](key: A, value: B)(m: MutMap[A, B]) = diff --git a/hkmc2/shared/src/test/mlscript-compile/Rendering.mls b/hkmc2/shared/src/test/mlscript-compile/Rendering.mls index 4e05be4deb..058fac6ee7 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Rendering.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Rendering.mls @@ -111,7 +111,7 @@ fun render(target, ...args): Str = // Add indentations to results of `toString` from other objects. fun indentText(text: Str, currentLevel: Int): Str = if indent is null then text else... - let indents = (indent as Str).repeat(currentLevel + 1) + let indents = indent.repeat(currentLevel + 1) text .split("\n") .map of (line, index, lines) => @@ -127,7 +127,7 @@ fun render(target, ...args): Str = // The length of indentations that should be prepended to items in this // array if this array is displayed in multi-lines. itemIndentationLength = - if indent is ~null as indent then (level + 1) * indent.length else 0 + if indent is null then 0 else (level + 1) * indent.length // Return `true` if the array has no remaining elements. let done() = let isDone = null @@ -161,7 +161,7 @@ fun render(target, ...args): Str = // The length of indentations that should be prepended to items in this // array if this array is displayed in multi-lines. itemIndentationLength = - if indent is ~null as indent then (level + 1) * indent.length else 0 + if indent is null then 0 else (level + 1) * indent.length // Return `true` if the array has no remaining elements. let done() = peek.done let next(isFirst: Bool, prefixLength: Int) = @@ -181,7 +181,7 @@ fun render(target, ...args): Str = let length = fieldNames.length i = 0 - itemIndentationLength = if indent is ~null as indent then (level + 1) * indent.length else 0 + itemIndentationLength = if indent is null then 0 else (level + 1) * indent.length let done() = i >= length let next(isFirst: Bool, prefixLength: Int) = if fieldNames.[i] is do set i += 1 @@ -202,8 +202,8 @@ fun render(target, ...args): Str = // brackets and spaces around items are excluded from the length. singleLineLength = startPos + symbols.start.length + (if symbols.padding then padding.length else 0) - parentIndent = if indent is ~null as indent then indent.repeat(level) else "" - itemIdent = if indent is ~null as indent then indent.repeat(level + 1) else "" + parentIndent = if indent is null then null else indent.repeat(level) + itemIdent = if indent is null then null else indent.repeat(level + 1) // Iterate through and render all items. while done() is false do let item = next(items.length is 0, singleLineLength) @@ -240,7 +240,7 @@ fun render(target, ...args): Str = length = entries.length i = 0 itemIndentationLength = - if indent is ~null as indent then (level + 1) * indent.length else 0 + if indent is null then 0 else (level + 1) * indent.length let skipNonEnumerable() = while i < length and entries.[i].1!enumerable is false do set i += 1 let done() = i >= length @@ -278,7 +278,7 @@ fun render(target, ...args): Str = // The length of indentations that should be prepended to items in this // array if this array is displayed in multi-lines. itemIndentationLength = - if indent is ~null as indent then (level + 1) * indent.length else 0 + if indent is null then 0 else (level + 1) * indent.length // Return `true` if the array has no remaining elements. let done() = peek.done let next(isFirst: Bool, prefixLength: Int) = @@ -315,8 +315,8 @@ fun render(target, ...args): Str = items = mut [] hasMultiline = false singleLineLength = startPos + symbols.start.length + startPadding.length - parentIndent = if indent is ~null as indent then indent.repeat(level) else "" - itemIdent = if indent is ~null as indent then indent.repeat(level + 1) else "" + parentIndent = if indent is null then null else indent.repeat(level) + itemIdent = if indent is null then null else indent.repeat(level + 1) while done() is false do let keyValue = next(items.length is 0, singleLineLength) @@ -363,7 +363,7 @@ fun render(target, ...args): Str = false then "false" arg === () then "()" // Reuse the cached result if the display mode is inline. - indent is null and visitedObjects.get(arg) is Str as cached then cached + visitedObjects.has(arg) and indent is null then visitedObjects.get(arg) visitingObjects.has(arg) and visitingObjects.get(arg) is // We have already found this circular reference before Int as index then "ref'" + index @@ -389,7 +389,7 @@ fun render(target, ...args): Str = let desc = Object.getOwnPropertyDescriptor(arg, "prototype") let head = if // ES 2015 classes: `desc.writable` is `false`. - desc is Object as desc and desc!writable is false and + desc is Object and desc!writable is false and // If it is from MLscript, use the kind written in metadata. desc!value!constructor!hasOwnProperty(definitionMetadataSymbol) and desc!value!constructor!(definitionMetadataSymbol) is [kind, name, ...] then diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index 5146fc8ce1..539d071b2c 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -524,12 +524,6 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { this.lastHandler = lastHandler; this.resumed = resumed; } - get lastFrame() { - return this.last; - } - get lastHandlerFrame() { - return this.lastHandler; - } toString() { return runtime.render(this); } static [definitionMetadata] = ["class", "ContTrace", ["next", "last", "nextHandler", "lastHandler", "resumed"]]; }); @@ -750,14 +744,15 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { throw runtime.safeCall(globalThis.Error(tmp3)) } static _try(f) { - let res, scrut; + let res, scrut, tmp; res = runtime.safeCall(f()); - scrut = Runtime.curEffect; - if (scrut === null) { - return res + scrut = Runtime.curEffect !== null; + if (scrut === true) { + tmp = Runtime.curEffect; + Runtime.curEffect = null; + return Runtime.EffectHandle(tmp) } - Runtime.curEffect = null; - return Runtime.EffectHandle(scrut); + return res; } static printRaw(x) { let rcd, tmp; @@ -821,17 +816,19 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { atTail = true; if (debug === true) { lbl: while (true) { - let cur, nextHandler, tmp, tmp1; - if (curHandler !== null) { + let scrut, cur, scrut1, tmp, tmp1; + scrut = curHandler !== null; + if (scrut === true) { cur = curHandler.next; lbl1: while (true) { - let curLocals, loc, scrut, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10; - if (cur !== null) { + let scrut2, curLocals, loc, scrut3, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10; + scrut2 = cur !== null; + if (scrut2 === true) { curLocals = cur.getLocals; loc = cur.getLoc; if (showLocals === true) { - scrut = curLocals.length > 0; - if (scrut === true) { + scrut3 = curLocals.length > 0; + if (scrut3 === true) { tmp2 = runtime.safeCall(curLocals.map(lambda)); tmp3 = runtime.safeCall(tmp2.join(", ")); tmp4 = " with locals: " + tmp3; @@ -854,15 +851,15 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { } break; } - nextHandler = curHandler.nextHandler; - curHandler = nextHandler; - if (nextHandler === null) { + curHandler = curHandler.nextHandler; + scrut1 = curHandler !== null; + if (scrut1 === true) { + tmp = "\n\twith handler " + curHandler.handler["constructor"]["name"]; + tmp1 = msg + tmp; + msg = tmp1; + atTail = false; continue lbl } - tmp = "\n\twith handler " + nextHandler.handler["constructor"]["name"]; - tmp1 = msg + tmp; - msg = tmp1; - atTail = false; continue lbl; } break; @@ -975,8 +972,9 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { runtime.safeCall(globalThis.console.log(tmp4)); cur = contTrace.nextHandler; lbl: while (true) { - let tmp5; - if (cur !== null) { + let scrut2, tmp5; + scrut2 = cur !== null; + if (scrut2 === true) { tmp5 = Runtime.showHandlerContChain(cur, hl, vis, 0); runtime.safeCall(globalThis.console.log(tmp5)); cur = cur.nextHandler; @@ -1000,11 +998,10 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { return runtime.safeCall(globalThis.console.log(eff)); } static unwind(...saved) { - let effect, tmp; - effect = Runtime.curEffect; + let tmp; tmp = new Runtime.FunctionContFrame.class(null, saved); - effect.contTrace.lastFrame.next = tmp; - effect.contTrace.last = effect.contTrace.lastFrame.next; + Runtime.curEffect.contTrace.last.next = tmp; + Runtime.curEffect.contTrace.last = Runtime.curEffect.contTrace.last.next; return runtime.Unit } static mkEffect(handler, handlerFun) { @@ -1019,7 +1016,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { static handleBlockImpl(cur, handler) { let handlerFrame; handlerFrame = new Runtime.HandlerContFrame.class(null, null, handler); - cur.contTrace.lastHandlerFrame.nextHandler = handlerFrame; + cur.contTrace.lastHandler.nextHandler = handlerFrame; cur.contTrace.lastHandler = handlerFrame; cur.contTrace.last = handlerFrame; return Runtime.handleEffects(cur) @@ -1054,11 +1051,11 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { prevHandlerFrame = cur.contTrace; lbl: while (true) { let scrut7, scrut8; - scrut7 = prevHandlerFrame.nextHandler; - if (scrut7 !== null) { - scrut8 = scrut7.handler !== cur.handler; + scrut7 = prevHandlerFrame.nextHandler !== null; + if (scrut7 === true) { + scrut8 = prevHandlerFrame.nextHandler.handler !== cur.handler; if (scrut8 === true) { - prevHandlerFrame = scrut7; + prevHandlerFrame = prevHandlerFrame.nextHandler; continue lbl } } @@ -1098,7 +1095,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { cur = Runtime.curEffect; scrut4 = saved.next !== null; if (scrut4 === true) { - cur.contTrace.lastFrame.next = saved.next; + cur.contTrace.last.next = saved.next; } scrut5 = saved.last !== saved; if (scrut5 === true) { @@ -1106,7 +1103,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { } scrut6 = saved.nextHandler !== null; if (scrut6 === true) { - cur.contTrace.lastHandlerFrame.nextHandler = saved.nextHandler; + cur.contTrace.lastHandler.nextHandler = saved.nextHandler; cur.contTrace.lastHandler = saved.lastHandler; return cur } @@ -1131,9 +1128,8 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { cont = contTrace.next; handlerCont = contTrace.nextHandler; lbl: while (true) { - let frame, old, scrut, scrut1, scrut2, tmp, tmp1, tmp2; + let old, scrut, scrut1, scrut2, tmp, tmp1, tmp2; if (cont instanceof Runtime.FunctionContFrame.class) { - frame = cont; Runtime.curEffect = null; old = Runtime.stackDepth; try { @@ -1150,8 +1146,8 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { value = Runtime.curEffect; } if (value instanceof Runtime.EffectSig.class) { - value.contTrace.lastFrame.next = frame.next; - value.contTrace.lastHandlerFrame.nextHandler = handlerCont; + value.contTrace.last.next = cont.next; + value.contTrace.lastHandler.nextHandler = handlerCont; scrut1 = contTrace.last !== cont; if (scrut1 === true) { value.contTrace.last = contTrace.last; @@ -1163,7 +1159,7 @@ lambda4 = (undefined, function (Runtime2, EffectHandle1, value) { } return value; } - cont = frame.next; + cont = cont.next; continue lbl; } if (handlerCont instanceof Runtime.HandlerContFrame.class) { diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 34955316c2..43921f7276 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -77,7 +77,8 @@ class EffectHandle(_reified: EffectSig) with fun _try(f: () -> Any) = let res = f() - if curEffect is ~null as tmp then + if curEffect !== null then + let tmp = curEffect set curEffect = null EffectHandle(tmp) else res @@ -222,11 +223,7 @@ data class ContTrace( nextHandler: HandlerContFrame | null, lastHandler: ContTrace | HandlerContFrame | null, resumed: Bool -) with - // mkEffect fills both tail links before publishing the trace in curEffect. - // Empty chains point back to the trace itself; null is only used during construction. - fun lastFrame: ContTrace | HandlerContFrame | FunctionContFrame = last as (ContTrace | HandlerContFrame | FunctionContFrame) - fun lastHandlerFrame: ContTrace | HandlerContFrame = lastHandler as (ContTrace | HandlerContFrame) +) // PrintStackEffect stores the showLocals flag; FatalEffect has no handler function. data class EffectSig(contTrace: ContTrace, handler: Any, handlerFun: Function | Bool | null) @@ -250,20 +247,20 @@ fun raisePrintStackEffect(showLocals) = fun topLevelEffect(debug) = let tr = curEffect let v = null - while tr is EffectSig as eff and eff.handler === PrintStackEffect do - console.log(showStackTrace("Stack Trace:", eff, debug, eff.handlerFun as Bool)) + while tr is EffectSig and tr.handler === PrintStackEffect do + console.log(showStackTrace("Stack Trace:", tr, debug, tr.handlerFun)) set curEffect = null - v = resume(eff.contTrace)(()) + v = resume(tr.contTrace)(()) tr = curEffect - if tr is EffectSig as tr then + if tr is EffectSig then set curEffect = null throw CustomStackError(showStackTrace("Error: Unhandled effect " + tr.handler!constructor!name, tr, debug, false)) else v fun illegalEffect(position) = - let tmp = curEffect as EffectSig + let tmp = curEffect set curEffect = null throw CustomStackError(showStackTrace("Error: Effect " + tmp.handler!constructor!name + " is raised " + position, tmp, false, false)) @@ -273,24 +270,23 @@ fun showStackTrace(header: Str, tr: EffectSig, debug: Bool, showLocals: Bool) = curHandler = tr.contTrace atTail = true if debug do - while curHandler is ~null as handlerFrame do - let cur = handlerFrame.next - while cur is ~null as frame do - let curLocals = frame.getLocals - let loc = frame.getLoc + while curHandler !== null do + let cur = curHandler.next + while cur !== null do + let curLocals = cur.getLocals + let loc = cur.getLoc let localsMsg = if showLocals and curLocals.length > 0 then " with locals: " + curLocals.map((l, ...) => l.localName + "=" + Rendering.render(l.value)).join(", ") else "" set - msg += "\n\tat " + frame.getNme + " (" + loc + ")" + msg += "\n\tat " + cur.getNme + " (" + loc + ")" msg += localsMsg - cur = frame.next + cur = cur.next atTail = false - let nextHandler = handlerFrame.nextHandler - set curHandler = nextHandler - if nextHandler is ~null as handlerFrame do + set curHandler = curHandler.nextHandler + if curHandler !== null do set - msg += "\n\twith handler " + handlerFrame.handler!constructor!name + msg += "\n\twith handler " + curHandler.handler!constructor!name atTail = false if atTail do set msg += "\n\tat tail position" @@ -346,9 +342,9 @@ fun debugContTrace(contTrace) = hl.set("last-handler", new Set([contTrace.lastHandler])) console.log(showFunctionContChain(contTrace.next, hl, vis, 0)) let cur = contTrace.nextHandler - while cur is ~null as frame do - console.log(showHandlerContChain(frame, hl, vis, 0)) - set cur = frame.nextHandler + while cur !== null do + console.log(showHandlerContChain(cur, hl, vis, 0)) + set cur = cur.nextHandler console.log() else console.log("Not a cont trace:") @@ -366,10 +362,9 @@ fun debugEff(eff) = // runtime implementations fun unwind(...saved) = - val effect = curEffect as EffectSig set - effect.contTrace.lastFrame.next = new mut FunctionContFrame(null, saved) - effect.contTrace.last = effect.contTrace.lastFrame.next + curEffect.contTrace.last.next = new mut FunctionContFrame(null, saved) + curEffect.contTrace.last = curEffect.contTrace.last.next fun mkEffect(handler: Any, handlerFun: Function | Bool | null) = let res = new mut EffectSig(new mut ContTrace(null, null, null, null, false), handler, handlerFun) @@ -381,7 +376,7 @@ fun mkEffect(handler: Any, handlerFun: Function | Bool | null) = fun handleBlockImpl(cur: EffectSig, handler: Any) = let handlerFrame = new mut HandlerContFrame(null, null, handler) set - cur.contTrace.lastHandlerFrame.nextHandler = handlerFrame + cur.contTrace.lastHandler.nextHandler = handlerFrame cur.contTrace.lastHandler = handlerFrame cur.contTrace.last = handlerFrame handleEffects(cur) @@ -391,7 +386,7 @@ fun enterHandleBlock(handler: Any, body: () -> Any) = if curEffect === null then tmp else - handleBlockImpl(curEffect as EffectSig, handler) + handleBlockImpl(curEffect, handler) fun handleEffects(cur: Any): Any = while cur is @@ -410,15 +405,15 @@ fun handleEffect(cur: EffectSig): Any = // debugEff(cur) // find the handle block corresponding to the current effect let prevHandlerFrame = cur.contTrace - while prevHandlerFrame.nextHandler is ~null as next and next.handler !== cur.handler do - set prevHandlerFrame = next + while prevHandlerFrame.nextHandler !== null and prevHandlerFrame.nextHandler.handler !== cur.handler do + set prevHandlerFrame = prevHandlerFrame.nextHandler // no matching handle block if prevHandlerFrame.nextHandler === null do return cur // the matching handle block - let handlerFrame = prevHandlerFrame.nextHandler as HandlerContFrame + let handlerFrame = prevHandlerFrame.nextHandler // unlink and save frames let saved = new mut ContTrace( @@ -442,15 +437,15 @@ fun handleEffect(cur: EffectSig): Any = set curEffect = null let tmp = set stackDepth += 2 in (cur.handlerFun as Function)(resume(cur.contTrace)) if curEffect !== null then - set cur = curEffect as EffectSig + set cur = curEffect // relink the saved frames if saved.next !== null do - set cur.contTrace.lastFrame.next = saved.next + set cur.contTrace.last.next = saved.next if saved.last !== saved do set cur.contTrace.last = saved.last if saved.nextHandler !== null do set - cur.contTrace.lastHandlerFrame.nextHandler = saved.nextHandler + cur.contTrace.lastHandler.nextHandler = saved.nextHandler cur.contTrace.lastHandler = saved.lastHandler cur else @@ -467,25 +462,25 @@ fun resumeContTrace(contTrace: ContTrace, value: Any): Any = let cont = contTrace.next let handlerCont = contTrace.nextHandler while - cont is FunctionContFrame as frame then + cont is FunctionContFrame then set curEffect = null - set value = set stackDepth += 3 in frame.resume(value) + set value = set stackDepth += 3 in cont.resume(value) if curEffect !== null do set value = curEffect if value is EffectSig as effect then set - effect.contTrace.lastFrame.next = frame.next - effect.contTrace.lastHandlerFrame.nextHandler = handlerCont + effect.contTrace.last.next = cont.next + effect.contTrace.lastHandler.nextHandler = handlerCont if contTrace.last !== cont do set effect.contTrace.last = contTrace.last if handlerCont !== null do set effect.contTrace.lastHandler = contTrace.lastHandler return value else - set cont = frame.next - handlerCont is HandlerContFrame as frame then - set cont = frame.next - set handlerCont = frame.nextHandler + set cont = cont.next + handlerCont is HandlerContFrame then + set cont = handlerCont.next + set handlerCont = handlerCont.nextHandler else return value diff --git a/hkmc2/shared/src/test/mlscript-compile/Shape.mls b/hkmc2/shared/src/test/mlscript-compile/Shape.mls index 791c75a702..1b0a83f50b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Shape.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Shape.mls @@ -28,7 +28,7 @@ fun show(s: Shape) = fun sel(s1: Shape, s2: Shape): Array[Shape] = if [s1, s2] is - [Class(ConcreteClassSymbol(_, _, Some(args), _), params), Lit(n)] and n is Str as n then + [Class(ConcreteClassSymbol(_, _, Some(args), _), params), Lit(n)] and n is Str then let i = args.findIndex(pass1((arg: Block.Symbol) => arg.name == n)) if i >= 0 then [params.(i)] else [] [Dyn, Lit(n)] and n is Str diff --git a/hkmc2/shared/src/test/mlscript-compile/Term.mls b/hkmc2/shared/src/test/mlscript-compile/Term.mls index 18392e711c..78b2a6548e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Term.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Term.mls @@ -74,7 +74,7 @@ class Term with class Context(val names: Map[Str, Int], val bindings: Map[Symbol, Str], val dependencies: Set[Str], val printOnly: Bool) with fun get(sym: Symbol): Str = - if bindings.get(sym) is Str as name then name + if bindings.has(sym) then bindings.get(sym) else if printOnly then sym.name else throw Error(StrOps.concat("Invalid binding name ", sym.name)) fun nest: Context = Context(names, new Map(bindings), dependencies, printOnly) @@ -84,7 +84,9 @@ class Context(val names: Map[Str, Int], val bindings: Map[Symbol, Str], val depe fn fun depends(d: Str) = dependencies.add(d) fun freshName(name: Str): Str = - let i = if names.get(name) is Int as i then i else 0 + if not names.has(name) do + names.set(name, 0) + let i = names.get(name) names.set(name, i + 1) StrOps.concat of name, "_", i.toString() diff --git a/hkmc2/shared/src/test/mlscript-compile/XML.mls b/hkmc2/shared/src/test/mlscript-compile/XML.mls index 4433912538..988afeb0b1 100644 --- a/hkmc2/shared/src/test/mlscript-compile/XML.mls +++ b/hkmc2/shared/src/test/mlscript-compile/XML.mls @@ -23,7 +23,7 @@ fun serializeValue(value) = if value is fun joinAttributes(attributes) = attributes mapping of case [name, value] then name + "=" + serializeValue(value) - StyleAttributeValue as style then "style=" + (style as StyleAttributeValue).toValue() + StyleAttributeValue as style then "style=" + style.toValue() Object as record then joinAttributes(Object.entries(record)) joined(" ") diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls index d64efb20fb..30d6e68b0e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls @@ -32,10 +32,10 @@ fun toArrays(strData: Str) = // Keep looping over the regular expression matches until we can no longer find a match. while let arrMatches = objPattern.exec(strData) - arrMatches is Array as matches do + arrMatches !== null do // Get the delimiter that was found. - let strMatchedDelimiter = matches.1 + let strMatchedDelimiter = arrMatches.1 // Check to see if the given delimiter has a length (is not the start of string) // and if it matches field delimiter. @@ -47,17 +47,17 @@ fun toArrays(strData: Str) = // Now that we have our delimiter out of the way, // let's check to see which kind of value we captured (quoted or unquoted). let strMatchedValue = - if matches.2 != undefined + if arrMatches.2 != undefined then // We found a quoted value. When we capture this value, unescape any double quotes. - matches.2.replace of + arrMatches.2.replace of new mut RegExp("\"\"", "g") "\"" else // We found a non-quoted value. - matches.3 + arrMatches.3 - (arrData.at(arrData.length - 1) as Array).push(strMatchedValue) + arrData.at(arrData.length - 1).push(strMatchedValue) arrData diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls index 5092cd9150..8b90021db6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls @@ -98,7 +98,7 @@ fun parseIndentedText(text: Str) = text.split("\n") Iter.filtering((line: Str) => line.trim().length > 0) Iter.each of (line: Str) => - let indent = (indentRegex.exec(line) as Array[Str]).[1].length + let indent = indentRegex.exec(line).1.length let text = line.substring(indent) while indent <= stack.[stack.length - 1].indent do stack.pop() @@ -173,7 +173,8 @@ class ErrorDisplay extends HTMLElement with this.render() fun render() = if _error is Some(error) do - let stackLines = if error.stack is Str as stack then stack.split("\n") else mut [] + let stack = error.stack + let stackLines = if stack is Str then stack.split("\n") else mut [] if stackLines.length > 0 and stackLines.[0].startsWith(error.name) do stackLines.shift() set this.shadowRoot.innerHTML = elem("div", "class": "error-container") of diff --git a/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls b/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls index ed7444afaa..a10ecf6a8e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Pwd.mls @@ -3,7 +3,7 @@ :sjs -let folderName1 = (process.env!PWD as Str).split("/").pop() +let folderName1 = process.env!PWD.split("/").pop() in let folderName2 = process.cwd().split("/").pop() in folderName2 === folderName1 || folderName2 === "shared" //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls index 430cb58600..8815cff96a 100644 --- a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls @@ -35,19 +35,19 @@ ys |> ith(8) ys |> ith(9) //│ = None -let ys = if ys |> popFront is {rest: rest} then rest +let ys = (ys |> popFront).rest //│ ys = [2, 3, 1, 2, 3, 1, 2, 3] -let ys = if ys |> popBack is {rest: rest} then rest +let ys = (ys |> popBack).rest //│ ys = [2, 3, 1, 2, 3, 1, 2] -let ys = if ys |> popBack is {rest: rest} then rest +let ys = (ys |> popBack).rest //│ ys = [2, 3, 1, 2, 3, 1] -let ys = if ys |> popBack is {rest: rest} then rest +let ys = (ys |> popBack).rest //│ ys = [2, 3, 1, 2, 3] -let ys = if ys |> popFront is {rest: rest} then rest +let ys = (ys |> popFront).rest //│ ys = [3, 1, 2, 3] -let ys = if ys |> popFront is {rest: rest} then rest +let ys = (ys |> popFront).rest //│ ys = [1, 2, 3] -let ys = if ys |> popFront is {rest: rest} then rest +let ys = (ys |> popFront).rest //│ ys = [2, 3] let ys = xs ++ xs ++ xs From 5ddaf499729fb96722d670cc26d970c80488badf Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 22:16:31 +0800 Subject: [PATCH 187/228] Restore direct accesses after lenient resolution --- hkmc2/shared/src/test/mlscript-compile/Block.mls | 4 ++-- .../test/mlscript-compile/apps/parsing-web-demo/main.mls | 3 +-- .../src/test/mlscript-compile/apps/parsing/Keywords.mls | 9 ++++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Block.mls b/hkmc2/shared/src/test/mlscript-compile/Block.mls index a088650eee..8ed66e70ec 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Block.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Block.mls @@ -101,8 +101,8 @@ fun showLiteral(l: Literal) = if l is undefined then "undefined" null then "null" - Str then "\"" + String(l) + "\"" - else String(l) + Str then "\"" + l.toString() + "\"" + else l.toString() fun showSymbol(s: Symbol) = s.name.replaceAll("$", "_") diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls index 8b90021db6..403b32cf58 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing-web-demo/main.mls @@ -173,8 +173,7 @@ class ErrorDisplay extends HTMLElement with this.render() fun render() = if _error is Some(error) do - let stack = error.stack - let stackLines = if stack is Str then stack.split("\n") else mut [] + let stackLines = error.stack.split("\n") if stackLines.length > 0 and stackLines.[0].startsWith(error.name) do stackLines.shift() set this.shadowRoot.innerHTML = elem("div", "class": "error-container") of diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls index 3cf102bfcf..56676cfb29 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/parsing/Keywords.mls @@ -27,11 +27,14 @@ class Keyword(val name: Str, val leftPrec: Some[Int] | None, val rightPrec: Some (if rightPrec is Some(prec) then prec.toString() else "N/A"), ")" fun makePrecMap(startPrec: Int, ...ops: Array[Str]) = - let m = MutMap.empty - ops.forEach of (names, i, _) => - names.split(" ").forEach of (op, _, _) => + let + m = MutMap.empty + i = 0 + while i < ops.length do + ops.at(i).split(" ").forEach of (op, _, _) => if op.length > 0 do m |> MutMap.insert of op, i + startPrec + set i += 1 m val all: MutMap.MutMap[Str, Keyword] = MutMap.empty From c46cfde995c7e2ca2adc8c02b4da39b3548a17a2 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 22:35:44 +0800 Subject: [PATCH 188/228] Fix subclass pattern refinement of self references --- .../scala/hkmc2/semantics/NewResolver.scala | 21 +++-- .../ups/EvaluationContext.mls | 16 ++-- .../mlscript/newres/SelfPatternRefinement.mls | 88 +++++++++++++++++++ 3 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/SelfPatternRefinement.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 01ed999924..4aa9122344 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2193,7 +2193,15 @@ class NewResolver: resolution.publish(TypeShape.Nominal(cls)) declaredType(resolution, Map.empty) }) - def narrow(sh: TermShape)(using NewResolverState): Unit = sh match + def subclass(base: ClassLikeDef)(using NewResolverState): Unit = + // A base-class annotation or self reference can denote a subclass + // instance. The test exposes that subclass's declared fields even + // when no constructor call supplies a concrete receiver shape. + // Omitted subclass arguments remain abstract and share the cached + // type, rather than borrowing types from unrelated allocations. + atPattern(testedClass): candidate => + if candidate.isInstanceOfClass(base) then check(candidate, N) + def narrow(sh: TermShape)(using NewResolverState): Unit = shapeParts(sh).value match case Marked(_: RecordTypeShape, _) => atPattern(testedClass)(check(_, N)) case Marked(unknown: UnknownValueShape, _) => @@ -2222,14 +2230,9 @@ class NewResolver: case value: TermShape => narrow(value) case NoShape => () case Marked(nominal: NominalInstanceView, _) if !nominal.isInstanceOfClass(cls) => - // A base-class annotation can contain a subclass instance. Its - // constructor test exposes that subclass's declarations; omitted - // subclass type arguments remain abstract, never inferred from - // unrelated constructor calls elsewhere in the unit. - // Reuse the abstract instantiation when recursive flow reaches - // this test again, so its omitted arguments have stable identities. - atPattern(testedClass): candidate => - if candidate.isInstanceOfClass(nominal.defn) then check(candidate, N) + subclass(nominal.defn) + case Marked(base: BaseShape, _) if !base.isInstanceOfClass(cls) => + subclass(base.defn) case _ => check(sh, N) shape match case sh: TermShape => narrow(sh) diff --git a/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls b/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls index 1fcd89f770..7fae80829a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls +++ b/hkmc2/shared/src/test/mlscript-compile/ups/EvaluationContext.mls @@ -22,8 +22,8 @@ data class Term with None then let fv = if this is Var(name) then new Set([name]) - App(lhs, rhs) then (lhs as Term).freeVars.union((rhs as Term).freeVars) - Abs(name, body) then (body as Term).freeVars.difference(new Set([name])) + App(lhs, rhs) then lhs.freeVars.union(rhs.freeVars) + Abs(name, body) then body.freeVars.difference(new Set([name])) set _freeVars = Some(fv) fv @@ -43,18 +43,18 @@ data class Term with fun equals(that) = if this is Var(name) and that is Var(name') then name === name' App(lhs, rhs) and that is App(lhs', rhs') then - (lhs as Term).equals(lhs') && (rhs as Term).equals(rhs') + lhs.equals(lhs') && rhs.equals(rhs') Abs(Var(name), body) and that is Abs(Var(name'), body') then - name === name' && (body as Term).equals(body') + name === name' && body.equals(body') else false // Pretty-print the term. fun show = if this is Var(name) then name - App(Abs as lhs, rhs) then "(" + (lhs as Term).show + ") " + (rhs as Term).show - App(lhs, App as rhs) then (lhs as Term).show + " (" + (rhs as Term).show + ")" - App(lhs, rhs) then (lhs as Term).show + " " + (rhs as Term).show - Abs(Var(name), body) then "λ" + name + ". " + (body as Term).show + App(Abs as lhs, rhs) then "(" + lhs.show + ") " + rhs.show + App(lhs, App as rhs) then lhs.show + " (" + rhs.show + ")" + App(lhs, rhs) then lhs.show + " " + rhs.show + Abs(Var(name), body) then "λ" + name + ". " + body.show fun subst(term, name, target: Term) = if term is Var(name') and diff --git a/hkmc2/shared/src/test/mlscript/newres/SelfPatternRefinement.mls b/hkmc2/shared/src/test/mlscript/newres/SelfPatternRefinement.mls new file mode 100644 index 0000000000..abe3d81d3f --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/SelfPatternRefinement.mls @@ -0,0 +1,88 @@ +:. + +// A base-class method can run on a subclass. Matching this must expose the +// subclass's declared fields, including recursive fields of the base type. +:expect 3 +data class Expr with + constructor + Lit(value: Int) + Add(lhs: Expr, rhs: Expr) + fun eval: Int = if this is + Lit(value) then value + Add(lhs, rhs) then lhs.eval + rhs.eval +Add(Lit(1), Lit(2)).eval +//│ = 3 + +// The receiver can be captured by a nested function; aliases and nested +// constructor patterns must retain the subclass interface and lexical path. +:expect [3, 0, 4] +class Reader with + fun read(): Int = + let self = this + fun nested() = if self is Text as text then text.value.length else 0 + nested() + fun first(): Int = if this is Pair(Text as text, _) then text.value.length else 0 +class Text(val value: Str) extends Reader +class Pair(val left: Reader, val right: Reader) extends Reader +[Text("abc").read(), (new Reader).read(), Pair(Text("four"), Text("five")).first()] +//│ = [3, 0, 4] + +// Each enclosing generic activation retains its own field type when a local +// subclass is matched by an inherited method. +:expect [5, 6] +class First(val first: Int) +class Second(val second: Int) +private fun make[A](item: A) = + class Parent with + fun read() = if this is Child(items) then items.0 else item + class Child(val items: Array[A]) extends Parent + Child([item]) +[make(First(5)).read().first, make(Second(6)).read().second] +//│ = [5, 6] + +// A concrete allocation is not an abstract self reference. An impossible +// subclass branch must not add an extra candidate to the result. +:expect 7 +class ConcreteBase +class ConcreteChild extends ConcreteBase +let result = if new ConcreteBase is ConcreteChild as child then child else [7] +result.0 +//│ = 7 +//│ result = [7] + +// Refinement supplies the declared field interface, not arbitrary members. +:e +data class Bad with + constructor + Entry(text: Str) + fun read = if this is Entry(text) then text.absent +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.58: fun read = if this is Entry(text) then text.absent +//│ ║ ^^^^^^^^^^^ +//│ ╟── Value of type 'Str' does not contain member 'absent' +//│ ║ l.57: Entry(text: Str) +//│ ╙── ^^^ + +// A generic subclass's omitted argument stays abstract even when a local +// allocation happens to supply a string. +:e +class Parent with + fun read = if this is Child(item) then item.length else 0 +class Child[A](val item: A) extends Parent +Child("abc").read +//│ ╔══[COMPILATION ERROR] This selection of member 'length' has no resolved target +//│ ║ l.70: fun read = if this is Child(item) then item.length else 0 +//│ ╙── ^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Exercise the imported recursive methods whose pattern bindings previously +// needed casts, including nested aliases in the pretty-printer. +:expect [true, true, "x y", "(λx. x) y", true, false] +import "../../mlscript-compile/ups/EvaluationContext.mls" +open EvaluationContext +let term = App(Var("x"), Var("y")) +[term.freeVars.has("x"), term.freeVars.has("y"), term.show, + App(Abs(Var("x"), Var("x")), Var("y")).show, + term.equals(App(Var("x"), Var("y"))), term.equals(App(Var("x"), Var("z")))] +//│ = [true, true, "x y", "(λx. x) y", true, false] +//│ term = App(Var("x"), Var("y")) From acfbefbca7b94330632847a465a981e86cca034a Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 23:31:18 +0800 Subject: [PATCH 189/228] Approximate Boolean constraints during member resolution --- doc/new-resolution-regular-types.md | 11 ++- doc/new-resolution-type-value-flow.md | 17 +++- doc/reference.md | 6 ++ .../test/scala/hkmc2/TypeRelationTest.scala | 87 ++++++++++------- .../scala/hkmc2/semantics/NewResolver.scala | 72 ++++++++++---- .../hkmc2/semantics/NewResolverState.scala | 3 +- .../newres/BooleanConstraintApproximation.mls | 96 +++++++++++++++++++ .../src/test/mlscript/newres/ReslScratch.mls | 21 ++++ 8 files changed, 246 insertions(+), 67 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/BooleanConstraintApproximation.mls diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index d70932ecc2..0a50e2f29a 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -89,11 +89,12 @@ A | (A & Array[A]) = A ((A | Int) & Str | Int) & Str = (A | Int) & Str ``` -`TypeShape.Combined` retains the formula as one type. In particular, supplying a -union still creates one instance wrapper, and a negative obligation against that -union stays whole. Member-interface observation can inspect its components, as -for source unions and intersections. Normalization does not turn one supplied -union into several independently supplied types. +`TypeShape.Combined` retains the formula as one type. Supplying a union still +creates one instance wrapper. Constraint propagation approximates an upper union +by constraining every component, and observes lower intersection components +independently. Member lookup still merges record intersection interfaces. +Normalization itself does not turn one supplied union into several independently +supplied types. Formula atoms retain their lexical bindings, canonical binder instances, and ordinary marks. Different written references to the same unbound parameter and diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index b9dee9de47..64d3d68ea8 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -25,11 +25,13 @@ An ordinary integer argument to `x: A` contributes only a lower bound. For neither argument must satisfy the other's type. If two distinct supplied types `Int` and `Str` reach the same activation of `A`, -each obligation applies to both. Supplying the single type `Int | Str` instead -retains that complete type as the negative constraint target. Positive union -elimination does not justify splitting a negative union into conjunctive obligations. -Concrete mismatches such as `Str <: Int` can remain quiet during migration; this -does not permit dropping the corresponding constraints. +each obligation applies to both. Resolution also approximates an upper union by +constraining both components: `L <: A | B` propagates into both `A` and `B`. A lower +intersection supplies either component independently when inspecting its interface +for constraints. These rules collect possible resolution targets without solving +alternative constraint sets or forming products of intersection candidates. +They deliberately approximate type inference; concrete incompatibilities such as +`Str <: Int` do not currently produce resolution diagnostics. ## Instance wrappers and interface observations @@ -39,6 +41,11 @@ application, or destructuring requests an interface through `listenInstanceViews The type itself stays in the `TypeResolution`/`TypeShape` graph. A written interface restricts observation even when more specific implementation values reach it. +Member lookup merges the fields of record intersections. Constraint inspection +instead observes each component independently, including through aliases, type +parameters, and inference holes. The type-view cache distinguishes these two +observations so inferred arguments cannot change the interface of an annotation. + A `DeclaredType` contains: - A source `TypeResolution` node. diff --git a/doc/reference.md b/doc/reference.md index 2fa3afcd8f..76fe059324 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -1406,6 +1406,12 @@ annotations therefore do not always preserve the precision of a wholly unannotat parameter. Concrete type-mismatch diagnostics are also incomplete during the new-resolution migration. +Type-argument inference approximates a union in an expected type by propagating +constraints into every component. An intersection in an actual type contributes +its components independently. Resolution uses these approximations to collect +member targets without searching alternative constraint sets. Ordinary member +lookup still combines the fields of record intersections. + ### Structural Record Types ```mlscript diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 62cdb7663d..76e7c63b6e 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -3,7 +3,7 @@ package semantics import org.scalatest.funsuite.AnyFunSuite import scala.collection.mutable.ArrayBuffer -import hkmc2.syntax.{Fun, Tree} +import hkmc2.syntax.{Cls, Fun, Tree} import hkmc2.utils.*, shorthands.* @@ -389,32 +389,60 @@ class TypeRelationTest extends AnyFunSuite: assert(right.allocatedTypeInstanceCount == 0) detach() - // Primitive incompatibilities do not yet produce diagnostics, so diff tests - // cannot establish that these input obligations were recorded at all. Check - // the constraint targets directly, including a whole union versus its parts. - test("each concrete upper target receives later bounds and a union stays whole"): + test("closed nominal candidates do not multiply along generic substitution paths"): + // A worksheet can check the selected member, but cannot detect redundant + // candidates that multiply when a constraint cycle crosses generic callers. + val h = new Harness + import h.given + val name: Tree.Ident = Tree.Ident("Closed") + val symbol = ClassSymbol(Tree.TypeDef(Cls, name, N), name) + val defn = ClassDef.Plain(N, Cls, symbol, BlockMemberSymbol("Closed", Nil), Nil, N, + ObjBody(Term.Blk(Nil, Term.UnitVal())), N, Nil, Nil, N)(N) + h.state.lexicalTypeBinders(symbol) = Set.empty + val original = NominalInstanceView(defn, Map.empty, N)(N)(h.resolver) + val scheme = h.tpe(TypeShape.Top).resolution + val substitutions = (1 to 16).map: index => + val (parameter, _) = h.parameter(s"A$index") + h.state.instantiateTypeParameters(scheme, FlowSymbol.app(), List(parameter)) + val host = new TermShapeHost + var current: TermShape = original + (1 to 1000).foreach: index => + current = h.resolver.instantiateShape(current, substitutions(index % substitutions.size)) + host.publish(current) + assert(host.currentShapes.size == 1) + assert(h.state.allocatedTypeInstanceCount == substitutions.size) + + test("intersection inputs propagate independently and reuse their subscriptions"): + // Diff tests cover inferred interfaces. This also checks that one intersection + // component can propagate before the other has any candidates, and that replay + // of the relation does not grow subscriptions or allocate parameter instances. val h = new Harness import h.given val (a, at) = h.parameter("A") - val one = h.tpe(TypeShape.Unit) - val two = h.tpe(TypeShape.Abstract) - val writtenUnion = h.tpe(TypeShape.Union(one.resolution, two.resolution)) - val union = h.resolver.declaredType(writtenUnion.resolution, Map.empty) + val (b, bt) = h.parameter("B") + val (_, result) = h.parameter("Result") + val input = ContextualType(h.tpe(TypeShape.Intersection(at.resolution, bt.resolution)), Nil) + val expected = ContextualType(h.tpe(TypeShape.Tuple(result.resolution :: Nil)), Nil) + val seen = h.observe(ContextualType(result, Nil)) + def tuple(value: TermShape): TermShape = + InstanceShape(h.tpe(TypeShape.Tuple(h.tpe(TypeShape.Inferred(value)).resolution :: Nil))) val first = IntroShape(Term.UnitVal(), N) val second = DynShape() - h.resolver.publishParameter(a, first) - h.resolver.constrainTypes(ContextualType(at, Nil), ContextualType(one, Nil)) - h.resolver.constrainTypes(ContextualType(at, Nil), ContextualType(two, Nil)) - h.resolver.publishParameter(a, second) - for target <- List(one, two); bound <- List(first, second) do - assert(h.state.typeConstraints((target, bound, Nil))) - val (b, bt) = h.parameter("B") - val distinct = OpaqueTypeShape(Term.UnitVal())(TypeInterfaceReason.Unavailable(Term.UnitVal())) - h.resolver.constrainTypes(ContextualType(bt, Nil), ContextualType(union, Nil)) - h.resolver.publishParameter(b, distinct) - assert(h.state.typeConstraints((union, distinct, Nil))) - assert(!h.state.typeConstraints((one, distinct, Nil))) - assert(!h.state.typeConstraints((two, distinct, Nil))) + val left = tuple(first) + val right = tuple(second) + h.resolver.constrainTypes(input, expected) + h.resolver.publishParameter(a, left) + assert(seen.toList == List(first)) + h.resolver.publishParameter(b, right) + assert(seen.toSet == Set(first, second)) + val counts = (a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) + (1 to 1000).foreach: _ => + h.resolver.constrainTypes(input, expected) + h.resolver.publishParameter(a, left) + h.resolver.publishParameter(b, right) + assert((a.inferenceHost.listeners.size, b.inferenceHost.listeners.size) == counts) + assert(seen.size == 2) + assert(h.state.allocatedTypeInstanceCount == 0) test("supplied arguments receive input obligations instead of discarding them"): val h = new Harness @@ -433,21 +461,6 @@ class TypeRelationTest extends AnyFunSuite: assert(h.state.typeConstraints((target, bound, Nil))) assert(a.currentShapes.toSet == Set(InstanceShape(one), InstanceShape(two))) - test("a supplied union receives an input obligation as one type reference"): - val h = new Harness - import h.given - val (a, at) = h.parameter("A") - val one = h.tpe(TypeShape.Unit) - val two = h.tpe(TypeShape.Abstract) - val writtenUnion = h.tpe(TypeShape.Union(one.resolution, two.resolution)) - val union = h.resolver.declaredType(writtenUnion.resolution, Map.empty) - val bound = IntroShape(Term.UnitVal(), N) - h.state.markExplicitTypeArgument(a) - h.resolver.publishParameter(a, InstanceShape(union)) - h.resolver.constrainTypes(ContextualType(h.tpe(TypeShape.Inferred(bound)), Nil), ContextualType(at, Nil)) - assert(h.state.typeConstraints((union, bound, Nil))) - assert(!h.state.typeConstraints((one, bound, Nil))) - assert(!h.state.typeConstraints((two, bound, Nil))) test("supplied parameter references forward input obligations across marked contexts"): val h = new Harness diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 4aa9122344..90e6d090f9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -259,8 +259,7 @@ class NewResolver: // This immutable declaration metadata belongs to the original // graph, even when the type was reached through an imported AST. if !unguardedOnly then - direct ++= defn.sym.getState.newResolverState.lexicalTypeBinders.get(defn.sym).getOrElse( - lastWords(s"Nominal declaration ${defn.sym.nme} must record its enclosing type binders")) + direct ++= nominalLexicalBinders(defn) case TypeShape.Alias(symbol, rhs) => val bound = symbol.defn.get.tparams.map(_.sym).toSet rhs.foreach(edge(_, bound)) @@ -839,9 +838,15 @@ class NewResolver: * observations before following parameters so recursive interfaces reach the * same graph nodes instead of allocating fresh inference variables. */ - private[semantics] def listenInstanceViews(value: TermShape)(listener: Listener)(using NewResolverState): Unit = value match + private[semantics] def listenInstanceViews(value: TermShape)(listener: Listener)(using NewResolverState): Unit = + listenInstanceViews(value, mergeIntersections = true)(listener) + + private def listenConstraintViews(value: TermShape)(listener: Listener)(using NewResolverState): Unit = + listenInstanceViews(value, mergeIntersections = false)(listener) + + private def listenInstanceViews(value: TermShape, mergeIntersections: Bool)(listener: Listener)(using NewResolverState): Unit = value match case Marked(instance: InstanceShape, marks) => - listenTypeViews(transportType(instance.tpe, marks :: Nil))(listener) + listenTypeViews(transportType(instance.tpe, marks :: Nil), mergeIntersections)(listener) case _ => listener(value) private def listenTermViews(term: Term)(listener: Listener)(using NewResolverState): Unit = @@ -863,7 +868,12 @@ class NewResolver: case tuple: TupleShape => tuple.copy(instances = instances.withOverrides(tuple.instances))(this) case record: RecordShape => record.copy(instances = instances.withOverrides(record.instances)) case nominal: NominalInstanceView => - val bindings = instances.map((parameter, instance) => parameter -> instanceType(instance)) ++ + // Own arguments are already bound; only enclosing binders can acquire + // new entries. Retaining unrelated callers' binders makes closed types + // such as Int distinct on each path through a cyclic constraint graph. + val lexical = nominalLexicalBinders(nominal.defn) + val bindings = instances.filter((parameter, _) => lexical(parameter)) + .map((parameter, instance) => parameter -> instanceType(instance)) ++ nominal.bindings.map((parameter, bound) => parameter -> bound.instantiate(instances)) nominal.copy(bindings = bindings, parent = nominal.parent.map(instantiateShape(_, instances)))(nominal.annotation)(this) case record: RecordTypeShape => @@ -924,6 +934,14 @@ class NewResolver: case other => publish(other) private def listenTypeViews(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = + listenTypeViews(tpe, mergeIntersections = true)(listener) + + /** Constraint inputs approximate an intersection by its separate alternatives. + * They must not wait for every component or form products of their candidates. + * Member lookup still merges record intersections, so cache the two kinds of + * observation separately and retain the choice through deferred references. + */ + private def listenTypeViews(tpe: DeclaredType, mergeIntersections: Bool)(listener: Listener)(using NewResolverState): Unit = // Equal types can be observed through different input-only arguments. Reuse // their semantic host, but report this observation's witness rather than the // parameter declaration retained by the host's first subscriber. @@ -933,11 +951,12 @@ class NewResolver: (shape, origin) match case (Marked(opaque: OpaqueTypeShape, marks), S(reason)) => listener(Marked(opaque.copy()(reason), marks)) case _ => listener(shape) - typeViews.get(tpe) match + val key = (tpe, mergeIntersections) + typeViews.get(key) match case S(host) => host.listen(observe) case N => val host = new TermShapeHost - typeViews(tpe) = host + typeViews(key) = host host.listen(observe) def follow(current: DeclaredType, args: TypeApplication, aliases: Set[TypeResolution], source: Term, publish: Listener)(using NewResolverState): Unit = @@ -969,11 +988,12 @@ class NewResolver: if !aliases(current.resolution) then listenArgumentParts(argument.instantiate(current.instances)): parts => follow(if positive then parts.output else parts.input, args, aliases + current.resolution, source, publish) - case TypeShape.Inferred(value) => listenInstanceViews(instantiateShape(value, current.instances))(publish) + case TypeShape.Inferred(value) => listenInstanceViews(instantiateShape(value, current.instances), mergeIntersections)(publish) case TypeShape.Hole(host) => host.subscribe: value => - listenInstanceViews(instantiateShape(value, current.instances))(publish) + listenInstanceViews(instantiateShape(value, current.instances), mergeIntersections)(publish) case TypeShape.Nominal(defn) => - val bindings = bind(defn.tparams) + val parameters = nominalLexicalBinders(defn) ++ defn.tparams.map(_.sym) + val bindings = bind(defn.tparams).filter((parameter, _) => parameters(parameter)) defn.ext match case N => publish(NominalInstanceView(defn, bindings, implicitParent(defn))(S(source))(this)) case S(parent) => @@ -985,7 +1005,7 @@ class NewResolver: val parentType = typeResolution(parent.cls) withTypeDependencies(parentType): dependencies => val local = captureNominalBindings(defn, bindings.filter((symbol, _) => dependencies(symbol))) - listenTypeViews(declaredType(parentType, local)): ext => + listenTypeViews(declaredType(parentType, local), mergeIntersections): ext => publish(NominalInstanceView(defn, bindings, S(ext))(S(source))(this)) case TypeShape.Alias(symbol, rhs) => // Revisiting the same alias reference without reaching a structural @@ -1013,7 +1033,7 @@ class NewResolver: unknown.copy()(unknown.provenance.withTypeOrigin(source)).exit(marks) case _ => value annotated match - case value: TermShape => listenInstanceViews(instantiateShape(value, current.instances))(publish) + case value: TermShape => listenInstanceViews(instantiateShape(value, current.instances), mergeIntersections)(publish) case NoShape => () case reference: TypeShape.Reference => follow(referenceType(reference, current), args, aliases, source, publish) @@ -1053,8 +1073,13 @@ class NewResolver: publish(RecordTypeShape(fields.map((field, sign) => field.sym.nme -> declaredType(sign, current)).toMap)( source, fields.map((field, _) => field.sym.nme -> field).toMap)) case TypeShape.Union(left, right) => next(left); next(right) + case TypeShape.Intersection(left, right) if !mergeIntersections => next(left); next(right) case TypeShape.Intersection(left, right) => conjunction(declaredType(left, current) :: declaredType(right, current) :: Nil, Nil) + case TypeShape.Combined(formula) if !mergeIntersections => + formula.orderedAtoms.foreach: atom => + val component = atom.instantiate(current.instances) + follow(component, noTypeArguments, aliases, component.resolution.source, publish) case TypeShape.Combined(formula) => // Choose witnesses in source order, independently of set hashing. val clauses = formula.clauses.toList.sortWith((left, right) => @@ -1323,7 +1348,7 @@ class NewResolver: seen: Set[TypeResolution])(using NewResolverState): Unit = if !seen(tpe.resolution) then val next = seen + tpe.resolution def observe(listener: Listener)(using NewResolverState): Unit = - listenInstanceViews(value): actual => + listenConstraintViews(value): actual => actual.enter(captures) match case actual: TermShape => listener(actual) case NoShape => () @@ -1372,12 +1397,17 @@ class NewResolver: case NoShape => () case _ => () case _ => () + case TypeShape.Union(left, right) => + follow(declaredType(left, tpe), noTypeArguments, captures, next) + follow(declaredType(right, tpe), noTypeArguments, captures, next) case TypeShape.Intersection(left, right) => follow(declaredType(left, tpe), noTypeArguments, captures, next) follow(declaredType(right, tpe), noTypeArguments, captures, next) - // Every conjunct is required. Disjunctions need alternative matching; - // propagating to every branch would instead impose their intersection. - case TypeShape.Combined(formula) if formula.clauses.size == 1 => + // Resolution approximates an upper union by constraining every component. + // This supplies bounds without choosing branches or retaining alternative + // constraint sets. Lower intersections likewise contribute independent + // candidates through listenConstraintViews, avoiding candidate products. + case TypeShape.Combined(formula) => formula.orderedAtoms.foreach(atom => follow(atom.instantiate(tpe.instances), noTypeArguments, captures, next)) case TypeShape.Record(_, fields) => observe(constrainRecord(fields.map((field, sign) => field -> declaredType(sign, tpe)), _, marks)) case TypeShape.Function(_, _) => observe(constrainFunction(tpe, _, marks)) @@ -1388,7 +1418,7 @@ class NewResolver: case TypeShape.SelectedArgument(argument, positive) => listenArgumentParts(argument.instantiate(tpe.instances)): parts => follow(if positive then parts.output else parts.input, args, captures, next) - case TypeShape.Nominal(cls) => listenInstanceViews(value): value => + case TypeShape.Nominal(cls) => listenConstraintViews(value): value => // Erasing a callback's signature must not erase the obligation to // check its implementation. Its unannotated inputs can receive any // value, independently of the calls observed through Function. @@ -1441,7 +1471,7 @@ class NewResolver: // A concrete target stays fixed, but must receive bounds that arrive // through a symbolic source later. Concrete mismatch reporting is // separate from retaining this obligation in the graph. - case Marked(_: InstanceShape, _) => listenInstanceViews(value)(inferTypeArguments(tpe, _, marks)) + case Marked(_: InstanceShape, _) => listenConstraintViews(value)(inferTypeArguments(tpe, _, marks)) case _ => () // Alias expansion guards only unproductive cycles. Descending into a tuple, // nominal argument, or arrow installs another edge with its own cycle guard. @@ -1484,7 +1514,7 @@ class NewResolver: val ShapeParts(actual, instances, _) = shapeParts(source) actual match case Marked(_: InstanceShape, _) => - listenInstanceViews(actual)(constrainFunction(expected, _, marks)) + listenConstraintViews(actual)(constrainFunction(expected, _, marks)) return case _ => () val context = actual.applicationHead._2 @@ -1678,6 +1708,10 @@ class NewResolver: case NoShape => parentView.getMember(name).withMarks(inner :: receiver :: Nil) inherited.withAnnotation(view.annotation) + private def nominalLexicalBinders(defn: ClassLikeDef): Set[VarSymbol] = + defn.sym.getState.newResolverState.lexicalTypeBinders.get(defn.sym).getOrElse( + lastWords(s"Nominal declaration ${defn.sym.nme} must record its enclosing type binders")) + /** Supplied class arguments are outside the instance scope. Both member and * parent annotations refer to those parameters from inside that scope. * Enclosing binders instead enter through their own explicit Capture nodes. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 43d8187d15..860c7c008d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -347,7 +347,8 @@ final class NewResolverState private ( }) assert(instances.keySet == parameters.toSet, "A source scheme's binders must remain stable") instances - val typeViews: Cache[DeclaredType, TermShapeHost] = + // The flag selects merged member interfaces versus independent constraint inputs. + val typeViews: Cache[(DeclaredType, Bool), TermShapeHost] = new Cache(inherited.map(_.typeViews), identity) val patternTypes: Cache[(Identity[Pattern.Constructor], InnerSymbol), DeclaredType] = new Cache(inherited.map(_.patternTypes), identity) diff --git a/hkmc2/shared/src/test/mlscript/newres/BooleanConstraintApproximation.mls b/hkmc2/shared/src/test/mlscript/newres/BooleanConstraintApproximation.mls new file mode 100644 index 0000000000..fb4c699395 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/BooleanConstraintApproximation.mls @@ -0,0 +1,96 @@ +:. + +// An upper union sends argument constraints into both sets of type parameters. +// Observe the element's members, not just a wrapper that contains the element. +:expect 3 +fun structural[A, B](x: Indexable[A] | Indexable[B]) = x.at(0) +fun runStructural(x: Indexable[Array]) = structural(x).length +runStructural([[1, 2, 3]]) +//│ = 3 + +fun nominal[A, B](x: Iterator[A] | Iterator[B]) = x.next() + +(x: Iterator[Array]) => nominal(x).value.length +//│ = fun + +// Explicit arguments retain their interfaces while nested variables still infer. +fun identity[T](x: T): T = x +fun supplied[A, B](x: Indexable[A] | Indexable[B]) = + identity[Indexable[A] | Indexable[B]](x).at(0) + +:expect 2 +fun runSupplied(x: Indexable[Array]) = supplied(x).length +runSupplied([[1, 2]]) +//│ = 2 + +// The same rule applies without record or nominal structure. +fun either[A, B](x: A | B) = x + +:expect 4 +either([1, 2, 3, 4]).length +//│ = 4 + +// Constraint inspection treats a lower intersection as independent candidates. +// Ordinary member lookup still combines the two declared record interfaces, +// including when the constraint view of that type has already been observed. +:expect [3, "SECOND"] +type Cell[A] = {value: A} +type First = {first: Int} +type Second = {second: Str} +fun extract[A](x: Cell[A]) = x.value +fun direct(x: Cell[First] & Cell[Second]) = + extract(x) + [x.value.first, x.value.second.toUpperCase()] +direct(value: {first: 3, second: "second"}) +//│ = [3, "SECOND"] + +// With eager diagnostics, the separate inferred candidates are observable: +// Second contributes a candidate without first, instead of merging into First. +:e +(x: Cell[First] & Cell[Second]) => extract(x).first +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.50: (x: Cell[First] & Cell[Second]) => extract(x).first +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Record type does not contain member 'first' +//│ ║ l.39: type Second = {second: Str} +//│ ╙── ^^^^^^^^^^^ +//│ = fun + +// The default lenient mode accepts a member supplied by either candidate. +:expect [5, "six"] +#lang(eagerResolution: false) +fun projected(x: Cell[First] & Cell[Second]) = + let value = extract(x) + [value.first, value.second] +projected(value: {first: 5, second: "six"}) +//│ = [5, "six"] + +// Nested generic calls retain the captures of each separate outer activation. +:expect [7, "eight"] +#lang(eagerResolution: true) +private fun enclosing[A](x: Cell[A]) = + fun inner[B](y: Cell[A] | Cell[B]) = y.value + inner(x) +[enclosing(value: {first: 7}).first, enclosing(value: {second: "eight"}).second] +//│ = [7, "eight"] + +// Recursive unions reuse the same constraint edges rather than expanding paths. +:expect 3 +type Node[A, B] = {item: A, next: Chain[B, A]} +type Chain[A, B] = Node[A, B] | Node[B, A] +class Link(val item: Array) with + val next = this +fun recursive[A, B](x: Chain[A, B]) = x.next.next.item +recursive(Link([1, 2, 3])).length +//│ = 3 + +// Nominal views discard unrelated substitutions but retain their own arguments +// and enclosing binders when union constraints cross generic calls. +:expect [1, "ABC", 2] +private fun captured[A](x: A) = + class Box[B](val second: B) with + fun first(): A = x + private fun pass[B, C](box: Box[B] | Box[C]) = box + pass(new Box[Array]([1, 2])) +[captured([1]).first().length, captured("abc").first().toUpperCase(), captured([1]).second.length] +//│ = [1, "ABC", 2] diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls index 3516720fbb..bf2f7e4062 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls @@ -227,3 +227,24 @@ foo({a: 1}) +fun test[A, B](x: Iterator[A] | Iterator[B]) = + x.next() + +(x: Iterator[Array]) => test(x).value +//│ = fun + + +fun test[A, B](x: Indexable[A] | Indexable[B]) = + x.at(0) + +(x: Indexable[Array]) => test(x).length +//│ = fun + + +fun test[A, B](x: Indexable[A]) = + x.at(0) + +(x: Indexable[Array]) => test(x).length +//│ = fun + + From 046991873699715ce08eaed83e204e0cd842c006 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 00:03:28 +0800 Subject: [PATCH 190/228] Cover function intersection constraints and migrate lastpiece --- doc/new-resolution-regular-types.md | 12 ++++-- doc/new-resolution-suite-migration.md | 12 +++--- doc/new-resolution-type-value-flow.md | 7 +++- doc/reference.md | 5 ++- .../scala/hkmc2/semantics/NewResolver.scala | 5 ++- .../test/mlscript-compile/nofib/lastpiece.mls | 14 ++++--- .../FunctionIntersectionConstraints.mls | 40 +++++++++++++++++++ 7 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/FunctionIntersectionConstraints.mls diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index 0a50e2f29a..0f953f7cb2 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -322,7 +322,11 @@ limitations use ordinary `:e` expectations. `TypeFormulaTest` checks normalization against Boolean truth tables. `TypeRelationTest` checks a thousand repeated reductions, -unchanged binder allocation and source-listener counts, late bounds, whole negative -union targets, and the loss of caller identity under captured alias rebasing. These -checks support normalization's local invariants; they do not establish the whole -resolver's termination argument. +unchanged binder allocation and source-listener counts, late bounds, independent +intersection propagation, and the loss of caller identity under captured alias +rebasing. These checks support normalization's local invariants; they do not +establish the whole resolver's termination argument. + +`newres/FunctionIntersectionConstraints.mls` checks that both function components +contribute result bounds and contravariant parameter bounds, with eager diagnostics +also observing each concrete result candidate. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 2fd4125716..42ac42ceff 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -49,11 +49,11 @@ compilation fixture; mixed-mode imports are supported. | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | -| Main | 45 | 4 | 49 | +| Main | 46 | 4 | 50 | | Applications | 18 | 2 | 20 | -| Nofib | 37 | 2 | 39 | +| Nofib | 38 | 1 | 39 | | WASM | 1 | 0 | 1 | -| Total | 101 | 8 | 109 | +| Total | 103 | 7 | 110 | `LegacyGenericLibrary` deliberately uses legacy resolution to test mixed-mode imports. The other legacy compilation fixtures are listed below. Counts describe @@ -66,8 +66,8 @@ source configuration, not the number of test cases reported by SBT. | `FingerTreeList` | New resolution exceeds the compilation time limit around recursive trees and tuple spreads. | | `CSP`, `QuoteExample1` | Quasiquote type selections and wildcard-reference lowering are unsupported. | | `parsing/Lexer` | Token constructors with trailing `using` parameters need automatic contextual argument insertion. `newres/LexerMigration` reproduces the missing behavior. | -| `parsing/ParseRule` | Recursive rule inference exceeds the compilation time limit when compiling consumers. | -| `nofib/lastpiece`, `nofib/sorting` | New resolution exceeds the compilation time limit. `sorting` also needs an `int_of_char(c: Str)` parameter annotation. | +| `parsing/ParseRule` | Recursive rule inference exceeds the compilation time limit when compiling consumers. Callback inputs exposed by `Iter.mapping` also leave `rule.map` with an unknown receiver. | +| `nofib/sorting` | New resolution exceeds the compilation time limit, including with the required `int_of_char(c: Str)` parameter annotation. | ## Remaining worksheet constraints @@ -112,7 +112,7 @@ following constraints identify the current failing behavior and representative t transformations lack result interfaces in `ups/examples/Computation` and `ups/examples/ListPredicates`. Evaluation-context and Hindley–Milner worksheets additionally need recursive member interfaces; `ups/examples/EvaluationContext` - exceeds the worksheet time limit. + has unknown `freeVars` results and unresolved `show` and `unapply` selections. - **Quasiquotes:** `codegen/Quasiquotes` needs type-selection and generated-reference support. Pattern and quote lowering must preserve source targets and receiver paths. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 64d3d68ea8..32e5cb70d8 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -27,8 +27,11 @@ neither argument must satisfy the other's type. If two distinct supplied types `Int` and `Str` reach the same activation of `A`, each obligation applies to both. Resolution also approximates an upper union by constraining both components: `L <: A | B` propagates into both `A` and `B`. A lower -intersection supplies either component independently when inspecting its interface -for constraints. These rules collect possible resolution targets without solving +intersection contributes every component separately when inspecting its interface +for constraints: `L1 & L2 <: R` installs both `L1 <: R` and `L2 <: R`. +For example, `(Int -> Int) & (Str -> Str) <: A -> B` constrains both arrows, +producing `A <: Int`, `Int <: B`, `A <: Str`, and `Str <: B`. +These rules collect possible resolution targets without solving alternative constraint sets or forming products of intersection candidates. They deliberately approximate type inference; concrete incompatibilities such as `Str <: Int` do not currently produce resolution diagnostics. diff --git a/doc/reference.md b/doc/reference.md index 76fe059324..94153c19e8 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -1408,8 +1408,9 @@ new-resolution migration. Type-argument inference approximates a union in an expected type by propagating constraints into every component. An intersection in an actual type contributes -its components independently. Resolution uses these approximations to collect -member targets without searching alternative constraint sets. Ordinary member +each component separately: `L1 & L2 <: R` installs both `L1 <: R` and `L2 <: R`. +Resolution uses these approximations to collect member targets without searching +alternative constraint sets. Ordinary member lookup still combines the fields of record intersections. ### Structural Record Types diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 90e6d090f9..bfc37bcae6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -936,8 +936,9 @@ class NewResolver: private def listenTypeViews(tpe: DeclaredType)(listener: Listener)(using NewResolverState): Unit = listenTypeViews(tpe, mergeIntersections = true)(listener) - /** Constraint inputs approximate an intersection by its separate alternatives. - * They must not wait for every component or form products of their candidates. + /** Constraint inspection propagates every intersection component separately. + * L1 & L2 <: R installs both L1 <: R and L2 <: R; it does not choose one. + * Do not wait for every component or form products of their candidates. * Member lookup still merges record intersections, so cache the two kinds of * observation separately and retain the choice through deferred references. */ diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/lastpiece.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/lastpiece.mls index bc6b506d43..fb7e6a9cf3 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/lastpiece.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/lastpiece.mls @@ -1,3 +1,5 @@ +#lang(0.3.x) + import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" open NofibPrelude @@ -8,7 +10,7 @@ module lastpiece with ... fun isSome(x) = x is Some -fun mapMaybe(f, ls) = if ls is +fun mapMaybe(f: Function, ls) = if ls is Nil then Nil h :: t and f(h) is None then mapMaybe(f, t) @@ -360,7 +362,7 @@ fun displayBoard(bd) = fun row(n) = hcat(map(col => sq(n, col), enumFromTo(1, maxCol))) above_(vcat(map(row, enumFromTo(1, maxCol))), false, text(Nil)) -fun eliminateEmpty(cons, p, g, q) = if p is +fun eliminateEmpty(cons: Function, p, g, q) = if p is Empty then q else [ NotEmpty, @@ -462,7 +464,7 @@ fun printDoc(d) = let done = "\n" :: Nil fullRender(ZigZagMode, 200, 1.5, put, done, d) -fun fullRender(m, l, r, txt, a, b) = +fun fullRender(m, l, r, txt: Function, a, b) = fun annTxt(p, x) = if p is NoAnnot(s, _) then txt(s, x) _ then x @@ -470,7 +472,7 @@ fun fullRender(m, l, r, txt, a, b) = fun ceiling(x) = globalThis.Math.ceil(x) -fun fullRenderAnn(m, lineLen, ribbons, txt, rest, doc) = if m is +fun fullRenderAnn(m, lineLen, ribbons, txt: Function, rest, doc) = if m is OneLineMode then easyDisplay(spaceText, (a, b) => b, txt, rest, reduceDoc(doc)) LeftMode then easyDisplay(nlText, first, txt, rest, reduceDoc(doc)) else @@ -479,7 +481,7 @@ fun fullRenderAnn(m, lineLen, ribbons, txt, rest, doc) = if m is let doc1 = best(bestLineLen, ribbonLen, reduceDoc(doc)) displayDoc(m, lineLen, ribbonLen, txt, rest, doc1) -fun easyDisplay(nlSpaceText, choose, txt, end, x) = +fun easyDisplay(nlSpaceText, choose: Function, txt: Function, end, x) = fun lay(x) = if x is NoDoc then throw Error("easyDisplay: NoDoc") Union(p, q) then lay(choose(p, q)) @@ -491,7 +493,7 @@ fun easyDisplay(nlSpaceText, choose, txt, end, x) = Beside(_, _, _) then throw Error("easyDisplay Beside") lay(x) -fun displayDoc(m, pageWidth, ribbonWidth, txt, end, doc) = +fun displayDoc(m, pageWidth, ribbonWidth, txt: Function, end, doc) = let gapWidth = pageWidth - ribbonWidth let shift = intDiv(gapWidth, 2) fun lay(k, docc) = diff --git a/hkmc2/shared/src/test/mlscript/newres/FunctionIntersectionConstraints.mls b/hkmc2/shared/src/test/mlscript/newres/FunctionIntersectionConstraints.mls new file mode 100644 index 0000000000..04cdf0bf67 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/FunctionIntersectionConstraints.mls @@ -0,0 +1,40 @@ +:. +#lang(eagerResolution: false) + +// Both arrows constrain A -> B: A <: Int, Int <: B, A <: Str, Str <: B. +// Each result member witnesses a different arrow; choosing just one loses it. +:expect ["12.0", "ABC"] +fun adapt[A, B](f: A -> B): A -> B = f +fun results(f: (Int -> Int) & (Str -> Str)) = + let g = adapt(f) + [g(12).toFixed(1), g("abc").toUpperCase()] +results(x => x) +//│ = ["12.0", "ABC"] + +// The same two constraints must propagate contravariantly into both domains. +// The only declared source of these fields is the argument passed to invoke. +// Leave domains uncalled so argument values cannot supply the missing fields. +fun invoke[A, B](f: A -> B, value: A): B = f(value) +private fun domains[X, Y](f: (X -> Int) & (Y -> Str), x: X, y: Y) = + invoke(f, {first: 1, second: "two"}) + [x.first, y.second] + +// Eager checking sees both concrete result candidates, including the one that +// lacks each member. The approximation does not turn the result into dyn. +:e +#lang(eagerResolution: true) +fun allResults(f: (Int -> Int) & (Str -> Str)) = + let g = adapt(f) + [g(0).toUpperCase(), g("").toFixed(0)] +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.28: [g(0).toUpperCase(), g("").toFixed(0)] +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Int' does not contain member 'toUpperCase' +//│ ║ l.26: fun allResults(f: (Int -> Int) & (Str -> Str)) = +//│ ╙── ^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.28: [g(0).toUpperCase(), g("").toFixed(0)] +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Value of type 'Str' does not contain member 'toFixed' +//│ ║ l.26: fun allResults(f: (Int -> Int) & (Str -> Str)) = +//│ ╙── ^^^ From e18f0d694748220acf9ef2f951fcb9ba11f1a20b Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 11:15:52 +0800 Subject: [PATCH 191/228] Fix buffered class resolution and evaluation context interfaces --- doc/new-resolution-suite-migration.md | 12 +- .../main/scala/hkmc2/codegen/Lowering.scala | 11 +- .../scala/hkmc2/semantics/Elaborator.scala | 4 + .../scala/hkmc2/semantics/NewResolver.scala | 13 ++- .../main/scala/hkmc2/semantics/Shape.scala | 26 +++++ .../src/main/scala/hkmc2/semantics/Term.scala | 10 +- .../test/mlscript-compile/ObjectBuffer.mls | 7 +- .../src/test/mlscript-compile/Runtime.mls | 6 +- .../src/test/mlscript/objbuf/BasicsObjBuf.mls | 107 ++++++++++++++---- .../annotations/AnnotationPrecedence.mls | 62 +++++----- .../ups/examples/EvaluationContext.mls | 26 +++-- 11 files changed, 194 insertions(+), 90 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 42ac42ceff..a69171353e 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -37,15 +37,15 @@ compilation fixture; mixed-mode imports are supported. | lifter | 11 | 5 | 16 | | meta | 7 | 0 | 7 | | nofib | 38 | 0 | 38 | -| objbuf | 1 | 2 | 3 | +| objbuf | 2 | 1 | 3 | | opt | 30 | 0 | 30 | | std | 8 | 2 | 10 | | syntax | 11 | 0 | 11 | | tailrec | 2 | 3 | 5 | | ucs | 69 | 1 | 70 | -| ups | 59 | 12 | 71 | +| ups | 60 | 11 | 71 | | wasm | 23 | 0 | 23 | -| Total | 526 | 109 | 635 | +| Total | 528 | 107 | 635 | | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | @@ -100,7 +100,6 @@ following constraints identify the current failing behavior and representative t - **Recursive definitions and staged functions:** `basics/FunDefs` and recursive getter cases in `tailrec` can overflow during resolution. `block-staging/Functions` and `codegen/FirstClassFunctionTransform` encounter unfinished generated symbols. - `objbuf/BasicsObjBuf` exceeds the worksheet time limit under new resolution. - **Legacy flow analysis:** the `flows` worksheets run a separate flow pass that does not handle new-resolution selections and can throw on `NewSel` nodes. - **Runtime instrumentation and privacy:** `codegen/ObjectMethodDebinding` loses @@ -110,9 +109,8 @@ following constraints identify the current failing behavior and representative t `ups/SimpleTransform`, `std/RenderingTest`, and regex worksheets use pattern values or direct `.unapply`/`.unapplyStringPrefix` access. Range and higher-order transformations lack result interfaces in `ups/examples/Computation` and - `ups/examples/ListPredicates`. Evaluation-context and Hindley–Milner worksheets - additionally need recursive member interfaces; `ups/examples/EvaluationContext` - has unknown `freeVars` results and unresolved `show` and `unapply` selections. + `ups/examples/ListPredicates`. `ups/examples/EvaluationContext2` and the + Hindley–Milner worksheets additionally need recursive member interfaces. - **Quasiquotes:** `codegen/Quasiquotes` needs type-selection and generated-reference support. Pattern and quote lowering must preserve source targets and receiver paths. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 683e13be4c..5e07c44977 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -328,15 +328,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu )(mod.toLoc) case _ => _defn reportAnnotations(defn, defn.extraAnnotations) - val bufferableAnnots = defn.annotations.flatMap: - case Annot.Trm(trm: SynthSel) => - if trm.sym.contains(ctx.builtins.annotations.buffered) then - S(false -> trm) - else if trm.sym.contains(ctx.builtins.annotations.bufferable) then - S(true -> trm) - else - N - case _ => N + val bufferableAnnots = defn.annotations.collect: + case annot @ Annot.Bufferable(keepInstances) => keepInstances -> annot if bufferableAnnots.length > 1 then raise(ErrorReport( msg"Only one of bufferable annotation is allowed." -> Loc(bufferableAnnots.map(_._2)) :: Nil, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 93fa3c908e..277594a1a0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -808,6 +808,10 @@ extends Importer: return S(Annot.Generator()(tree.toLoc)) case ctx.builtins.annotations.async => return S(Annot.Async()(tree.toLoc)) + case ctx.builtins.annotations.buffered => + return S(Annot.Bufferable(false)(tree.toLoc)) + case ctx.builtins.annotations.bufferable => + return S(Annot.Bufferable(true)(tree.toLoc)) case ctx.builtins.annotations.pure => return S(Annot.Pure()(tree.toLoc)) case _ => () diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index bfc37bcae6..87b7406eef 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2928,7 +2928,7 @@ class NewResolver: bms.onComplete: () => log(s"listenedBMS: bms = ${bms.describe}") valueTarget(bms, receiver) match - case S(sym: (ModuleOrObjectSymbol | TermSymbol | ClassSymbol)) => + case S(sym: (ModuleOrObjectSymbol | TermSymbol | ClassSymbol | PatternSymbol)) => // Selection is independent of the selected value's shape. In particular, // an assignment needs its target even if the value has no inferred shape. // Pattern resolution supplies its own interpretation of the selected head. @@ -2936,10 +2936,11 @@ class NewResolver: val wrappedListener: Listener = sh => log(s"fromBMS: bms = ${bms.showDbg}, sh = ${sh.shwDbg}, flow = ${resSym.showDbg}, markss = ${markss.map(_.showDbg)}") val sh0 = sh - // Modules and objects introduce no enter/exit boundary of their own. - // Adding an exit here would create a mismatch because we do not track module captures explicitly. + // Modules, objects, and runtime pattern objects introduce no value + // boundary. Pattern bodies have separate matching scopes; passing the + // generated matcher object does not enter or leave those scopes. val exited = sym match - case _: ModuleOrObjectSymbol => sh + case _: ModuleOrObjectSymbol | _: PatternSymbol => sh case sym: TermSymbol if sym.k is LetBind => sh case _ => MarkedShape.exit(sh, ResolutionBoundary(sym), S(resSym)) (exited match @@ -3070,8 +3071,8 @@ class NewResolver: * and opened names use the same interpretation as direct references. */ private def valueTarget(member: BlockMemberSymbol, receiver: Bool): Opt[DefinitionSymbol[?]] = - if receiver then member.asModOrObj.orElse(member.asTrm).orElse(member.asCls) - else member.asTrm.orElse(member.asModOrObj).orElse(member.asCls) + if receiver then member.asModOrObj.orElse(member.asTrm).orElse(member.asCls).orElse(member.asPat) + else member.asTrm.orElse(member.asModOrObj).orElse(member.asCls).orElse(member.asPat) def listenTerm(trm: Term)(listener: Listener)(using NewResolverState): Unit = log(s"listenTerm: trm = ${trm.showDbg}") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index f5bb465551..d6345d0444 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -654,10 +654,36 @@ class DefnShape(val defn: Definition, val ext: Opt[TermShape]) extends CoreHeadS }'${defn.bsym.nme}'" // override def toString: String = s"DefnShape(${defn.describe} ${defn.bsym.nme})" override def toString: String = s"DefnShape(${defn.describe})" + /** Shape-only counterparts of BufferableTransform's static members. The extra + * (buffer, index) list precedes the original method/constructor lists; it must + * not consume their arguments or expose instance fields as static members. + * These fields are never lowered: their symbols identify the generated names. + */ + private lazy val bufferedMembers: Map[Str, RecordMember] = defn match + case cls: ClassDef if cls.annotations.exists(_.isInstanceOf[Annot.Bufferable]) => + given Elaborator.State = cls.sym.getState + def field(name: Str, value: Term): (Str, RecordMember) = + name -> RecordMember(RcdField(Term.Lit(syntax.Tree.StrLit(name)), value), false) + def withBuffer(body: Term): Term = + val params = PlainParamList(List("buffer", "index").map: name => + Param.simple(VarSymbol(new syntax.Tree.Ident(name), erasedType = N)))(cls.toLoc) + Term.Lam(params, body) + // Constructors return the allocated index after all their original lists. + val ctor = (cls.paramsOpt.toList ::: cls.auxParams).foldRight[Term](Term.Lit(syntax.Tree.IntLit(0))): + (params, body) => Term.Lam(params, body) + val methods = cls.body.methods.filter(method => method.body.nonEmpty && !method.tsym.isPrivate).map: method => + val ref = Term.MemberRef(method.bsym)(new syntax.Tree.Ident(method.bsym.nme), FlowSymbol.memSym(method.bsym)) + field(method.bsym.nme, withBuffer(ref)) + (field("size", Term.Lit(syntax.Tree.IntLit(0))) :: + field("ctor", withBuffer(ctor)) :: methods).toMap + case _ => Map.empty + protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = defn match case defn: ModuleOrObjectDef => MemberLookup.inClass(defn, ext, name) + case cls: ClassDef if cls.annotations.exists(_.isInstanceOf[Annot.Bufferable]) => + bufferedMembers.get(name).fold[MemberLookup](MemberLookup.Missing)(MemberLookup.Found(_, Nil)) case _: TermDefinition => MemberLookup.Missing case _ => MemberLookup.Missing def toLoc: Opt[Loc] = defn.sym.toLoc diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 7736c433ff..61c5f7e635 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -40,6 +40,8 @@ enum Annot extends Located: case RaiseEffects()(val toLoc: Opt[Loc]) // Whether the function is guaranteed to not raise effects. case Pure()(val toLoc: Opt[Loc]) + // Buffered classes expose generated allocation and access methods on the class value. + case Bufferable(keepInstances: Bool)(val toLoc: Opt[Loc]) case Config(modify: hkmc2.Config => hkmc2.Config)(val toLoc: Opt[Loc]) // Marks if a function or lambda is one-shot, i.e. called at most once. // Functions with multiple parameter lists are considered here as a chain of @@ -60,7 +62,7 @@ enum Annot extends Located: def subTerms: Vector[Term] = this match case Trm(trm) => Vector.single(trm) case _: Modifier | Untyped() | TailRec() | TailCall() | Inline() | NoInline() - | Generator() | Async() | RaiseEffects() | Pure() | _: Config | _: Affine => Vector.empty + | Generator() | Async() | RaiseEffects() | Pure() | _: Bufferable | _: Config | _: Affine => Vector.empty def show(using Scope, ShowCfg, Raise): Document = this match case Untyped() => doc"@untyped" @@ -74,6 +76,7 @@ enum Annot extends Located: case Affine(n) => doc"@affine($n)" case Modifier(mod) => doc"@${mod.name}" case Pure() => doc"@pure" + case Bufferable(keepInstances) => if keepInstances then doc"@bufferable" else doc"@buffered" case Trm(trm) => doc"@${trm.show}" case Config(_) => doc"@config(...)" @@ -1538,10 +1541,7 @@ sealed abstract class ClassLikeDef extends TypeLikeDef: case _ => N def extraAnnotations(using Ctx): Ls[Annot] = annotations.filter: case Annot.Modifier(Keyword.`declare` | Keyword.`abstract` | Keyword.`data`) => false - case Annot.Trm(trm: SynthSel) if - (kind is Cls) && - (trm.sym.contains(ctx.builtins.annotations.bufferable) || - trm.sym.contains(ctx.builtins.annotations.buffered)) => false + case _: Annot.Bufferable if kind is Cls => false case _ => true diff --git a/hkmc2/shared/src/test/mlscript-compile/ObjectBuffer.mls b/hkmc2/shared/src/test/mlscript-compile/ObjectBuffer.mls index d031ff9bc1..111802c2fd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/ObjectBuffer.mls +++ b/hkmc2/shared/src/test/mlscript-compile/ObjectBuffer.mls @@ -6,7 +6,7 @@ fun max(a, b) = if a < b then b else a module ObjectBuffer with abstract class ObjectBuffer with val buf - fun mkNew(cls: { size: Int, ctor: (ObjectBuffer, Int) -> Any }) + fun mkNew[A](cls: { size: Int, ctor: (ObjectBuffer, Int) -> A }): A fun del(cls: { size: Int }, inst) fun read(idx) fun write(idx, v) @@ -19,9 +19,8 @@ module ObjectBuffer with buf.[0] = buf.length buf.[1] = -1 - fun mkNew(cls: { size: Int, ctor: (ObjectBuffer, Int) -> Any }) = - if cls.size === 0 do return 0 - val idx = _alloc(getBlockSize(cls.size)) + fun mkNew[A](cls: { size: Int, ctor: (ObjectBuffer, Int) -> A }): A = + val idx = if cls.size === 0 then 0 else _alloc(getBlockSize(cls.size)) cls.ctor(this, idx) fun del(cls: { size: Int }, inst) = diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 43921f7276..3bbafe44eb 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -85,9 +85,13 @@ fun _try(f: () -> Any) = // For `pattern` definitions -data class MatchSuccess(output, bindings) +data class MatchSuccess[out A, out B](output: A, bindings: B) data class MatchFailure(errors) +// Runtime pattern objects expose their transformed output through MatchSuccess. +type MatchResult[A] = MatchSuccess[A, Any] | MatchFailure +type Pattern[A, B] = { unapply: A -> MatchResult[B] } + // For pattern matching on tuples module Tuple with diff --git a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls index 4dc81a4357..a73c0e1c0f 100644 --- a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls +++ b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls @@ -1,3 +1,4 @@ +:. :js import "../../mlscript-compile/ObjectBuffer.mls" @@ -48,9 +49,9 @@ class A2(x) with @buffered class B0(x) //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.48: @buffered +//│ ║ l.49: @buffered //│ ║ ^^^^^^^^^ -//│ ║ l.49: class B0(x) +//│ ║ l.50: class B0(x) //│ ╙── ^^^^^^^^^^^ :ge @@ -58,16 +59,16 @@ class B0(x) @buffered class B1(x) //│ ╔══[COMPILATION ERROR] Only one of bufferable annotation is allowed. -//│ ║ l.57: @buffered -//│ ║ ^^^^^^^^^ //│ ║ l.58: @buffered +//│ ║ ^^^^^^^^^ +//│ ║ l.59: @buffered //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.57: @buffered -//│ ║ ^^^^^^^^^ //│ ║ l.58: @buffered //│ ║ ^^^^^^^^^ -//│ ║ l.59: class B1(x) +//│ ║ l.59: @buffered +//│ ║ ^^^^^^^^^ +//│ ║ l.60: class B1(x) //│ ╙── ^^^^^^^^^^^ :ge @@ -75,30 +76,30 @@ class B1(x) @bufferable class B2(x) //│ ╔══[COMPILATION ERROR] Only one of bufferable annotation is allowed. -//│ ║ l.74: @buffered +//│ ║ l.75: @buffered //│ ║ ^^^^^^^^^ -//│ ║ l.75: @bufferable +//│ ║ l.76: @bufferable //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.74: @buffered +//│ ║ l.75: @buffered //│ ║ ^^^^^^^^^ -//│ ║ l.75: @bufferable +//│ ║ l.76: @bufferable //│ ║ ^^^^^^^^^^^ -//│ ║ l.76: class B2(x) +//│ ║ l.77: class B2(x) //│ ╙── ^^^^^^^^^^^ :w @bufferable module B3 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.91: @bufferable +//│ ║ l.92: @bufferable //│ ╙── ^^^^^^^^^^^ :w @bufferable object B3 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.98: @bufferable +//│ ║ l.99: @bufferable //│ ╙── ^^^^^^^^^^^ @@ -177,11 +178,11 @@ class A with @untyped public data class LocatedBuffer(x) //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.176: @buffered +//│ ║ l.177: @buffered //│ ║ ^^^^^^^^^ -//│ ║ l.177: @untyped +//│ ║ l.178: @untyped //│ ║ ^^^^^^^^ -//│ ║ l.178: public data class LocatedBuffer(x) +//│ ║ l.179: public data class LocatedBuffer(x) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // The following declaration starts a fresh span. @@ -189,7 +190,75 @@ public data class LocatedBuffer(x) @buffered class NextLocatedBuffer(x) //│ ╔══[COMPILATION ERROR] Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead. -//│ ║ l.189: @buffered +//│ ║ l.190: @buffered //│ ║ ^^^^^^^^^ -//│ ║ l.190: class NextLocatedBuffer(x) +//│ ║ l.191: class NextLocatedBuffer(x) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +// A buffered method keeps all its original argument lists and result interface. +@annotations.buffered +class Curried with + constructor(value: Str) + fun append(suffix: Str)(last: Str): Str = value + suffix + last + fun valueLength: Int = value.length + fun echo[T](value: T): T = value + +:expect "abc" +let curried = buf.mkNew(Curried)("a") +Curried.append(buf, curried)("b")("c").toLowerCase() +//│ = "abc" +//│ curried = 6 + +:expect "1.0" +Curried.valueLength(buf, curried).toFixed(1) +//│ = "1.0" + +:expect ["ABC", "12.0"] +[Curried.echo(buf, curried)("abc").toUpperCase(), Curried.echo(buf, curried)(12).toFixed(1)] +//│ = ["ABC", "12.0"] + +// Even a zero-sized class must run its constructor. +:expect 1 +let initialized = 0 +@buffered +class Empty with + set initialized += 1 +let empty = buf.mkNew(Empty) +initialized +//│ = 1 +//│ empty = 0 +//│ initialized = 1 + +// Generated static members belong only to annotated classes and exclude fields. +:e +Curried.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.235: Curried.missing +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Class value does not contain member 'missing' +//│ ║ l.201: class Curried with +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +Curried.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.245: Curried.value +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Class value does not contain member 'value' +//│ ║ l.201: class Curried with +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +class Ordinary with + fun append(value: Str) = value +Ordinary.append +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.257: Ordinary.append +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Class value does not contain member 'append' +//│ ║ l.255: class Ordinary with +//│ ╙── ^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index 025e52a701..6486bc2960 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -26,10 +26,12 @@ fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). //│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Resolution error in member reference +//│ ╔══[COMPILATION ERROR] Resolution error in application //│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ║ ^ -//│ ╙── Expected a term; got pattern 'A' +//│ ║ ^^^^ +//│ ╟── Pattern 'A' cannot be called like a function. +//│ ║ l.8: pattern A(pattern B) = B +//│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y //│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^ @@ -51,51 +53,51 @@ fun foo(x) = if x is @annotations.compile (A(0) as y) then y :e fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.52: fun foo(x) = if x is @annotations.compile {A(0) as y} then y +//│ ║ l.54: fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.59: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.61: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.59: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.61: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.59: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.61: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.59: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.61: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.75: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.77: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.75: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.77: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.75: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.77: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.75: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.77: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ :e fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.90: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y +//│ ║ l.92: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╙── ^ :fixme fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.96: fun foo(x) = if x is (@annotations.compile A(0) as y) then y +//│ ║ l.98: fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╙── ^ @@ -107,14 +109,14 @@ type Test :w @Test 1 //│ ╔══[COMPILATION ERROR] Resolution error in member reference -//│ ║ l.108: @Test 1 +//│ ║ l.110: @Test 1 //│ ║ ^^^^^ //│ ╙── Expected a term; got type alias 'Test' //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.108: @Test 1 +//│ ║ l.110: @Test 1 //│ ║ ^^^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.108: @Test 1 +//│ ║ l.110: @Test 1 //│ ╙── ^ //│ = 1 @@ -122,14 +124,14 @@ type Test :w @Test 2 + 1 //│ ╔══[COMPILATION ERROR] Resolution error in member reference -//│ ║ l.123: @Test 2 + 1 +//│ ║ l.125: @Test 2 + 1 //│ ║ ^^^^^ //│ ╙── Expected a term; got type alias 'Test' //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.123: @Test 2 + 1 +//│ ║ l.125: @Test 2 + 1 //│ ║ ^^^^^ //│ ╟── This annotation is not supported on application terms. -//│ ║ l.123: @Test 2 + 1 +//│ ║ l.125: @Test 2 + 1 //│ ╙── ^^^^^ //│ = 3 @@ -137,14 +139,14 @@ type Test :w @Test 2 as Int //│ ╔══[COMPILATION ERROR] Resolution error in member reference -//│ ║ l.138: @Test 2 as Int +//│ ║ l.140: @Test 2 as Int //│ ║ ^^^^^ //│ ╙── Expected a term; got type alias 'Test' //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.138: @Test 2 as Int +//│ ║ l.140: @Test 2 as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.138: @Test 2 as Int +//│ ║ l.140: @Test 2 as Int //│ ╙── ^^^^^^^^ //│ = 2 @@ -152,14 +154,14 @@ type Test :w @Test id(2) as Int //│ ╔══[COMPILATION ERROR] Resolution error in member reference -//│ ║ l.153: @Test id(2) as Int +//│ ║ l.155: @Test id(2) as Int //│ ║ ^^^^^ //│ ╙── Expected a term; got type alias 'Test' //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.153: @Test id(2) as Int +//│ ║ l.155: @Test id(2) as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.153: @Test id(2) as Int +//│ ║ l.155: @Test id(2) as Int //│ ╙── ^^^^^^^^^^^^ //│ = 2 @@ -167,10 +169,10 @@ type Test :re (@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.168: (@Test) +//│ ║ l.170: (@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.168: (@Test) +//│ ║ l.170: (@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -178,10 +180,10 @@ type Test :re print(@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.179: print(@Test) +//│ ║ l.181: print(@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.179: print(@Test) +//│ ║ l.181: print(@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls b/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls index 57e4af7a1b..2fd23eb40f 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/EvaluationContext.mls @@ -1,3 +1,4 @@ +:. :js // This is an interesting experiment in evaluation context. @@ -21,7 +22,7 @@ data class Term with let _freeVars = None // Compute free variables. Retrieve the cache if it exists. - fun freeVars = if _freeVars is + fun freeVars: Set[Str] = if _freeVars is Some(fv) then fv None then let fv = if this is @@ -149,7 +150,7 @@ id_x <~> succ zero <~> false_ //│ = true -fun subst(term, name, target) = if term is +fun subst(term, name, target: Term) = if term is Var(name') and name === name' then target else term @@ -231,7 +232,7 @@ redex of id_x redex of Var("a") //│ ═══[RUNTIME ERROR] Error: match error -fun step(term) = +fun step(term): Term = if term is ByValue.Context(ByValue.Redex) as [subterm, remake] then remake(subterm) :re @@ -258,7 +259,7 @@ step(App(App(id_a, id_b), id_x)).show step(App(id_x, id_y)).show //│ = "λy. y" -fun evalByValue(term) = +fun evalByValue(term: Term): Term = while term is ByValue.Context(ByValue.Redex) as [subterm, remake] then set term = remake(subterm) @@ -281,16 +282,16 @@ fun evalByValue'(term) = set term = remake(subterm) else term //│ ╔══[COMPILATION ERROR] Pattern chaining is not supported in pattern compilation. -//│ ║ l.214: App(Abs as lhs, Context(Hole) as [rhs, remake]) => +//│ ║ l.215: App(Abs as lhs, Context(Hole) as [rhs, remake]) => //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Pattern chaining is not supported in pattern compilation. -//│ ║ l.217: App(Context(Hole) as [lhs, remake], Term as rhs) => +//│ ║ l.218: App(Context(Hole) as [lhs, remake], Term as rhs) => //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.282: else term +//│ ║ l.283: else term //│ ╙── ^^^^ //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.282: else term +//│ ║ l.283: else term //│ ╙── ^^^^ object ByName with @@ -317,7 +318,14 @@ print of t.show import "../../../mlscript-compile/Runtime.mls" -fun stepByStep(system, term, maxSteps) = +// An evaluation system supplies a redex pattern and a context matcher. Its +// successful output is a reduced subterm together with a reconstruction function. +type EvaluationSystem = { + Redex: Runtime.Pattern[Term, Term], + Context: { unapply: (Runtime.Pattern[Term, Term], Term) -> Runtime.MatchResult[[Term, Term -> Term]] } +} + +fun stepByStep(system: EvaluationSystem, term: Term, maxSteps: Int) = let numSteps = 0 print of "Term: " + term.show while numSteps < maxSteps and system.Context.unapply(system.Redex, term) is From eda9975843f8f527751ef272d18340a754c9fca1 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 11:30:08 +0800 Subject: [PATCH 192/228] Fix recursive getter resolution cycles --- doc/new-resolution-design.md | 16 +++ doc/new-resolution-suite-migration.md | 15 +- .../scala/hkmc2/semantics/NewResolver.scala | 32 ++++- .../hkmc2/semantics/NewResolverState.scala | 5 + .../test/mlscript-compile/GenericLibrary.mls | 5 + .../test/mlscript/newres/RecursiveGetters.mls | 132 ++++++++++++++++++ .../src/test/mlscript/newres/WIPTest.mls | 12 -- .../src/test/mlscript/tailrec/Simple.mls | 5 +- 8 files changed, 198 insertions(+), 24 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/RecursiveGetters.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 0ba6bee880..089c4c5775 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -178,6 +178,22 @@ disambiguation. Class projections, constructor patterns, and type references req known, unambiguous identities. See `newres/Dynamic.mls`, `Records.mls`, `SpreadCalls.mls`, and `loose/Targets.mls`. +## Recursive getter results + +Parameterless definition bodies have a shared result host, cached by definition +and generic activation before following the body. Recursive references subscribe +to that host instead of traversing the body again. An unproductive cycle has no +normal result candidates; a productive branch publishes candidates through the +cycle, with the publisher suppressing duplicates. No unknown or dynamic fallback +is introduced to stop recursion. + +The host stores results at the body's lexical endpoint, including their activation +events. Each reference still applies its own exit and capture marks. Resolver-state +caches and consumer-local publisher copies preserve this graph across imports +without sharing mutable inference between consumers. `newres/RecursiveGetters` +checks cycles, later candidates, captures, generic specializations, imports, and +runtime getter effects. + ## Normal results of control flow Resolution must agree with lowering about which expressions produce values. diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index a69171353e..12efa1d026 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -41,11 +41,11 @@ compilation fixture; mixed-mode imports are supported. | opt | 30 | 0 | 30 | | std | 8 | 2 | 10 | | syntax | 11 | 0 | 11 | -| tailrec | 2 | 3 | 5 | +| tailrec | 3 | 2 | 5 | | ucs | 69 | 1 | 70 | | ups | 60 | 11 | 71 | | wasm | 23 | 0 | 23 | -| Total | 528 | 107 | 635 | +| Total | 529 | 106 | 635 | | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | @@ -97,9 +97,12 @@ following constraints identify the current failing behavior and representative t in `handlers`, `codegen/ScopedBlocksAndHandlers`, and handler cases in `lifter`. Receiver flow, resumption arguments, and abortive results need distinct handling (`newres/HandlerResults`). -- **Recursive definitions and staged functions:** `basics/FunDefs` and recursive - getter cases in `tailrec` can overflow during resolution. `block-staging/Functions` - and `codegen/FirstClassFunctionTransform` encounter unfinished generated symbols. +- **Operator checks, tail-call annotations, and staged functions:** `basics/FunDefs` + needs negative expectations for calls to non-callable operator definitions. + `tailrec/Annots` and `tailrec/TailRecOpt` need `@tailcall` support for getter + references and selections; `Annots` also contains an unresolved handler receiver. + `block-staging/Functions` and `codegen/FirstClassFunctionTransform` encounter + unfinished generated symbols. - **Legacy flow analysis:** the `flows` worksheets run a separate flow pass that does not handle new-resolution selections and can throw on `NewSel` nodes. - **Runtime instrumentation and privacy:** `codegen/ObjectMethodDebinding` loses @@ -118,6 +121,8 @@ Focused coverage for capture identity and nominal interfaces is in `newres/CtxSe `ValCtxSens`, `Projections`, `RecursiveEnvironment`, `SpreadCalls`, `ScopePaths`, `InheritedTypeArguments`, and `InterfaceExposure`. These tests constrain fixes to recursive and exposed interfaces; unknown shapes must not silently become dynamic. +`newres/RecursiveGetters` covers cyclic getter inference, productive results, +captured and generic activations, imports, and invalid member access. ## Migration rules diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 87b7406eef..439a1e8409 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2832,7 +2832,7 @@ class NewResolver: case S(td: TermDefinition) if td.params.isEmpty => td.body match case S(body) => - listenTerm(body)(listener) + listenByNameBody(td, body)(listener) case N => ??? // TODO error case S(d) => @@ -2840,6 +2840,25 @@ class NewResolver: case N => sym.defnListeners += (d => listener(defnShapes.getOrElseUpdate(sym, DefnShape(d, N)))) + /** Close recursive getter dependencies before following the body. A cycle with + * no normal result publishes nothing; a productive branch can still publish + * later and propagate around the cycle until duplicate candidates stop it. + * Results stay at the body's lexical endpoint: each reference applies its own + * exit/capture marks after subscribing. Activation events retain substitutions + * when a deferred body candidate arrives through an enclosing generic call. + */ + private def listenByNameBody(td: TermDefinition, body: Term)(listener: Listener)(using NewResolverState): Unit = + assert(td.params.isEmpty) + val key = (td.tsym, rstate.instances) + rstate.byNameResults.get(key) match + case S(host) => listenValueHost(host)(listener) + case N => + val host = new Host[ShapeEvent]: + def showDbg(using DebugPrinter): Str = s"result of ${td.tsym.nme}" + rstate.byNameResults(key) = host + listenValueHost(host)(listener) + listenTerm(body)(publishActivated(host, _)) + def pipeTerm(from: Term, to: ShapeHost)(using NewResolverState): Unit = log(s"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.currentShapes}") listenTerm(from): sh => @@ -2972,7 +2991,7 @@ class NewResolver: case S(_: Param) => receive(MarkedShape.enter(shape, ResolutionBoundary(td.tsym), N)) case _ => receive(shape) case N => td.body.foreach: body => - listenTerm(body)(shape => + listenByNameBody(td, body)(shape => // Legacy synthesized fields use a plain reference to their // constructor parameter. Supply the field capture explicitly; // new-resolution fields already carry it in their body syntax. @@ -3127,12 +3146,15 @@ class NewResolver: case value: TermShape => receive(instantiateShape(value, rstate.instances)) case symbol: SymShape => receive(symbol) - private def listenMemberVariable(sym: TermSymbol)(receive: Listener)(using NewResolverState): Unit = - rstate.memberVariable(sym).subscribeToShapes(activationListener: + private def listenValueHost(host: ShapeHost)(receive: Listener)(using NewResolverState): Unit = + host.subscribeToShapes(activationListener: case value: TermShape => receive(value) - case _: SymShape => softAssert(false, "Member variables contain only term shapes") + case _: SymShape => softAssert(false, "Value hosts contain only term shapes") ) + private def listenMemberVariable(sym: TermSymbol)(receive: Listener)(using NewResolverState): Unit = + listenValueHost(rstate.memberVariable(sym))(receive) + def listen(trm: Term, discardMarks: Bool = false)(receive: ShapeListener[Shape])(using NewResolverState): Unit = val listener = activationListener(receive) log(s"listen: trm = ${trm.showDbg}") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 860c7c008d..3a61186225 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -265,6 +265,11 @@ final class NewResolverState private ( new Cache(inherited.map(_.selfShapes), identity) val defnShapes: Cache[DefinitionSymbol[?], DefnShape] = new Cache(inherited.map(_.defnShapes), identity) + // Parameterless definition bodies share a result node before following their + // references. Keep distinct generic activations separate, and inherit the host + // identity so imported listeners use the consumer's private publisher data. + val byNameResults: Cache[(TermSymbol, TypeSubstitution), ShapeHost] = + new Cache(inherited.map(_.byNameResults), identity) val typeInterpretations: Cache[Identity[Term], TypeResolution] = new Cache(inherited.map(_.typeInterpretations), identity) // Qualification can deliver the same declaration in several environments. diff --git a/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls b/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls index e3d3cca530..9290c96bdc 100644 --- a/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls +++ b/hkmc2/shared/src/test/mlscript-compile/GenericLibrary.mls @@ -9,3 +9,8 @@ module GenericLibrary with val known = identity(Known(3)) val mapped = [Known(4)].map((item, ...) => Known(5)) fun inferred[A](x: A) = x + // Importers must observe a recursive getter's result without rebuilding its cycle. + fun recursiveIdentity[A](x: A) = + fun result = if true then x else result + result + fun recursiveGetter = if true then Known(7) else recursiveGetter diff --git a/hkmc2/shared/src/test/mlscript/newres/RecursiveGetters.mls b/hkmc2/shared/src/test/mlscript/newres/RecursiveGetters.mls new file mode 100644 index 0000000000..548b1688ec --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/RecursiveGetters.mls @@ -0,0 +1,132 @@ +:. + +// Resolving a recursive getter must terminate even when it has no normal result. +fun self = self + +fun left = right +fun right = left + +:e +left.oops +//│ ╔══[COMPILATION ERROR] This selection of member 'oops' has no resolved target +//│ ║ l.10: left.oops +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +module Loop with + fun value = value + +:e +Loop.value.oops +//│ ╔══[COMPILATION ERROR] This selection of member 'oops' has no resolved target +//│ ║ l.20: Loop.value.oops +//│ ╙── ^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// The recursive edge also runs: getter reads must retain their runtime effects. +:expect 45 +private fun countdown() = + let remaining = 3 + fun result = if remaining == 0 then {answer: 45} else + set remaining = remaining - 1 + result + result.answer +countdown() +//│ = 45 + +// A base result discovered after a forward reference must reach both getters. +:expect [42, 42] +fun first = second +fun second = if true then {answer: 42} else first +[first.answer, second.answer] +//│ = [42, 42] + +// Re-observing completed definitions must replay their inferred result. +:expect 42 +first.answer +//│ = 42 + +// Recursive getters retain the enclosing call's captured value. +:expect [1, 2] +private fun captured(value) = + fun result = if true then value else result + result +[captured({left: 1}).left, captured({right: 2}).right] +//│ = [1, 2] + +// Selected member getters use the same finite result graph as direct references. +:expect 43 +module Getters with + fun first = second + fun second = if true then {answer: 43} else first +Getters.first.answer +//│ = 43 + +// Sharing results must retain each invocation's generic substitution. +:expect [3, 4] +class First(val left: Int) +class Second(val right: Int) +private fun generic[T](value: T) = + fun result = if true then value else result + result +[generic(First(3)).left, generic(Second(4)).right] +//│ = [3, 4] + +// A by-name generic consumes its own type arguments before returning a closure. +:expect [5, 6] +private fun identity[T] = if true then (value: T) => value else identity[T] +[identity[First](First(5)).left, identity[Second](Second(6)).right] +//│ = [5, 6] + +// Imported bodies retain copied listeners and independent caller activations. +import "../../mlscript-compile/GenericLibrary.mls" + +:expect [8, 9, 7] +[GenericLibrary.recursiveIdentity(First(8)).left, + GenericLibrary.recursiveIdentity(Second(9)).right, + GenericLibrary.recursiveGetter.known] +//│ = [8, 9, 7] + +// A result cycle does not grant dynamic member access. +:ge +fun invalid = if true then {answer: 44} else invalid +invalid.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.93: invalid.missing +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'missing' +//│ ║ l.92: fun invalid = if true then {answer: 44} else invalid +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A valid alternative must not hide a later alternative without the member. +:ge +fun mixed = if true then {answer: 46} else if true then {other: 47} else mixed +mixed.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.105: mixed.answer +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'answer' +//│ ║ l.104: fun mixed = if true then {answer: 46} else if true then {other: 47} else mixed +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Exposure of a getter's enclosing function must still propagate unknown inputs. +:ge +fun exposed(value) = + fun result = if true then value else result + result.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.118: result.answer +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── Parameter 'value' admits values of unknown shape. +//│ ║ l.116: fun exposed(value) = +//│ ║ ^^^^^ +//│ ╟── 'exposed' is exposed by this compilation unit. +//│ ║ l.116: fun exposed(value) = +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.117: fun result = if true then value else result +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.118: result.answer +//│ ╙── ^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls index 3e98241a22..35dda488f6 100644 --- a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls +++ b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls @@ -138,12 +138,6 @@ M.p.id //│ | | | | listenBMS: bms = value //│ | | | | listenedBMS: bms = value //│ | | | | listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ -//│ | | | | listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ -//│ | | | | listen: trm = member:Predef⁰ -//│ | | | | listenBMS: bms = module -//│ | | | | listenedBMS: bms = module -//│ | | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() -//│ | | | | PUSH module:Predef¹ //│ | | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = ⋅p⁰, markss = List() //│ | | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, ⋅p⁰) //│ | | | | = module:Predef¹ @@ -154,12 +148,6 @@ M.p.id //│ | | | listenBMS: bms = value //│ | | | listenedBMS: bms = value //│ | | | listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ -//│ | | | listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ -//│ | | | listen: trm = member:Predef⁰ -//│ | | | listenBMS: bms = module -//│ | | | listenedBMS: bms = module -//│ | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() -//│ | | | PUSH module:Predef¹ //│ | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = ⋅p⁰, markss = List() //│ | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, ⋅p⁰) //│ | | | = module:Predef¹ diff --git a/hkmc2/shared/src/test/mlscript/tailrec/Simple.mls b/hkmc2/shared/src/test/mlscript/tailrec/Simple.mls index 9f44db8584..4ac492599e 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/Simple.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/Simple.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -14,7 +15,7 @@ module Foo with @tailrec fun bar = bar //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.15: fun bar = bar +//│ ║ l.16: fun bar = bar //│ ╙── ^^^ @tailrec @@ -53,6 +54,6 @@ module A with module B with fun g(x) = A.f(x * 2) //│ ╔══[WARNING] This function is marked @tailrec but has no direct self-recursion. -//│ ║ l.52: fun f(x) = B.g(x + 1) +//│ ║ l.53: fun f(x) = B.g(x + 1) //│ ╙── ^ From 26229c167ef7a07f36e4e5e9296dc2f6609b4f6b Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 11:48:14 +0800 Subject: [PATCH 193/228] Migrate recursive function worksheets to new resolution --- doc/new-resolution-suite-migration.md | 12 +++--- .../src/test/mlscript/basics/FunDefs.mls | 40 ++++++++++++++++--- .../src/test/mlscript/tailrec/TailRecOpt.mls | 25 +++++++----- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index 12efa1d026..e485a0d557 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -25,7 +25,7 @@ compilation fixture; mixed-mode imports are supported. | --- | ---: | ---: | ---: | | apps | 17 | 0 | 17 | | backlog | 5 | 3 | 8 | -| basics | 78 | 12 | 90 | +| basics | 79 | 11 | 90 | | block-staging | 4 | 3 | 7 | | codegen | 106 | 20 | 126 | | ctx | 3 | 10 | 13 | @@ -41,11 +41,11 @@ compilation fixture; mixed-mode imports are supported. | opt | 30 | 0 | 30 | | std | 8 | 2 | 10 | | syntax | 11 | 0 | 11 | -| tailrec | 3 | 2 | 5 | +| tailrec | 4 | 1 | 5 | | ucs | 69 | 1 | 70 | | ups | 60 | 11 | 71 | | wasm | 23 | 0 | 23 | -| Total | 529 | 106 | 635 | +| Total | 531 | 104 | 635 | | Compilation suite | New resolution | Legacy resolution | Total | | --- | ---: | ---: | ---: | @@ -97,10 +97,8 @@ following constraints identify the current failing behavior and representative t in `handlers`, `codegen/ScopedBlocksAndHandlers`, and handler cases in `lifter`. Receiver flow, resumption arguments, and abortive results need distinct handling (`newres/HandlerResults`). -- **Operator checks, tail-call annotations, and staged functions:** `basics/FunDefs` - needs negative expectations for calls to non-callable operator definitions. - `tailrec/Annots` and `tailrec/TailRecOpt` need `@tailcall` support for getter - references and selections; `Annots` also contains an unresolved handler receiver. +- **Tail-call annotations and staged functions:** `tailrec/Annots` needs `@tailcall` + support for getter references and contains an unresolved handler receiver. `block-staging/Functions` and `codegen/FirstClassFunctionTransform` encounter unfinished generated symbols. - **Legacy flow analysis:** the `flows` worksheets run a separate flow pass that diff --git a/hkmc2/shared/src/test/mlscript/basics/FunDefs.mls b/hkmc2/shared/src/test/mlscript/basics/FunDefs.mls index b66958aead..f4119bebcb 100644 --- a/hkmc2/shared/src/test/mlscript/basics/FunDefs.mls +++ b/hkmc2/shared/src/test/mlscript/basics/FunDefs.mls @@ -1,3 +1,4 @@ +:. fun foo = 1 @@ -8,7 +9,15 @@ fun (+) plus: Int fun (+) : Int +// This operator denotes an integer, so applying it is invalid. +:e 1 + 2 +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.14: 1 + 2 +//│ ║ ^^^^^ +//│ ╟── Value of type 'Int' cannot be called like a function. +//│ ║ l.10: fun (+) : Int +//│ ╙── ^^^ fun f = g @@ -22,10 +31,25 @@ fun (++)(x, y) = 1 fun (**) pow = 1 +:e 1 ** 2 +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.35: 1 ** 2 +//│ ║ ^^^^^^ +//│ ╟── Integer literal cannot be called like a function. +//│ ║ l.32: fun (**) pow = 1 +//│ ╙── ^ +// The local '+' still denotes an integer inside this generic operator body. +:e fun (:::) appendAll[A](a, b): A = a + b +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.46: fun (:::) appendAll[A](a, b): A = a + b +//│ ║ ^^^^^ +//│ ╟── Value of type 'Int' cannot be called like a function. +//│ ║ l.10: fun (+) : Int +//│ ╙── ^^^ fun (>>) foo(a, b) = a * b @@ -36,21 +60,27 @@ fun (>>) foo(a, b) = a * b :e fun ~ concat(a, b) = a + b //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found application -//│ ║ l.37: fun ~ concat(a, b) = a + b +//│ ║ l.61: fun ~ concat(a, b) = a + b //│ ╙── ^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.37: fun ~ concat(a, b) = a + b +//│ ║ l.61: fun ~ concat(a, b) = a + b //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: b -//│ ║ l.37: fun ~ concat(a, b) = a + b +//│ ║ l.61: fun ~ concat(a, b) = a + b //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.61: fun ~ concat(a, b) = a + b +//│ ║ ^^^^^ +//│ ╟── Value of type 'Int' cannot be called like a function. +//│ ║ l.10: fun (+) : Int +//│ ╙── ^^^ // * Note the strange parsing :pt :todo // better error? fun not(b) = !b //│ ╔══[PARSE ERROR] Expected an expression; found dynamic selector instead -//│ ║ l.51: fun not(b) = !b +//│ ║ l.81: fun not(b) = !b //│ ╙── ^^ //│ ———————————————| Parsed tree |—————————————————————————————————————————————————————————————————————— //│ DynAccess: @@ -65,7 +95,7 @@ fun not(b) = !b //│ fld = Ident of "b" //│ dynamic = true //│ ╔══[COMPILATION ERROR] Illegal definition in term position. -//│ ║ l.51: fun not(b) = !b +//│ ║ l.81: fun not(b) = !b //│ ╙── ^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls b/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls index f0a0bc6ef7..a1e363d783 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls @@ -1,3 +1,4 @@ +:. :js open annotations @@ -213,7 +214,7 @@ fun f(x) = @tailcall f(x) @tailcall g() //│ ╔══[COMPILATION ERROR] This call is not in tail position. -//│ ║ l.212: @tailcall f(x) +//│ ║ l.213: @tailcall f(x) //│ ╙── ^^^^ :lift @@ -405,21 +406,27 @@ module A with @tailcall g(x - 1) A.f(10000) //│ ╔══[COMPILATION ERROR] This tail call exits the current scope and is not optimized. -//│ ║ l.404: fun g(x) = if x < 0 then 0 else @tailcall f(x) +//│ ║ l.405: fun g(x) = if x < 0 then 0 else @tailcall f(x) //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] This tail call exits the current scope and is not optimized. -//│ ║ l.405: @tailcall g(x - 1) +//│ ║ l.406: @tailcall g(x - 1) //│ ╙── ^^^^^^^^ //│ = 0 -// These calls are represented as field selections and don't yet have the explicitTailCall parameter. -:breakme -:e +// Getter selections do not yet carry an explicit tail-call flag. Require the +// unused-annotation warning until the non-tail getter call can be diagnosed. +:w module A with fun f = g fun g = @tailcall f f +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.422: @tailcall f +//│ ║ ^^^^^^^^^ +//│ ╟── This annotation is not supported on selection terms. +//│ ║ l.422: @tailcall f +//│ ╙── ^ :expect 0 module A with @@ -435,12 +442,12 @@ class A with fun g(x) = if x == 0 then 1 else @tailcall f(x - 1) (new A).f(10) //│ ╔══[COMPILATION ERROR] Calls from class methods cannot yet be marked @tailcall. -//│ ║ l.434: @tailrec fun f(x) = if x == 0 then 0 else @tailcall g(x - 1) +//│ ║ l.441: @tailrec fun f(x) = if x == 0 then 0 else @tailcall g(x - 1) //│ ╙── ^^^^^^^^ //│ ╔══[COMPILATION ERROR] Class methods may not yet be marked @tailrec. -//│ ║ l.434: @tailrec fun f(x) = if x == 0 then 0 else @tailcall g(x - 1) +//│ ║ l.441: @tailrec fun f(x) = if x == 0 then 0 else @tailcall g(x - 1) //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Calls from class methods cannot yet be marked @tailcall. -//│ ║ l.435: fun g(x) = if x == 0 then 1 else @tailcall f(x - 1) +//│ ║ l.442: fun g(x) = if x == 0 then 1 else @tailcall f(x - 1) //│ ╙── ^^^^^^^^ //│ = 0 From 791dae06a7497a4d60f1779237843bceeed65820 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 3 Oct 2026 11:35:14 +0800 Subject: [PATCH 194/228] Make various changes to Prelude, tests --- .../shared/src/test/mlscript-compile/Iter.mls | 16 ++++---- .../shared/src/test/mlscript-compile/Term.mls | 2 +- .../test/mlscript/basics/MiscArrayTests.mls | 2 +- .../src/test/mlscript/basics/Underscores.mls | 2 +- .../codegen/AuxiliaryConstructors.mls | 25 +++++++----- .../mlscript/codegen/PrivateConstructors.mls | 20 ++++++++++ .../src/test/mlscript/decls/Prelude.mls | 39 ++++++++++++++++++- .../src/test/mlscript/interop/Arrays.mls | 24 ++++++------ .../test/mlscript/meta/CompilerFiction.mls | 2 +- .../src/test/mlscript/newres/Arrays.mls | 24 ++++++------ .../test/mlscript/newres/GenericMethods.mls | 2 +- .../newres/PatternDefinitionBindings.mls | 2 +- .../shared/src/test/mlscript/std/IterTest.mls | 4 +- .../annotations/AnnotationPrecedence.mls | 2 +- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 15 files changed, 116 insertions(+), 54 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/codegen/PrivateConstructors.mls diff --git a/hkmc2/shared/src/test/mlscript-compile/Iter.mls b/hkmc2/shared/src/test/mlscript-compile/Iter.mls index 94601d4e30..b595b026ac 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Iter.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Iter.mls @@ -71,9 +71,9 @@ class IterableBase with fun length = asArray.length fun values() = iterator() -// * Implements the `Iterable` protocol. +// * Implements the `IterableImpl` protocol. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol -class Iterable(mk: () -> Iterator) extends IterableBase with +class IterableImpl(mk: () -> Iterator) extends IterableBase with fun iterator() = mk() // * Implements the `Iterator` protocol. @@ -100,7 +100,7 @@ fun getIterator(something): Iterator = else prototype.constructor.name else test.call(something) -fun adaptIterable(iterable, makeNext: Function) = Iterable of () => +fun adaptIterable(iterable, makeNext: Function) = IterableImpl of () => let iterator = getIterator of iterable IteratorImpl of makeNext of iterator @@ -115,14 +115,14 @@ fun mapping(xs, op: Function) = fun withMap(op) = _ mapping(op) fun flattening(xss) = - Iterable of () => + IterableImpl of () => let iterableIterator = getIterator of xss - // Get the first `Iterator` from the first `Iterable`. + // Get the first `Iterator` from the first `IterableImpl`. let currentIterator = if let firstIterableResult = iterableIterator.next() firstIterableResult.done then None else Some of getIterator of firstIterableResult.value - // Recursively skip empty `Iterable`s and get the next `Iterator` and value. + // Recursively skip empty `IterableImpl`s and get the next `Iterator` and value. fun skipEmptyIterables() = if let nextIterableResult = iterableIterator.next() nextIterableResult.done then None @@ -184,7 +184,7 @@ fun foldingImpl(iterator: Iterator, acc, op: Function) = acc fun appended(xs, ys) = - Iterable of () => + IterableImpl of () => let xsIterator = getIterator of xs let currentIterator = xsIterator IteratorImpl of () => if @@ -268,7 +268,7 @@ fun each(xs, op: Function) = fun toArray(view) = Array.from(view) -fun fromStack(stack) = Iterable of () => +fun fromStack(stack) = IterableImpl of () => let current = stack IteratorImpl of () => if current is Cons(head, tail) then diff --git a/hkmc2/shared/src/test/mlscript-compile/Term.mls b/hkmc2/shared/src/test/mlscript-compile/Term.mls index 78b2a6548e..0b1225835a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Term.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Term.mls @@ -74,7 +74,7 @@ class Term with class Context(val names: Map[Str, Int], val bindings: Map[Symbol, Str], val dependencies: Set[Str], val printOnly: Bool) with fun get(sym: Symbol): Str = - if bindings.has(sym) then bindings.get(sym) + if bindings.get(sym) is ~undefined as name then name else if printOnly then sym.name else throw Error(StrOps.concat("Invalid binding name ", sym.name)) fun nest: Context = Context(names, new Map(bindings), dependencies, printOnly) diff --git a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls index bdd351ea55..2639b35f78 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls @@ -31,7 +31,7 @@ xs.reverse() :re xs.map(x => x * 2) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in MiscArrayTests.mls:32 diff --git a/hkmc2/shared/src/test/mlscript/basics/Underscores.mls b/hkmc2/shared/src/test/mlscript/basics/Underscores.mls index 897afcdaf5..2b1c546f21 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Underscores.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Underscores.mls @@ -54,7 +54,7 @@ open Iter filtering of [], _ => 1 -//│ = Iterable(_) +//│ = IterableImpl(_) print(_) //│ = fun diff --git a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls index df5255c244..cc6d47453a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls @@ -4,6 +4,13 @@ // === Basic constructor with single param list === +:fixme +class Foo with + constructor() +//│ ╔══[COMPILATION ERROR] Unsupported constructor in this position. +//│ ║ l.9: constructor() +//│ ╙── ^^^^^^^^^^^^^ + // Plain class with constructor(x, y) generates JS constructor with field assignments :sjs class Foo with @@ -11,10 +18,10 @@ class Foo with fun getX = x fun getY = y //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Foo1; -//│ (class Foo { +//│ let Foo3; +//│ (class Foo2 { //│ static { -//│ Foo1 = this +//│ Foo3 = this //│ } //│ constructor(x, y) { //│ this.#x = x; @@ -118,7 +125,7 @@ class B extends A(1)(2)(3) :e class B extends A(0) //│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported -//│ ║ l.119: class B extends A(0) +//│ ║ l.126: class B extends A(0) //│ ╙── ^^^^ new B @@ -129,16 +136,16 @@ class C :e class D extends C(0) //│ ╔══[COMPILATION ERROR] Too many parameter lists for parent class -//│ ║ l.130: class D extends C(0) +//│ ║ l.137: class D extends C(0) //│ ╙── ^^^^ :e new A(1)(2) with fun a = 0 //│ ╔══[COMPILATION ERROR] Extending a partially applied class is not supported -//│ ║ l.136: new A(1)(2) with +//│ ║ l.143: new A(1)(2) with //│ ║ ^^^^^^^^^^^^^^^^ -//│ ║ l.137: fun a = 0 +//│ ║ l.144: fun a = 0 //│ ╙── ^^^^^^^^^^^ //│ = $anon { x: 1 } @@ -147,8 +154,8 @@ new A(1)(2)(3)(4) with fun a = 0 //? = $anon { x: 1 } //│ ╔══[COMPILATION ERROR] Too many parameter lists for parent class -//│ ║ l.146: new A(1)(2)(3)(4) with +//│ ║ l.153: new A(1)(2)(3)(4) with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.147: fun a = 0 +//│ ║ l.154: fun a = 0 //│ ╙── ^^^^^^^^^^^ //│ = $anon { x: 1 } diff --git a/hkmc2/shared/src/test/mlscript/codegen/PrivateConstructors.mls b/hkmc2/shared/src/test/mlscript/codegen/PrivateConstructors.mls new file mode 100644 index 0000000000..eeeb13e150 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/codegen/PrivateConstructors.mls @@ -0,0 +1,20 @@ +:. +:js + + +:todo +class Foo with + private constructor(val x, val y) +//│ ╔══[WARNING] This annotation has no effect +//│ ║ l.7: private constructor(val x, val y) +//│ ║ ^^^^^^^ +//│ ╟── A target term is expected at the end of block +//│ ║ l.7: private constructor(val x, val y) +//│ ╙── ^ + +:breakme +:e +new Foo(1, 2) +//│ = Foo + + diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 7fc3765116..1e1084c28b 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -19,10 +19,12 @@ declare module Object with fromEntries getPrototypeOf defineProperty + declare class JSON declare module JSON with fun stringify + declare class Number declare fun Number(...args): Num declare module Number with @@ -33,15 +35,20 @@ declare module Number with MAX_SAFE_INTEGER NEGATIVE_INFINITY POSITIVE_INFINITY + declare class BigInt with + // private constructor() // FIXME: Unsupported constructor in this position. fun toString(...radix): Str -declare fun BigInt(value): BigInt +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt +declare fun BigInt(value: Str | Int | Bool | BigInt): BigInt declare module BigInt + declare class Function with val name: Str fun bind(thisArg, ...args): Function fun apply(thisArg, args: Array): Any declare module Function + declare class String declare fun String(...args): Str declare module String with @@ -49,11 +56,13 @@ declare module String with fromCharCode fromCodePoint raw + declare class RegExp with fun toString(): Str fun exec(input: Str): Array[Str] | null fun test(input: Str): Bool declare module RegExp + declare class Set[V] with val size: Int fun add(value: V): Set[V] @@ -64,6 +73,7 @@ declare class Set[V] with fun union(other: Set[V]): Set[V] fun difference(other: Set[V]): Set[V] declare module Set + declare class Map[K, V] with val size: Int fun get(key: K): V | undefined @@ -75,55 +85,76 @@ declare class Map[K, V] with fun values(): Iterator[V] fun entries(): Iterator[[K, V]] fun forEach(f: (V, K, Map[K, V]) -> Any): undefined + declare class Iterator[out T] with fun next(): { value: T, done: Bool } + declare module Map + declare class WeakSet[V] declare module WeakSet + declare class WeakMap[K, V] with fun get(key: K): V | undefined fun has(key: K): Bool fun delete(key: K): Bool fun id"set"(key: K, value: V): WeakMap[K, V] declare module WeakMap + declare class Error with val message: Str val name: Str val stack: Str | undefined declare fun Error(...args): Error + declare class TypeError extends Error declare fun TypeError(...args): TypeError + declare class RangeError extends Error declare fun RangeError(...args): RangeError + declare class Date declare module Date declare class ArrayBuffer declare module ArrayBuffer + declare class TypedArray declare module TypedArray + declare class Int8Array declare module Int8Array + declare class Uint8Array declare module Uint8Array + declare class Uint8ClampedArray declare module Uint8ClampedArray + declare class Int16Array declare module Int16Array + declare class Uint16Array declare module Uint16Array + declare class Int32Array declare module Int32Array + declare class Uint32Array declare module Uint32Array + declare class Float16Array declare module Float16Array + declare class Float32Array declare module Float32Array + declare class Float64Array declare module Float64Array + declare class BigInt64Array declare module BigInt64Array + declare class BigUint64Array declare module BigUint64Array @@ -300,6 +331,7 @@ declare module Reflect with ownKeys apply construct + declare module console with declare fun @@ -325,6 +357,7 @@ declare module console with declare module process with fun cwd(): Str val env + // Host APIs remain explicitly dynamic until their interfaces are modeled. declare val fs: dyn @@ -513,4 +546,6 @@ declare class HTMLElement with // Shared read interfaces for arrays, strings, and lazy views. Element types are // covariant, so using these interfaces does not widen an invariant collection. type Indexable[out A] = {length: Int, at: Int -> A, slice: Function} -type IterableIndexable[out A] = Indexable[A] & {iterator: () -> Iterator[A]} +type Iterable[out A] = {iterator: () -> Iterator[A]} +type IterableIndexable[out A] = Indexable[A] & Iterable[A] + diff --git a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls index 483ff811a5..4ce26de522 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls @@ -11,7 +11,7 @@ let arr = [true, false] :re arr.map(_ === false) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:12 @@ -53,7 +53,7 @@ arr.map((..., e) => e) //│ ║ l.51: arr.map((..., e) => e) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:51 @@ -86,15 +86,15 @@ Array() //│ ║ ^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.177: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.208: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.178: fun join(...separators): Str +//│ ║ l.209: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.179: fun reverse(): Array[T] +//│ ║ l.210: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.180: fun sort(...compare): Array[T] +//│ ║ l.211: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.181: ... (more lines omitted) ... +//│ ║ l.212: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -105,15 +105,15 @@ Array(1, 2, 3) //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 3 //│ ║ in ../decls/Prelude.mls -//│ ║ l.177: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.208: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.178: fun join(...separators): Str +//│ ║ l.209: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.179: fun reverse(): Array[T] +//│ ║ l.210: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.180: fun sort(...compare): Array[T] +//│ ║ l.211: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.181: ... (more lines omitted) ... +//│ ║ l.212: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2, 3] diff --git a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls index 6025b27144..51f832fe40 100644 --- a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls +++ b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls @@ -25,7 +25,7 @@ js.oops //│ ║ ^^^^^^^ //│ ╟── Module 'js' does not contain member 'oops' //│ ║ in ../decls/Prelude.mls -//│ ║ l.359: declare module js with +//│ ║ l.390: declare module js with //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index a56d8b8f72..e9d9372369 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -141,15 +141,15 @@ new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.177: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.208: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.178: fun join(...separators): Str +//│ ║ l.209: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.179: fun reverse(): Array[T] +//│ ║ l.210: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.180: fun sort(...compare): Array[T] +//│ ║ l.211: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.181: ... (more lines omitted) ... +//│ ║ l.212: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2] @@ -160,15 +160,15 @@ new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.177: declare class Array[T](val length: Int): Array[T] with +//│ ║ l.208: declare class Array[T](val length: Int): Array[T] with //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.178: fun join(...separators): Str +//│ ║ l.209: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.179: fun reverse(): Array[T] +//│ ║ l.210: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.180: fun sort(...compare): Array[T] +//│ ║ l.211: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.181: ... (more lines omitted) ... +//│ ║ l.212: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.211: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.242: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index f461fc3cf2..0ae3b0f27d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.197: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls index e885878109..d0b25a0af0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls @@ -99,7 +99,7 @@ pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Int' does not contain member 'toUpperCase' //│ ║ in ../decls/Prelude.mls -//│ ║ l.143: val length: Int +//│ ║ l.174: val length: Int //│ ╙── ^^^ // Conjunction produces a pair, not the string tested by its left operand. diff --git a/hkmc2/shared/src/test/mlscript/std/IterTest.mls b/hkmc2/shared/src/test/mlscript/std/IterTest.mls index 7da62e30b2..ec6a6773a0 100644 --- a/hkmc2/shared/src/test/mlscript/std/IterTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/IterTest.mls @@ -53,7 +53,7 @@ mkStr of fun xs = [1, 2, 3] Iter.mapping(_ + "!" !> print) xs -//│ = Iterable(_) +//│ = IterableImpl(_) xs.toArray() //│ > 1! @@ -68,7 +68,7 @@ xs.toArray() //│ = ["1!", "2!", "3!"] let ys = xs -//│ ys = Iterable(_) +//│ ys = IterableImpl(_) ys.toArray() //│ > 1! diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index 6486bc2960..236643fc9d 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -40,7 +40,7 @@ fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Object 'compile' cannot be called like a function. //│ ║ in ../../decls/Prelude.mls -//│ ║ l.477: object compile +//│ ║ l.508: object compile //│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: y //│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 55cfac9194..368ba4ba95 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.369: fun +//│ ║ l.400: fun //│ ║ ^^^ -//│ ║ l.370: const(c: Int): Int32 +//│ ║ l.401: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 1cadd9c6296d712ad260c9e15fdcb5889a33ce59 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 12:06:04 +0800 Subject: [PATCH 195/228] Use brace bodies throughout the Prelude --- .../scala/hkmc2/semantics/Elaborator.scala | 4 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 12 +- .../test/mlscript/basics/MiscArrayTests.mls | 2 +- .../src/test/mlscript/decls/Prelude.mls | 129 ++++++++++++------ .../src/test/mlscript/interop/Arrays.mls | 28 ++-- .../test/mlscript/meta/CompilerFiction.mls | 2 +- .../src/test/mlscript/newres/Arrays.mls | 28 ++-- .../test/mlscript/newres/GenericMethods.mls | 2 +- .../newres/PatternDefinitionBindings.mls | 2 +- .../annotations/AnnotationPrecedence.mls | 2 +- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 11 files changed, 134 insertions(+), 81 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 277594a1a0..ae5af58dc2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -333,7 +333,7 @@ object Elaborator: case Tree.Modified(_, body) => underlyingTypeDef(body) case _ => N - val stmts = parent.tree.withPart match + val stmts = parent.tree.bodies.headOption match case S(Tree.Block(stmts)) => stmts case _ => Nil stmts.flatMap(underlyingTypeDef).collectFirst: @@ -2293,7 +2293,7 @@ extends Importer: val owner = ctx.outer.inner softTODO((k is Als) || (k is Cls) || (k is Mod) || (k is Obj) || (k is Pat), k.desc + " not yet supported") - val body = td.reft ++ td.withPart match + val body = td.bodies match case Nil => N case hd :: Nil => S(hd) case hd :: hd2 :: tl => diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 41745a1d7b..5ebc577838 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -585,6 +585,12 @@ trait TypeOrTermDef extends Located: case Reft(base, body) => rec(base, symbName, annot, body :: refts) + // In `class C(...): R { ... }`, the parser attaches the brace body to R. + // Extract it as the class body, just as for a body following `extends`. + // Parenthesized result refinements remain part of the result annotation. + case InfixApp(tree, kw @ Keywrd(Keyword.`:`), Reft(ann, body)) if this.isInstanceOf[TypeDef] => + rec(InfixApp(tree, kw, ann), symbName, annot, body :: refts) + case InfixApp(tree, Keywrd(Keyword.`:`), ann) => rec(tree, symbName, S(ann), refts) @@ -663,10 +669,14 @@ trait TypeDefImpl(using State) extends TypeOrTermDef: rhs.getOrElse(Empty())) case Trt | Mxn => ??? + // Brace bodies and `with` bodies must expose the same members to builtin lookup + // and elaboration. Keep all candidates so elaboration can diagnose multiple bodies. + lazy val bodies: Ls[Tree] = reft ++ withPart + lazy val definedSymbols: Map[Str, BlockMemberSymbol] = // val fromParams = // val fromTypeParams = - withPart match + bodies.headOption match case S(blk: Block) => blk.definedSymbols.toMap case _ => diff --git a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls index 2639b35f78..e3f7cf19e5 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls @@ -31,7 +31,7 @@ xs.reverse() :re xs.map(x => x * 2) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in MiscArrayTests.mls:32 diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 1e1084c28b..a92a82a23d 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -4,10 +4,11 @@ declare type Any declare type Anything declare type Nothing -declare class Object with +declare class Object { fun toString(): Str +} declare fun Object(...args): Object -declare module Object with +declare module Object { fun entries(target): Array[[Str, Any]] fun getOwnPropertyDescriptor(target, key): Object | undefined fun getOwnPropertyDescriptors(target): Object @@ -19,15 +20,17 @@ declare module Object with fromEntries getPrototypeOf defineProperty +} declare class JSON -declare module JSON with +declare module JSON { fun stringify +} declare class Number declare fun Number(...args): Num -declare module Number with +declare module Number { val MIN_VALUE MAX_VALUE @@ -35,35 +38,40 @@ declare module Number with MAX_SAFE_INTEGER NEGATIVE_INFINITY POSITIVE_INFINITY +} -declare class BigInt with +declare class BigInt { // private constructor() // FIXME: Unsupported constructor in this position. fun toString(...radix): Str +} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt declare fun BigInt(value: Str | Int | Bool | BigInt): BigInt declare module BigInt -declare class Function with +declare class Function { val name: Str fun bind(thisArg, ...args): Function fun apply(thisArg, args: Array): Any +} declare module Function declare class String declare fun String(...args): Str -declare module String with +declare module String { fun fromCharCode fromCodePoint raw +} -declare class RegExp with +declare class RegExp { fun toString(): Str fun exec(input: Str): Array[Str] | null fun test(input: Str): Bool +} declare module RegExp -declare class Set[V] with +declare class Set[V] { val size: Int fun add(value: V): Set[V] fun has(value: V): Bool @@ -72,9 +80,10 @@ declare class Set[V] with fun values(): Iterator[V] fun union(other: Set[V]): Set[V] fun difference(other: Set[V]): Set[V] +} declare module Set -declare class Map[K, V] with +declare class Map[K, V] { val size: Int fun get(key: K): V | undefined fun has(key: K): Bool @@ -85,26 +94,30 @@ declare class Map[K, V] with fun values(): Iterator[V] fun entries(): Iterator[[K, V]] fun forEach(f: (V, K, Map[K, V]) -> Any): undefined +} -declare class Iterator[out T] with +declare class Iterator[out T] { fun next(): { value: T, done: Bool } +} declare module Map declare class WeakSet[V] declare module WeakSet -declare class WeakMap[K, V] with +declare class WeakMap[K, V] { fun get(key: K): V | undefined fun has(key: K): Bool fun delete(key: K): Bool fun id"set"(key: K, value: V): WeakMap[K, V] +} declare module WeakMap -declare class Error with +declare class Error { val message: Str val name: Str val stack: Str | undefined +} declare fun Error(...args): Error declare class TypeError extends Error @@ -158,20 +171,23 @@ declare module BigInt64Array declare class BigUint64Array declare module BigUint64Array -declare class Promise with +declare class Promise { fun id"then"(onFulfilled: Function, ...onRejected): Promise -declare module Promise with +} +declare module Promise { fun resolve(value): Promise fun reject(reason): Promise fun all(values): Promise +} // MLscript-specific types declare class Bool -declare class Num with +declare class Num { fun toString(...radix): Str fun toFixed(...digits): Str +} declare class Int extends Num -declare class Str with +declare class Str { val length: Int fun toString(): Str fun at(index: Int): Str | undefined @@ -193,6 +209,7 @@ declare class Str with fun toLowerCase(): Str fun localeCompare(other: Str, ...localesAndOptions): Int fun trim(): Str +} declare class Class // The `Array` class/function is a footgun: @@ -206,7 +223,7 @@ declare class Class // Foreign constructor signatures describe the native binding and do not introduce // the `.class` storage used by generated MLscript constructor functions. // The `val length` parameter also supports matching with `Array(length)`. -declare class Array[T](val length: Int): Array[T] with +declare class Array[T](val length: Int): Array[T] { fun join(...separators): Str fun reverse(): Array[T] fun sort(...compare): Array[T] @@ -247,22 +264,26 @@ declare class Array[T](val length: Int): Array[T] with fun includes(value: T, ...from): Bool fun toString(): Str fun toLocaleString(...options): Str -declare module Array with +} +declare module Array { fun from(...args): Array[Any] fun concat isArray val prototype: Array[Any] +} -declare class Symbol with +declare class Symbol { val description: Str | undefined +} declare fun Symbol(...description): Symbol -declare module Symbol with +declare module Symbol { // The `TermDef` needs `rhs` to be defined to be recognized as `isMLsFun`. // Otherwise, it would be wrapped in `runtime.safeCall`, which accesses the // uninitialized `runtime` in the `Rendering` module. fun for: Str -> Any = () val iterator: Any +} // MLwasm-specific types declare class Int32 @@ -272,7 +293,7 @@ declare class Float64 declare class Int31 extends Int // `i31ref` // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math -declare module Math with +declare module Math { declare val E: Num @@ -320,8 +341,9 @@ declare module Math with tan: Num -> Num tanh: Num -> Num trunc: Num -> Num +} -declare module Reflect with +declare module Reflect { // Prototype objects may expose arbitrary host properties. fun getPrototypeOf(target): dyn fun @@ -331,8 +353,9 @@ declare module Reflect with ownKeys apply construct +} -declare module console with +declare module console { declare fun log @@ -353,10 +376,12 @@ declare module console with time timeEnd trace +} -declare module process with +declare module process { fun cwd(): Str val env +} // Host APIs remain explicitly dynamic until their interfaces are modeled. declare val fs: dyn @@ -364,19 +389,22 @@ declare val fs: dyn declare val Infinity // Wasm support -declare object WebAssembly with - declare class Instance with +declare object WebAssembly { + declare class Instance { val exports: dyn + } declare class Memory declare class Module - declare module Module with + declare module Module { fun exports imports + } fun instantiate validate +} // declare fun typeof: (Any) -> Str @@ -384,22 +412,24 @@ declare fun parseInt(str: Str, radix: Int): Int declare fun parseFloat(str: Str): Num -declare module source with +declare module source { object line name file +} -declare module js with +declare module js { fun bitand bitnot bitor shl try_catch +} -declare module wasm with - module i32 with +declare module wasm { + module i32 { fun const(c: Int): Int32 add(i1: Int32, i2: Int32): Int32 @@ -418,7 +448,8 @@ declare module wasm with ge_s(i1: Int32, i2: Int32): Int32 eqz(i: Int32): Int32 reinterpret_f32(f: Float32): Int32 - module i64 with + } + module i64 { fun const(c: Int): Int64 add(i1: Int64, i2: Int64): Int64 @@ -437,7 +468,8 @@ declare module wasm with ge_s(i1: Int64, i2: Int64): Int32 eqz(i: Int64): Int32 reinterpret_f64(f: Float64): Int64 - module f32 with + } + module f32 { fun const(c: Num): Float32 add(f1: Float32, f2: Float32): Float32 @@ -456,7 +488,8 @@ declare module wasm with gt(f1: Float32, f2: Float32): Int32 ge(f1: Float32, f2: Float32): Int32 reinterpret_i32(i: Int32): Float32 - module f64 with + } + module f64 { fun const(c: Num): Float64 add(f1: Float64, f2: Float64): Float64 @@ -475,13 +508,16 @@ declare module wasm with gt(f1: Float64, f2: Float64): Int32 ge(f1: Float64, f2: Float64): Int32 reinterpret_i64(i: Int64): Float64 - module ref with + } + module ref { fun i31(i: Int32): Int31 - module i31 with + } + module i31 { fun get_s(i: Int31): Int32 get_u(i: Int31): Int32 + } fun plus_impl minus_impl @@ -497,11 +533,13 @@ declare module wasm with neg_impl pos_impl not_impl +} -declare module debug with +declare module debug { fun printStack +} -declare module annotations with +declare module annotations { object pure object untyped object tailrec @@ -513,17 +551,21 @@ declare module annotations with object bufferable object generator object async +} -declare module handlers with +declare module handlers { fun await +} -declare module scope with +declare module scope { fun locally +} -declare module runtime with +declare module runtime { fun suspend handle_suspension +} // HTML DOM API definitions. @@ -531,7 +573,7 @@ declare module runtime with declare val document: dyn declare val customElements: dyn // Preserve the nominal superclass while its host members remain dynamic. -declare class HTMLElement with +declare class HTMLElement { val textContent: dyn val innerHTML: dyn val shadowRoot: dyn @@ -540,6 +582,7 @@ declare class HTMLElement with val addEventListener: dyn val querySelector: dyn val classList: dyn +} //│ ———————————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls index 4ce26de522..8d5768399d 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls @@ -11,7 +11,7 @@ let arr = [true, false] :re arr.map(_ === false) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:12 @@ -53,7 +53,7 @@ arr.map((..., e) => e) //│ ║ l.51: arr.map((..., e) => e) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:51 @@ -86,15 +86,15 @@ Array() //│ ║ ^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.208: declare class Array[T](val length: Int): Array[T] with -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.209: fun join(...separators): Str +//│ ║ l.225: declare class Array[T](val length: Int): Array[T] { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.226: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.210: fun reverse(): Array[T] +//│ ║ l.227: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.211: fun sort(...compare): Array[T] +//│ ║ l.228: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.212: ... (more lines omitted) ... +//│ ║ l.229: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -105,15 +105,15 @@ Array(1, 2, 3) //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 3 //│ ║ in ../decls/Prelude.mls -//│ ║ l.208: declare class Array[T](val length: Int): Array[T] with -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.209: fun join(...separators): Str +//│ ║ l.225: declare class Array[T](val length: Int): Array[T] { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.226: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.210: fun reverse(): Array[T] +//│ ║ l.227: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.211: fun sort(...compare): Array[T] +//│ ║ l.228: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.212: ... (more lines omitted) ... +//│ ║ l.229: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2, 3] diff --git a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls index 51f832fe40..e32fd80cb8 100644 --- a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls +++ b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls @@ -25,7 +25,7 @@ js.oops //│ ║ ^^^^^^^ //│ ╟── Module 'js' does not contain member 'oops' //│ ║ in ../decls/Prelude.mls -//│ ║ l.390: declare module js with +//│ ║ l.421: declare module js { //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index e9d9372369..9714479203 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -141,15 +141,15 @@ new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.208: declare class Array[T](val length: Int): Array[T] with -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.209: fun join(...separators): Str +//│ ║ l.225: declare class Array[T](val length: Int): Array[T] { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.226: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.210: fun reverse(): Array[T] +//│ ║ l.227: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.211: fun sort(...compare): Array[T] +//│ ║ l.228: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.212: ... (more lines omitted) ... +//│ ║ l.229: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2] @@ -160,15 +160,15 @@ new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.208: declare class Array[T](val length: Int): Array[T] with -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.209: fun join(...separators): Str +//│ ║ l.225: declare class Array[T](val length: Int): Array[T] { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.226: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.210: fun reverse(): Array[T] +//│ ║ l.227: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.211: fun sort(...compare): Array[T] +//│ ║ l.228: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.212: ... (more lines omitted) ... +//│ ║ l.229: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.242: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.259: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 0ae3b0f27d..933e40df04 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.228: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls index d0b25a0af0..a3b30a9929 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls @@ -99,7 +99,7 @@ pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Int' does not contain member 'toUpperCase' //│ ║ in ../decls/Prelude.mls -//│ ║ l.174: val length: Int +//│ ║ l.190: val length: Int //│ ╙── ^^^ // Conjunction produces a pair, not the string tested by its left operand. diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index 236643fc9d..6d35054e96 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -40,7 +40,7 @@ fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Object 'compile' cannot be called like a function. //│ ║ in ../../decls/Prelude.mls -//│ ║ l.508: object compile +//│ ║ l.548: object compile //│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: y //│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 368ba4ba95..8709e9ff2d 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.400: fun +//│ ║ l.432: fun //│ ║ ^^^ -//│ ║ l.401: const(c: Int): Int32 +//│ ║ l.433: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From ab3ef8ba8bc0e4e481b9f627b6900a5cb684ee60 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 12:13:20 +0800 Subject: [PATCH 196/228] Test abstract class interface annotations with brace bodies --- .../newres/AbstractClassInterface.mls | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/newres/AbstractClassInterface.mls diff --git a/hkmc2/shared/src/test/mlscript/newres/AbstractClassInterface.mls b/hkmc2/shared/src/test/mlscript/newres/AbstractClassInterface.mls new file mode 100644 index 0000000000..d0ac000dac --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/AbstractClassInterface.mls @@ -0,0 +1,20 @@ +:. + +// The annotation names the interface that subclasses of the abstract class +// conform to. The following braces contain the class body, not a refinement +// of the interface; its methods must remain available to subclasses. +type Named = {name: Str} +abstract class NamedBase(prefix: Str): Named { + fun label(suffix: Str): Str = prefix + suffix +} +class NamedChild extends NamedBase("hello ") { + val name: Str = this.label("world") +} +fun readName(value: Named): Str = value.name +fun labelThroughBase(value: NamedBase): Str = value.label("base") + +:expect ["hello world", "hello base"] +let child = new NamedChild +[readName(child), labelThroughBase(child)] +//│ = ["hello world", "hello base"] +//│ child = NamedChild { name: "hello world" } From f0c6b08ca6cf4997baf1044ed854d486b4c0053e Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 3 Oct 2026 12:18:50 +0800 Subject: [PATCH 197/228] Update Prelude again --- .../test/mlscript/basics/MiscArrayTests.mls | 2 +- .../src/test/mlscript/decls/Prelude.mls | 32 +++++++++++-------- .../src/test/mlscript/interop/Arrays.mls | 28 ++++++++-------- .../test/mlscript/meta/CompilerFiction.mls | 2 +- .../src/test/mlscript/newres/Arrays.mls | 28 ++++++++-------- .../test/mlscript/newres/GenericMethods.mls | 2 +- .../newres/PatternDefinitionBindings.mls | 2 +- .../annotations/AnnotationPrecedence.mls | 2 +- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +-- 9 files changed, 54 insertions(+), 48 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls index e3f7cf19e5..f07782b733 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls @@ -31,7 +31,7 @@ xs.reverse() :re xs.map(x => x * 2) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in MiscArrayTests.mls:32 diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index a92a82a23d..deef5ab3fc 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -7,7 +7,7 @@ declare type Nothing declare class Object { fun toString(): Str } -declare fun Object(...args): Object +declare fun Object(...args): Object // TODO: use an optional parameter when supported declare module Object { fun entries(target): Array[[Str, Any]] fun getOwnPropertyDescriptor(target, key): Object | undefined @@ -28,16 +28,17 @@ declare module JSON { stringify } -declare class Number -declare fun Number(...args): Num +declare class Number: Num +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/Number +declare fun Number(...args): Number // TODO: use an optional parameter when supported declare module Number { val - MIN_VALUE - MAX_VALUE - MIN_SAFE_INTEGER - MAX_SAFE_INTEGER - NEGATIVE_INFINITY - POSITIVE_INFINITY + MIN_VALUE: Num + MAX_VALUE: Num + MIN_SAFE_INTEGER: Int + MAX_SAFE_INTEGER: Int + NEGATIVE_INFINITY: Num + POSITIVE_INFINITY: Num } declare class BigInt { @@ -56,7 +57,8 @@ declare class Function { declare module Function declare class String -declare fun String(...args): Str +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/String +declare fun String(...args): Str // TODO: use an optional parameter when supported declare module String { fun fromCharCode @@ -118,11 +120,14 @@ declare class Error { val name: Str val stack: Str | undefined } -declare fun Error(...args): Error +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Error +declare fun Error(...args): Error // TODO: use optional parameters when supported +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError/TypeError declare class TypeError extends Error declare fun TypeError(...args): TypeError +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError/RangeError declare class RangeError extends Error declare fun RangeError(...args): RangeError @@ -223,7 +228,7 @@ declare class Class // Foreign constructor signatures describe the native binding and do not introduce // the `.class` storage used by generated MLscript constructor functions. // The `val length` parameter also supports matching with `Array(length)`. -declare class Array[T](val length: Int): Array[T] { +declare class Array[T](val length: Int) { fun join(...separators): Str fun reverse(): Array[T] fun sort(...compare): Array[T] @@ -266,7 +271,8 @@ declare class Array[T](val length: Int): Array[T] { fun toLocaleString(...options): Str } declare module Array { - fun from(...args): Array[Any] + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from + fun from(...args): Array[Any] // TODO: use optional parameters when supported fun concat isArray diff --git a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls index 8d5768399d..c423692e2e 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls @@ -11,7 +11,7 @@ let arr = [true, false] :re arr.map(_ === false) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:12 @@ -53,7 +53,7 @@ arr.map((..., e) => e) //│ ║ l.51: arr.map((..., e) => e) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:51 @@ -86,15 +86,15 @@ Array() //│ ║ ^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.225: declare class Array[T](val length: Int): Array[T] { -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.226: fun join(...separators): Str +//│ ║ l.230: declare class Array[T](val length: Int) { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.231: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.227: fun reverse(): Array[T] +//│ ║ l.232: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.228: fun sort(...compare): Array[T] +//│ ║ l.233: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.229: ... (more lines omitted) ... +//│ ║ l.234: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -105,15 +105,15 @@ Array(1, 2, 3) //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 3 //│ ║ in ../decls/Prelude.mls -//│ ║ l.225: declare class Array[T](val length: Int): Array[T] { -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.226: fun join(...separators): Str +//│ ║ l.230: declare class Array[T](val length: Int) { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.231: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.227: fun reverse(): Array[T] +//│ ║ l.232: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.228: fun sort(...compare): Array[T] +//│ ║ l.233: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.229: ... (more lines omitted) ... +//│ ║ l.234: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2, 3] diff --git a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls index e32fd80cb8..1bf49d5689 100644 --- a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls +++ b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls @@ -25,7 +25,7 @@ js.oops //│ ║ ^^^^^^^ //│ ╟── Module 'js' does not contain member 'oops' //│ ║ in ../decls/Prelude.mls -//│ ║ l.421: declare module js { +//│ ║ l.427: declare module js { //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 9714479203..f634f64a29 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -141,15 +141,15 @@ new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.225: declare class Array[T](val length: Int): Array[T] { -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.226: fun join(...separators): Str +//│ ║ l.230: declare class Array[T](val length: Int) { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.231: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.227: fun reverse(): Array[T] +//│ ║ l.232: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.228: fun sort(...compare): Array[T] +//│ ║ l.233: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.229: ... (more lines omitted) ... +//│ ║ l.234: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2] @@ -160,15 +160,15 @@ new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.225: declare class Array[T](val length: Int): Array[T] { -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.226: fun join(...separators): Str +//│ ║ l.230: declare class Array[T](val length: Int) { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.231: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.227: fun reverse(): Array[T] +//│ ║ l.232: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.228: fun sort(...compare): Array[T] +//│ ║ l.233: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.229: ... (more lines omitted) ... +//│ ║ l.234: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.259: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.264: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 933e40df04..95d131e5ea 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.245: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls index a3b30a9929..67abc25ce7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls @@ -99,7 +99,7 @@ pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Int' does not contain member 'toUpperCase' //│ ║ in ../decls/Prelude.mls -//│ ║ l.190: val length: Int +//│ ║ l.195: val length: Int //│ ╙── ^^^ // Conjunction produces a pair, not the string tested by its left operand. diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index 6d35054e96..17d50e5b27 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -40,7 +40,7 @@ fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Object 'compile' cannot be called like a function. //│ ║ in ../../decls/Prelude.mls -//│ ║ l.548: object compile +//│ ║ l.554: object compile //│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: y //│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 8709e9ff2d..9be1681755 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.432: fun +//│ ║ l.438: fun //│ ║ ^^^ -//│ ║ l.433: const(c: Int): Int32 +//│ ║ l.439: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 952069a0c4b75385fc8421a23f5a21a762e3ba93 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 12:26:06 +0800 Subject: [PATCH 198/228] Exclude directory configuration files from test discovery --- hkmc2/jvm/src/test/scala/hkmc2/CompileTestRunnerBase.scala | 3 +-- hkmc2/jvm/src/test/scala/hkmc2/TestFolders.scala | 4 ++++ hkmc2DiffTests/src/test/scala/hkmc2/DiffTestRunner.scala | 6 ++---- hkmc2DiffTests/src/test/scala/hkmc2/Watcher.scala | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/CompileTestRunnerBase.scala b/hkmc2/jvm/src/test/scala/hkmc2/CompileTestRunnerBase.scala index 4b2b8cd6c4..3cd762bbdf 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/CompileTestRunnerBase.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/CompileTestRunnerBase.scala @@ -31,12 +31,11 @@ abstract class CompileTestRunnerBase( val mainTestDir: os.Path = TestFolders.mainTestDir(workingDir) - val validExt = Set("mls") for dir <- compileDirs do { val allFiles = os.walk(dir) .filter(_.toIO.isFile) - .filter(_.ext in validExt) + .filter(TestFolders.isTestSource) lazy val compileTestFiles = allFiles.filter: file => file.segments.contains("mlscript-compile") diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TestFolders.scala b/hkmc2/jvm/src/test/scala/hkmc2/TestFolders.scala index 46a0a8f63c..94739a7bc3 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TestFolders.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TestFolders.scala @@ -8,6 +8,10 @@ import hkmc2.utils.*, shorthands.* * `mlscript-compile/` subdirectories (for compile tests) that it owns. */ object TestFolders: + /** Directory configuration files named `.mls` are not standalone test sources. */ + def isTestSource(file: os.BasePath): Bool = + file.ext === "mls" && file.baseName.nonEmpty + /** Helper to check whether `file` is inside (i.e. a descendant of) `dir`. */ def isInDir(file: os.Path, dir: os.Path): Bool = file.startsWith(dir) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/DiffTestRunner.scala b/hkmc2DiffTests/src/test/scala/hkmc2/DiffTestRunner.scala index be999a181a..b0f3232dc6 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/DiffTestRunner.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/DiffTestRunner.scala @@ -42,11 +42,9 @@ object DiffTestRunner: // To be overridden in subproject-specific State classes def testDir: os.Path = dir - val validExt = Set("mls") - val allFiles = os.walk(testDir) .filter(_.toIO.isFile) - .filter(_.ext in validExt) + .filter(TestFolders.isTestSource) def filter(file: os.RelPath): Bool = true @@ -68,7 +66,7 @@ object DiffTestRunner: val filePath = os.RelPath(gitStr.drop(3)) if prefix =:= "A " || prefix =:= "M " || prefix =:= "R " || prefix =:= "D " then N // * Disregard modified files that are staged - else if filePath.ext =/= "mls" then N + else if !TestFolders.isTestSource(filePath) then N else S(filePath) }.toSet catch case err: Throwable => diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/Watcher.scala b/hkmc2DiffTests/src/test/scala/hkmc2/Watcher.scala index 7bb1f2117d..97f95cfdb6 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/Watcher.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/Watcher.scala @@ -95,12 +95,12 @@ class Watcher(dirs: Ls[File]): def go(file: File) = // println(s"go $file") - val isMls = file.toString.endsWith(".mls") + val path = os.Path(file.pathAsString) + val isMls = TestFolders.isTestSource(path) if file.toString.endsWith(".scala") then watcher.close() else if isMls || file.toString.endsWith(".cmd") then Thread.sleep(100) - val path = os.Path(file.pathAsString) val basePath = path.segments.drop(dirPaths.head.segmentCount).toList.init val relativeName = basePath.map(_ + "/").mkString + path.baseName val rootPath = os.pwd/os.up From b201c3d1a87dcad0b0482e158ecdd7eff2f6cf09 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 3 Oct 2026 11:53:53 +0800 Subject: [PATCH 199/228] Minor --- doc/new-resolution-suite-migration.md | 2 -- hkmc2/shared/src/test/mlscript/lifter/Loops.mls | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/new-resolution-suite-migration.md b/doc/new-resolution-suite-migration.md index e485a0d557..4fd3c3c16d 100644 --- a/doc/new-resolution-suite-migration.md +++ b/doc/new-resolution-suite-migration.md @@ -89,8 +89,6 @@ following constraints identify the current failing behavior and representative t callable result precision across branches with different parameter lists. `std/LazyFingerTreeTest` loses the private view interface through slicing helpers; its general slice result also includes finger trees without `materialize`. - `lifter/Loops` includes an implicit unit result after an unconditional loop return, - preventing the returned closure from being called directly. - **Generators:** `codegen/Generators` needs the iterator interface produced by generator lowering. `newres/GeneratorResults` records the unresolved `.next`. - **Handlers:** handler-generated receiver and member references remain unresolved diff --git a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls index 8ed4e924a1..0c8af302ff 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls @@ -1,3 +1,4 @@ +:.. :. // :ignore // used to be ON with comment: Lifting should not require scope flattening (which doesn't handle loops) @@ -80,7 +81,12 @@ fun foo() = //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :expect 2 +:e foo()() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.85: foo()() +//│ ║ ^^^^^^^ +//│ ╙── Unit value cannot be called like a function. //│ = 2 From a530dbd96e5d4b18d73558db8acc30b48ce42f41 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 3 Oct 2026 12:12:40 +0800 Subject: [PATCH 200/228] Implement runtime class selection with C.class --- .../main/scala/hkmc2/codegen/Lowering.scala | 18 +- .../scala/hkmc2/semantics/Elaborator.scala | 4 +- .../scala/hkmc2/semantics/NewResolver.scala | 58 +++- .../src/main/scala/hkmc2/semantics/Term.scala | 9 + .../test/mlscript/codegen/ParamClasses.mls | 102 +++++-- .../src/test/mlscript/newres/ClassValues.mls | 258 ++++++++++++++++++ 6 files changed, 408 insertions(+), 41 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ClassValues.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 5e07c44977..49ace61e02 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -415,7 +415,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu blockImpl(imps ::: funs ::: rest, res) - def classOf(trm: Term, nw: Resolvable)(k: Path => Block)(using LoweringCtx): Block = + def classOf(trm: Term, nw: PossiblyErroneous)(k: Path => Block)(using LoweringCtx): Block = if newResolution then trm.classHead match case resolved @ Resolved(_, _: ClassSymbol) => @@ -702,6 +702,20 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu source = Diagnostic.Source.Compilation) else k(prefix, memberIdent(id, N), N) + /** Class selection uses the declaration binding or unwraps a constructor + * value, never the companion's ordinary member namespace. Mixed runtime + * representations cannot share one property access safely. + */ + private def classValue(sel: NewSel)(k: Path => Block)(using LoweringCtx): Block = + if sel.isErroneous then compError + else if sel.hasDynamicTarget || sel.resolvedMembers.nonEmpty || sel.classValueAccesses.size != 1 then fail: + ErrorReport(msg"This '.class' selection cannot mix class objects, constructor functions, and ordinary fields." -> sel.toLoc :: Nil, + source = Diagnostic.Source.Compilation) + else sel.classValueAccesses.head match + case ClassValueAccess.Declaration => classOf(sel.prefix, sel)(k) + case ClassValueAccess.Constructor => subTerm_nonTail(sel.prefix)(p => k(p.selSN("class"))) + case ClassValueAccess.Value => subTerm(sel.prefix)(k) + private def selectionPath(sel: NewSel, prefix: Path, name: Tree.Ident, target: Opt[DefinitionSymbol[?]]): Path = sel.tupleIndex match case S(index) => DynSelect(prefix, Value.Lit(Tree.IntLit(BigInt(index)))(N), true)(sel.toLoc) @@ -1149,6 +1163,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu instantiated match // * Due to whacky JS semantics, we need to make sure that selections leading to a call // * are preserved in the call and not moved to a temporary variable. + case sel: NewSel if sel.classValueAccesses.nonEmpty => classValue(sel)(conclude) case sel: NewSel => newSelection(sel): (prefix, name, target) => subTerm_nonTail(prefix): p => conclude(selectionPath(sel, p, name, target)) @@ -1309,6 +1324,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case whltrm: st.SynthWhile => ucs.Normalization(this)(whltrm)(k) + case sel: NewSel if sel.classValueAccesses.nonEmpty => classValue(sel)(k) case sel: NewSel => newSelection(sel): (prefix, name, target) => if sel.tupleIndex.nonEmpty then subTerm_nonTail(prefix)(p => k(selectionPath(sel, p, name, target))) else setupNamedSelection(prefix, name, target, sel.toLoc)(k) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index ae5af58dc2..3e0d1853a4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1278,7 +1278,9 @@ extends Importer: app(lt, rt)(tree, N, sym) def elaborateSelection(tree: Sel): Term = - val preTrm = subterm(tree.prefix, Receiver) + // `.class` chooses the class overload before requesting a receiver value. + // Interpreting C as a receiver first would also select its companion. + val preTrm = subterm(tree.prefix, if newResolution && tree.name.name == "class" then Clss else Receiver) // `source` has no runtime object. Recognize its statically named fields // before constructing a runtime selection, in either resolution mode. // Compare symbols so a user-defined binding named `source` stays ordinary. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 439a1e8409..cf9630fe08 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2008,7 +2008,7 @@ class NewResolver: // An ambiguous lookup can publish several constructors. Pair each // declaration only with its own references; the lookup diagnoses // competing candidates independently of this shape subscription. - case _: ClassDef => listenClass(lhs)(ref => + case _: ClassDef => listenClass(lhs, true)(ref => if ref.definition eq cls then publish(ref.marks) , _ => ()) case _: ModuleOrObjectDef => publish(Nil) @@ -2620,12 +2620,15 @@ class NewResolver: * companions. Aliases can supply constructor shapes; applied instances cannot. * Capture marks are retained for subsequent instance-member lookup. */ private case class ClassReference(definition: ClassDef, marks: Ls[Marks], - instances: TypeSubstitution, supplied: Opt[Ls[DeclaredType]]) + instances: TypeSubstitution, supplied: Opt[Ls[DeclaredType]], access: ClassValueAccess) - private def listenClass(trm: Term)(selected: ShapeListener[ClassReference], reject: ShapeListener[Shape])(using NewResolverState): Unit = + // Class consumers such as `new` attach the class target to the operand. + // `.class` instead keeps value aliases bound to their original storage and + // records how to extract the class on the selection itself. + private def listenClass(trm: Term, recordTarget: Bool)(selected: ShapeListener[ClassReference], reject: ShapeListener[TermShape])(using NewResolverState): Unit = def select(ref: ClassReference)(using NewResolverState): Unit = trm.classHead match - case target: NewResolvable => + case target: NewResolvable if recordTarget || ref.access == ClassValueAccess.Declaration => rstate.recordResolution(target, target.resolvedTargets.contains(ref.definition.sym))( target.resolvedTargets ::= ref.definition.sym) case _ => () @@ -2635,13 +2638,21 @@ class NewResolver: val marks = source.applicationHead._2 def fromDefinition(ds: DefnShape): Unit = ds.clsDef match - case S(cls: ClassDef) => select(ClassReference(cls, marks, instances, supplied)) + case S(cls: ClassDef) => + val access = ds.defn match + case td: TermDefinition if td.tsym.isInstanceOf[ClassCtorSymbol] && cls.hasDeclareModifier.isEmpty => + ClassValueAccess.Constructor + case _ => ClassValueAccess.Value + select(ClassReference(cls, marks, instances, supplied, access)) case _ => reject(sh) source match case Marked(ds: DefnShape, _) => fromDefinition(ds) case _ => reject(sh) trm match - case application @ TyApp(base, args) => listenClass(base)(ref => + // Local variables store the term interpretation of their initializer, even + // when their flow still contains its unconverted overload-set shape. + case _: SimpleRef if !recordTarget => listenTerm(trm)(value) + case application @ TyApp(base, args) => listenClass(base, recordTarget)(ref => val count = if ref.supplied.nonEmpty then 0 else ref.definition.tparams.length if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then resolError(trm, msg"Class '${ref.definition.sym.nme}' expected ${count} type ${ @@ -2654,7 +2665,7 @@ class NewResolver: select(ref.copy(instances = instances, supplied = S(supplied))) , reject) case Capture(base, thru) => - listenClass(base)(ref => select(ref.copy(marks = + listenClass(base, recordTarget)(ref => select(ref.copy(marks = ref.marks ::: EntryMark(ResolutionBoundary(thru), N, NoMarks) :: Nil)), reject) case _ => listen(trm): case sh: SymShape => @@ -2663,8 +2674,9 @@ class NewResolver: sh match case contextual: ContextualSymShape => contextual.instances case _ => TypeSubstitution.empty - , N)), - () => fromSymbol(sh, value, trm, _ => (), false)) + , N, ClassValueAccess.Declaration)), + () => fromSymbol(sh, value, trm, + sym => if !recordTarget then recordValueTarget(trm, sym), !recordTarget)) case sh: TermShape => value(sh) def newSel(sel: NewSel)(using NewResolverState): Unit = @@ -2704,11 +2716,35 @@ class NewResolver: // selection, but cannot choose a different member for that old syntax. case _ => () sel.cls match + case N if sel.id.name == "class" => + // Preserve a class interpretation even when the declaration also has a + // constructor, a module, or an unrelated term overload. Value aliases + // retain their runtime path and only unwrap actual constructor values. + listenClass(sel.prefix, false)(ref => + val cd = ref.definition + if rstate.canResolve(sel) then + rstate.recordResolution(sel, sel.classValueAccesses(ref.access))(sel.classValueAccesses += ref.access) + rstate.recordResolution(sel, sel.resolvedTargets.contains(cd.sym))(sel.resolvedTargets ::= cd.sym) + // A compiled helper can receive other constructors, but its emitted + // property access cannot change into an identity operation (or vice versa). + // Transport the new class's shape without mutating completed targets. + if sel.classValueAccesses(ref.access) then + listenExt(cd, ext => + val shape = defnShapes.getOrElseUpdate(cd.sym, DefnShape(cd, ext)) + val specialized = ref.supplied.fold[TermShape](shape)(args => SpecializedShape(shape, args, ref.instances)) + transportShape(specialized, ref.marks) match + case value: TermShape => publishViewed(sel, value, ref.instances) + case NoShape => ()) + else if !rstate.hasError(sel) then + rstate.markError(sel) + resolError(sel, msg"This previously defined '.class' selection cannot mix class objects and constructor functions." -> sel.toLoc :: Nil) + , shape => + member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty)) case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty) case S(cls) => - listenClass(cls)(ref => + listenClass(cls, true)(ref => val cd = ref.definition val marks = ref.marks val candidate = cd.sym -> marks @@ -2776,7 +2812,7 @@ class NewResolver: // listenClass preserves captures and supplies the same class-body exit as a // constructor value. In particular, a captured constructor already carries // that exit; adding a second one here would duplicate its instance boundary. - listenClass(nw.cls)(ref => + listenClass(nw.cls, true)(ref => val cd = ref.definition val marks = ref.marks val callerInstances = rstate.instances diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 61c5f7e635..57833e9e48 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -342,6 +342,13 @@ sealed trait NewRefImpl extends AnyRefImpl: // def tree: Tree.Ident = Tree.Dummy def refNum: Int = 0 // TODO +/** How a class reference reaches its runtime class object. Declaration references + * use the backend's class binding; constructor values carry it in `.class`; + * values obtained from an earlier class selection already are class objects. + */ +enum ClassValueAccess: + case Declaration, Constructor, Value + sealed trait NewSelImpl extends NewResolvableImpl: self: Term.NewSel => // At least one receiver permits runtime lookup without a static member symbol. @@ -352,6 +359,7 @@ sealed trait NewSelImpl extends NewResolvableImpl: var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution // Class identity and captures must survive even when candidates share an inherited member. var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil + var classValueAccesses: Set[ClassValueAccess] = Set.empty def hasAmbiguousClass(using Erasure): Bool = hasAmbiguousClassImpl // Also used by the guarded symbol lookup for completed imports. private[semantics] def hasAmbiguousClassImpl: Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: @@ -646,6 +654,7 @@ enum Term extends Statement, AutoLocated, ShapePublisher: val copy = NewSel(prefix.mkClone, id, cls.map(_.mkClone))(term.resSym) copy.resolvedMembers = term.resolvedMembers copy.resolvedClasses = term.resolvedClasses + copy.classValueAccesses = term.classValueAccesses copy.hasDynamicTarget = term.hasDynamicTarget copyNewResolution(term, copyShapes(term, copy)) case term @ UnresolvedRef(prefixes, id) => diff --git a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls index 91640435b1..c0b992746b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls @@ -1,3 +1,4 @@ +:. :js @@ -112,7 +113,8 @@ i.i1(20) //│ = 120 -// * This is strange, but does not need to be an error +// The new resolver rejects duplicate constructor members. +:e :sjs class Foo(x, val y, z, val z, z) with let x = 1 @@ -122,6 +124,72 @@ class Foo(x, val y, z, val z, z) with print("this.y = " + this.y) print("z = " + z) print("this.z = " + this.z) +//│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'z'. +//│ ╟── Defined at: +//│ ║ l.119: class Foo(x, val y, z, val z, z) with +//│ ║ ^ +//│ ╟── Defined at: +//│ ║ l.119: class Foo(x, val y, z, val z, z) with +//│ ║ ^^^^^ +//│ ╟── Defined at: +//│ ║ l.119: class Foo(x, val y, z, val z, z) with +//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.121: let y += 1 +//│ ║ ^ +//│ ╟── Class definition 'Foo' does not contain member 'y' +//│ ║ l.119: class Foo(x, val y, z, val z, z) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.120: let x = 1 +//│ ║ ^^^^^^^^^^^ +//│ ║ l.121: let y += 1 +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.122: print("x = " + x) +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.123: ... (more lines omitted) ... +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.124: print("this.y = " + this.y) +//│ ║ ^^^^^^ +//│ ╟── Class definition 'Foo' does not contain member 'y' +//│ ║ l.119: class Foo(x, val y, z, val z, z) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.120: let x = 1 +//│ ║ ^^^^^^^^^^^ +//│ ║ l.121: let y += 1 +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.122: print("x = " + x) +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.123: ... (more lines omitted) ... +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.125: print("z = " + z) +//│ ║ ^ +//│ ╟── Class definition 'Foo' does not contain member 'z' +//│ ║ l.119: class Foo(x, val y, z, val z, z) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.120: let x = 1 +//│ ║ ^^^^^^^^^^^ +//│ ║ l.121: let y += 1 +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.122: print("x = " + x) +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.123: ... (more lines omitted) ... +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.126: print("this.z = " + this.z) +//│ ║ ^^^^^^ +//│ ╟── Class definition 'Foo' does not contain member 'z' +//│ ║ l.119: class Foo(x, val y, z, val z, z) with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.120: let x = 1 +//│ ║ ^^^^^^^^^^^ +//│ ║ l.121: let y += 1 +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.122: print("x = " + x) +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.123: ... (more lines omitted) ... +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo9; //│ Foo9 = function Foo(x, y, z, z1, z2) { @@ -132,48 +200,26 @@ class Foo(x, val y, z, val z, z) with //│ Foo9.class = this //│ } //│ constructor(x, y, z, z1, z2) { -//│ let tmp3, tmp4, tmp5, tmp6, tmp7; -//│ this.y = y; -//│ this.z = z1; -//│ this.#z = z2; -//│ this.#x = 1; -//│ this.#y = this.y + 1; -//│ tmp3 = "x = " + this.#x; -//│ Predef.print(tmp3); -//│ tmp4 = "y = " + this.#y; -//│ Predef.print(tmp4); -//│ tmp5 = "this.y = " + this.y; -//│ Predef.print(tmp5); -//│ tmp6 = "z = " + this.#z; -//│ Predef.print(tmp6); -//│ tmp7 = "this.z = " + this.z; -//│ Predef.print(tmp7); +//│ throw "This code cannot be run as its compilation yielded an error."; //│ } -//│ #z; -//│ #x; -//│ #y; //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Foo", [null, "y", null, "z", null]]; //│ }); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +:re Foo(10, 20, 30, 40, 50) -//│ > x = 1 -//│ > y = 21 -//│ > this.y = 20 -//│ > z = 50 -//│ > this.z = 40 -//│ = Foo(_, 20, _, 40, _) +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e class Foo(val z, val z) //│ ╔══[COMPILATION ERROR] Duplicate definition of member named 'z'. //│ ╟── Defined at: -//│ ║ l.170: class Foo(val z, val z) +//│ ║ l.216: class Foo(val z, val z) //│ ║ ^^^^^ //│ ╟── Defined at: -//│ ║ l.170: class Foo(val z, val z) +//│ ║ l.216: class Foo(val z, val z) //│ ╙── ^^^^^ Foo(1, 2) diff --git a/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls new file mode 100644 index 0000000000..07bb557bf2 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls @@ -0,0 +1,258 @@ +:. +:js + +// Class objects are independent of constructor syntax and term overloads. +class Bare +class Empty() +class Primary(val x) +class Auxiliary { + constructor(val x) + fun value() = x +} +class Curried(val x)(val y) +class Overloaded { + fun answer() = 42 +} +val Overloaded = 7 +class Getter +fun Getter = throw "must not evaluate the term overload" +class Companion() +module Companion { + val value = 10 +} +//│ Overloaded = 7 + +:expect class Bare +Bare.class +//│ = class Bare + +:expect class Empty +Empty.class +//│ = class Empty + +:expect class Primary +Primary.class +//│ = class Primary + +:expect class Auxiliary +Auxiliary.class +//│ = class Auxiliary + +:expect class Curried +Curried.class +//│ = class Curried + +:expect class Overloaded +Overloaded.class +//│ = class Overloaded + +:expect class Getter +Getter.class +//│ = class Getter + +:expect class Companion { value: 10 } +Companion.class +//│ = class Companion { value: 10 } + +:expect 42 +(new Overloaded.class).answer() +//│ = 42 + +:expect 12 +(new Primary.class(12)).x +//│ = 12 + +:expect 13 +(new Auxiliary.class(13)).value() +//│ = 13 + +:expect 15 +(new Curried.class(14)(15)).y +//│ = 15 + +// Class-valued aliases and constructor aliases have different representations. +let raw = Primary.class +let ctor = Primary +//│ ctor = fun Primary { class: class Primary } +//│ raw = class Primary + +:expect true +raw == Primary.class +//│ = true + +:expect true +raw.class == raw +//│ = true + +:expect true +ctor.class == raw +//│ = true + +// Qualified, opened, and captured classes must retain their runtime receiver. +module Namespace { + class Item(val value) + class Plain + val Plain = 100 +} + +:expect class Item +Namespace.Item.class +//│ = class Item + +:expect class Plain +Namespace.Plain.class +//│ = class Plain + +open Namespace + +:expect true +Item.class == Namespace.Item.class +//│ = true + +fun captured(x) = { + class Local { + fun value() = x + } + val Local = 1 + (new Local.class).value() +} + +:expect 23 +captured(23) +//│ = 23 + +// Selecting the runtime class does not instantiate it. +class Explosive() { + throw "must not run the constructor" +} + +:expect class Explosive +Explosive.class +//│ = class Explosive + +:e +Empty().class +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.134: Empty().class +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Instance of class 'Empty' does not contain member 'class' +//│ ║ l.134: Empty().class +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +set Bare.class = 1 +//│ ╔══[COMPILATION ERROR] Assignment requires a term member +//│ ║ l.144: set Bare.class = 1 +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Specialization must survive class selection and subsequent construction. +class Generic[T](val value: T) +class Payload(val number: Int) + +:expect 31 +(new Generic[Payload].class(Payload(31))).value.number +//│ = 31 + +:expect 32 +(new Generic.class[Payload](Payload(32))).value.number +//│ = 32 + +:expect true +Generic[Payload].class == Generic.class +//│ = true + +// Each outer instance owns a distinct nested class object. +class Outer(val value) { + class Inner { + fun result() = value + } + val Inner = 0 +} +let first = Outer(41) +let second = Outer(42) +//│ first = Outer(41) +//│ second = Outer(42) + +:expect [41, 42, false] +[(new first.Inner.class).result(), (new second.Inner.class).result(), + first.Inner.class == second.Inner.class] +//│ = [41, 42, false] + +// Calling through a closure retains the selected class, even with inlining off. +fun runtimeClass() = Primary.class + +:noInline +:expect true +runtimeClass() == Primary.class +//│ = true + +// Forward references wait for the whole overload set to complete. +fun forwardClass() = Forward.class +class Forward +val Forward = false +//│ Forward = false + +:expect class Forward +forwardClass() +//│ = class Forward + +import "../../mlscript-compile/OverloadedClassLibrary.mls" + +:expect class Item +OverloadedClassLibrary.Item.class +//│ = class Item + +:expect 52 +(new OverloadedClassLibrary.Item.class).answer() +//│ = 52 + +// Ordinary fields and foreign dynamic selections keep their usual behavior. +let record = {"class": 5} +//│ record = {class: 5} + +:expect 5 +record.class +//│ = 5 + +:e +fun ambiguousClass(flag) = { + let mixed = if flag then Primary else Primary.class + mixed.class +} +ambiguousClass(true) +//│ ╔══[COMPILATION ERROR] This '.class' selection cannot mix class objects, constructor functions, and ordinary fields. +//│ ║ l.222: mixed.class +//│ ╙── ^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Later worksheet blocks may pass new constructors to an already compiled helper. +:expect true +private fun classOfValue(value) = value.class +classOfValue(Empty) == Empty.class +//│ = true + +:noInline +:expect true +classOfValue(Primary) == Primary.class +//│ = true + +// Foreign class declarations denote their native binding directly. +declare class String with + constructor(value: Str) + val length: Int +declare fun String(value: Str): Str + +:expect 5 +(new String.class("hello")).length +//│ = 5 + +:e +classOfValue(Primary.class) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.232: private fun classOfValue(value) = value.class +//│ ║ ^^^^^^^^^^^ +//│ ╟── This previously defined '.class' selection cannot mix class objects and constructor functions. +//│ ║ l.232: private fun classOfValue(value) = value.class +//│ ╙── ^^^^^^^^^^^ From a34b61a0d7a08fb284102fe408868177a4401348 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 3 Oct 2026 12:36:47 +0800 Subject: [PATCH 201/228] Restrict .class syntax to class declarations --- .../main/scala/hkmc2/codegen/Lowering.scala | 18 +-- .../scala/hkmc2/semantics/NewResolver.scala | 67 +++++------ .../src/main/scala/hkmc2/semantics/Term.scala | 16 ++- .../src/test/mlscript/newres/ClassValues.mls | 108 ++++++++++++------ 4 files changed, 106 insertions(+), 103 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 49ace61e02..9c0aaf0ec2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -702,20 +702,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu source = Diagnostic.Source.Compilation) else k(prefix, memberIdent(id, N), N) - /** Class selection uses the declaration binding or unwraps a constructor - * value, never the companion's ordinary member namespace. Mixed runtime - * representations cannot share one property access safely. - */ - private def classValue(sel: NewSel)(k: Path => Block)(using LoweringCtx): Block = - if sel.isErroneous then compError - else if sel.hasDynamicTarget || sel.resolvedMembers.nonEmpty || sel.classValueAccesses.size != 1 then fail: - ErrorReport(msg"This '.class' selection cannot mix class objects, constructor functions, and ordinary fields." -> sel.toLoc :: Nil, - source = Diagnostic.Source.Compilation) - else sel.classValueAccesses.head match - case ClassValueAccess.Declaration => classOf(sel.prefix, sel)(k) - case ClassValueAccess.Constructor => subTerm_nonTail(sel.prefix)(p => k(p.selSN("class"))) - case ClassValueAccess.Value => subTerm(sel.prefix)(k) - private def selectionPath(sel: NewSel, prefix: Path, name: Tree.Ident, target: Opt[DefinitionSymbol[?]]): Path = sel.tupleIndex match case S(index) => DynSelect(prefix, Value.Lit(Tree.IntLit(BigInt(index)))(N), true)(sel.toLoc) @@ -1163,7 +1149,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu instantiated match // * Due to whacky JS semantics, we need to make sure that selections leading to a call // * are preserved in the call and not moved to a temporary variable. - case sel: NewSel if sel.classValueAccesses.nonEmpty => classValue(sel)(conclude) + case sel: NewSel if sel.isClassValue => classOf(sel.prefix, sel)(conclude) case sel: NewSel => newSelection(sel): (prefix, name, target) => subTerm_nonTail(prefix): p => conclude(selectionPath(sel, p, name, target)) @@ -1324,7 +1310,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case whltrm: st.SynthWhile => ucs.Normalization(this)(whltrm)(k) - case sel: NewSel if sel.classValueAccesses.nonEmpty => classValue(sel)(k) + case sel: NewSel if sel.isClassValue => classOf(sel.prefix, sel)(k) case sel: NewSel => newSelection(sel): (prefix, name, target) => if sel.tupleIndex.nonEmpty then subTerm_nonTail(prefix)(p => k(selectionPath(sel, p, name, target))) else setupNamedSelection(prefix, name, target, sel.toLoc)(k) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index cf9630fe08..780f71c2da 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2008,7 +2008,7 @@ class NewResolver: // An ambiguous lookup can publish several constructors. Pair each // declaration only with its own references; the lookup diagnoses // competing candidates independently of this shape subscription. - case _: ClassDef => listenClass(lhs, true)(ref => + case _: ClassDef => listenClass(lhs, allowValueAliases = true)(ref => if ref.definition eq cls then publish(ref.marks) , _ => ()) case _: ModuleOrObjectDef => publish(Nil) @@ -2620,15 +2620,14 @@ class NewResolver: * companions. Aliases can supply constructor shapes; applied instances cannot. * Capture marks are retained for subsequent instance-member lookup. */ private case class ClassReference(definition: ClassDef, marks: Ls[Marks], - instances: TypeSubstitution, supplied: Opt[Ls[DeclaredType]], access: ClassValueAccess) + instances: TypeSubstitution, supplied: Opt[Ls[DeclaredType]]) - // Class consumers such as `new` attach the class target to the operand. - // `.class` instead keeps value aliases bound to their original storage and - // records how to extract the class on the selection itself. - private def listenClass(trm: Term, recordTarget: Bool)(selected: ShapeListener[ClassReference], reject: ShapeListener[TermShape])(using NewResolverState): Unit = + // `new` and projections can follow inferred class values. `.class` selects + // only declarations: evaluating a class reference as a term loses that syntax. + private def listenClass(trm: Term, allowValueAliases: Bool)(selected: ShapeListener[ClassReference], reject: ShapeListener[TermShape])(using NewResolverState): Unit = def select(ref: ClassReference)(using NewResolverState): Unit = trm.classHead match - case target: NewResolvable if recordTarget || ref.access == ClassValueAccess.Declaration => + case target: NewResolvable => rstate.recordResolution(target, target.resolvedTargets.contains(ref.definition.sym))( target.resolvedTargets ::= ref.definition.sym) case _ => () @@ -2638,21 +2637,16 @@ class NewResolver: val marks = source.applicationHead._2 def fromDefinition(ds: DefnShape): Unit = ds.clsDef match - case S(cls: ClassDef) => - val access = ds.defn match - case td: TermDefinition if td.tsym.isInstanceOf[ClassCtorSymbol] && cls.hasDeclareModifier.isEmpty => - ClassValueAccess.Constructor - case _ => ClassValueAccess.Value - select(ClassReference(cls, marks, instances, supplied, access)) + case S(cls: ClassDef) => select(ClassReference(cls, marks, instances, supplied)) case _ => reject(sh) source match - case Marked(ds: DefnShape, _) => fromDefinition(ds) + case Marked(ds: DefnShape, _) if allowValueAliases => fromDefinition(ds) case _ => reject(sh) trm match // Local variables store the term interpretation of their initializer, even // when their flow still contains its unconverted overload-set shape. - case _: SimpleRef if !recordTarget => listenTerm(trm)(value) - case application @ TyApp(base, args) => listenClass(base, recordTarget)(ref => + case _: SimpleRef if !allowValueAliases => listenTerm(trm)(value) + case application @ TyApp(base, args) => listenClass(base, allowValueAliases)(ref => val count = if ref.supplied.nonEmpty then 0 else ref.definition.tparams.length if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then resolError(trm, msg"Class '${ref.definition.sym.nme}' expected ${count} type ${ @@ -2665,7 +2659,7 @@ class NewResolver: select(ref.copy(instances = instances, supplied = S(supplied))) , reject) case Capture(base, thru) => - listenClass(base, recordTarget)(ref => select(ref.copy(marks = + listenClass(base, allowValueAliases)(ref => select(ref.copy(marks = ref.marks ::: EntryMark(ResolutionBoundary(thru), N, NoMarks) :: Nil)), reject) case _ => listen(trm): case sh: SymShape => @@ -2674,9 +2668,9 @@ class NewResolver: sh match case contextual: ContextualSymShape => contextual.instances case _ => TypeSubstitution.empty - , N, ClassValueAccess.Declaration)), + , N)), () => fromSymbol(sh, value, trm, - sym => if !recordTarget then recordValueTarget(trm, sym), !recordTarget)) + sym => if !allowValueAliases then recordValueTarget(trm, sym), !allowValueAliases)) case sh: TermShape => value(sh) def newSel(sel: NewSel)(using NewResolverState): Unit = @@ -2717,34 +2711,25 @@ class NewResolver: case _ => () sel.cls match case N if sel.id.name == "class" => - // Preserve a class interpretation even when the declaration also has a - // constructor, a module, or an unrelated term overload. Value aliases - // retain their runtime path and only unwrap actual constructor values. - listenClass(sel.prefix, false)(ref => + // Resolve the class overload directly, without selecting its constructor + // or companion. The resolved class symbol also identifies this selection + // during lowering; no ordinary member named `class` is involved. + listenClass(sel.prefix, allowValueAliases = false)(ref => val cd = ref.definition - if rstate.canResolve(sel) then - rstate.recordResolution(sel, sel.classValueAccesses(ref.access))(sel.classValueAccesses += ref.access) - rstate.recordResolution(sel, sel.resolvedTargets.contains(cd.sym))(sel.resolvedTargets ::= cd.sym) - // A compiled helper can receive other constructors, but its emitted - // property access cannot change into an identity operation (or vice versa). - // Transport the new class's shape without mutating completed targets. - if sel.classValueAccesses(ref.access) then - listenExt(cd, ext => - val shape = defnShapes.getOrElseUpdate(cd.sym, DefnShape(cd, ext)) - val specialized = ref.supplied.fold[TermShape](shape)(args => SpecializedShape(shape, args, ref.instances)) - transportShape(specialized, ref.marks) match - case value: TermShape => publishViewed(sel, value, ref.instances) - case NoShape => ()) - else if !rstate.hasError(sel) then - rstate.markError(sel) - resolError(sel, msg"This previously defined '.class' selection cannot mix class objects and constructor functions." -> sel.toLoc :: Nil) + rstate.recordResolution(sel, sel.resolvedTargets.contains(cd.sym))(sel.resolvedTargets ::= cd.sym) + listenExt(cd, ext => + val shape = defnShapes.getOrElseUpdate(cd.sym, DefnShape(cd, ext)) + val specialized = ref.supplied.fold[TermShape](shape)(args => SpecializedShape(shape, args, ref.instances)) + transportShape(specialized, ref.marks) match + case value: TermShape => publishViewed(sel, value, ref.instances) + case NoShape => ()) , shape => member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty)) case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty) case S(cls) => - listenClass(cls, true)(ref => + listenClass(cls, allowValueAliases = true)(ref => val cd = ref.definition val marks = ref.marks val candidate = cd.sym -> marks @@ -2812,7 +2797,7 @@ class NewResolver: // listenClass preserves captures and supplies the same class-body exit as a // constructor value. In particular, a captured constructor already carries // that exit; adding a second one here would duplicate its instance boundary. - listenClass(nw.cls, true)(ref => + listenClass(nw.cls, allowValueAliases = true)(ref => val cd = ref.definition val marks = ref.marks val callerInstances = rstate.instances diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 57833e9e48..ad78e961aa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -342,13 +342,6 @@ sealed trait NewRefImpl extends AnyRefImpl: // def tree: Tree.Ident = Tree.Dummy def refNum: Int = 0 // TODO -/** How a class reference reaches its runtime class object. Declaration references - * use the backend's class binding; constructor values carry it in `.class`; - * values obtained from an earlier class selection already are class objects. - */ -enum ClassValueAccess: - case Declaration, Constructor, Value - sealed trait NewSelImpl extends NewResolvableImpl: self: Term.NewSel => // At least one receiver permits runtime lookup without a static member symbol. @@ -359,7 +352,11 @@ sealed trait NewSelImpl extends NewResolvableImpl: var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution // Class identity and captures must survive even when candidates share an inherited member. var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil - var classValueAccesses: Set[ClassValueAccess] = Set.empty + // Unlike a field whose value happens to be a class, C.class has a class + // target without an ordinary member. Its receiver remains the class reference. + def isClassValue(using Erasure): Bool = + !isErroneous && self.cls.isEmpty && self.id.name == "class" && resolvedMembers.isEmpty && + !hasDynamicTarget && resolvedTargets.exists(_.isInstanceOf[ClassSymbol]) def hasAmbiguousClass(using Erasure): Bool = hasAmbiguousClassImpl // Also used by the guarded symbol lookup for completed imports. private[semantics] def hasAmbiguousClassImpl: Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: @@ -654,7 +651,6 @@ enum Term extends Statement, AutoLocated, ShapePublisher: val copy = NewSel(prefix.mkClone, id, cls.map(_.mkClone))(term.resSym) copy.resolvedMembers = term.resolvedMembers copy.resolvedClasses = term.resolvedClasses - copy.classValueAccesses = term.classValueAccesses copy.hasDynamicTarget = term.hasDynamicTarget copyNewResolution(term, copyShapes(term, copy)) case term @ UnresolvedRef(prefixes, id) => @@ -1029,6 +1025,8 @@ sealed trait Statement extends Located, ProductWithExtraInfo, Describable: if summon[ShowCfg].showFlowSymbols then doc"$pre${ sel.resolvedMembers match + case Nil if sel.resolvedTargets.nonEmpty => + doc"$str‹" :: sel.resolvedTargets.distinct.map(_.showName).mkDocument(", ") :: doc"›" case Nil => doc"${str}ˀˀˀ" case t :: Nil => t.showName case ts => doc"$str‹" :: ts.map(_.showName).mkDocument(", ") :: doc"›" diff --git a/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls index 07bb557bf2..decf7768e2 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls @@ -23,11 +23,19 @@ module Companion { //│ Overloaded = 7 :expect class Bare +:sr Bare.class +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ Bare⁰.class‹Bare¹› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = class Bare :expect class Empty +:sr Empty.class +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ Empty⁰.class‹Empty¹› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = class Empty :expect class Primary @@ -70,23 +78,67 @@ Companion.class (new Curried.class(14)(15)).y //│ = 15 -// Class-valued aliases and constructor aliases have different representations. +// Evaluated terms do not preserve the class-selection syntax. let raw = Primary.class let ctor = Primary +val saved = Primary //│ ctor = fun Primary { class: class Primary } //│ raw = class Primary +//│ saved = fun Primary { class: class Primary } :expect true raw == Primary.class //│ = true -:expect true -raw.class == raw -//│ = true +:e +raw.class +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.94: raw.class +//│ ║ ^^^^^^^^^ +//│ ╟── Class value does not contain member 'class' +//│ ║ l.7: class Primary(val x) +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -:expect true -ctor.class == raw -//│ = true +:e +ctor.class +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.104: ctor.class +//│ ║ ^^^^^^^^^^ +//│ ╟── Class 'Primary' does not contain member 'class' +//│ ║ l.7: class Primary(val x) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +saved.class +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.114: saved.class +//│ ║ ^^^^^^^^^^^ +//│ ╟── Class 'Primary' does not contain member 'class' +//│ ║ l.7: class Primary(val x) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +Primary.class.class +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.124: Primary.class.class +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class value does not contain member 'class' +//│ ║ l.7: class Primary(val x) +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +({make: Primary}).make.class +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.134: ({make: Primary}).make.class +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Primary' does not contain member 'class' +//│ ║ l.7: class Primary(val x) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Qualified, opened, and captured classes must retain their runtime receiver. module Namespace { @@ -133,17 +185,17 @@ Explosive.class :e Empty().class //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.134: Empty().class +//│ ║ l.186: Empty().class //│ ║ ^^^^^^^^^^^^^ //│ ╟── Instance of class 'Empty' does not contain member 'class' -//│ ║ l.134: Empty().class +//│ ║ l.186: Empty().class //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e set Bare.class = 1 //│ ╔══[COMPILATION ERROR] Assignment requires a term member -//│ ║ l.144: set Bare.class = 1 +//│ ║ l.196: set Bare.class = 1 //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -216,27 +268,17 @@ let record = {"class": 5} record.class //│ = 5 +// Passing a constructor as an argument does not preserve class-selection syntax. :e -fun ambiguousClass(flag) = { - let mixed = if flag then Primary else Primary.class - mixed.class -} -ambiguousClass(true) -//│ ╔══[COMPILATION ERROR] This '.class' selection cannot mix class objects, constructor functions, and ordinary fields. -//│ ║ l.222: mixed.class -//│ ╙── ^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. - -// Later worksheet blocks may pass new constructors to an already compiled helper. -:expect true private fun classOfValue(value) = value.class -classOfValue(Empty) == Empty.class -//│ = true - -:noInline -:expect true -classOfValue(Primary) == Primary.class -//│ = true +classOfValue(Empty) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.273: private fun classOfValue(value) = value.class +//│ ║ ^^^^^^^^^^^ +//│ ╟── Class 'Empty' does not contain member 'class' +//│ ║ l.6: class Empty() +//│ ╙── ^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Foreign class declarations denote their native binding directly. declare class String with @@ -248,11 +290,3 @@ declare fun String(value: Str): Str (new String.class("hello")).length //│ = 5 -:e -classOfValue(Primary.class) -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.232: private fun classOfValue(value) = value.class -//│ ║ ^^^^^^^^^^^ -//│ ╟── This previously defined '.class' selection cannot mix class objects and constructor functions. -//│ ║ l.232: private fun classOfValue(value) = value.class -//│ ╙── ^^^^^^^^^^^ From 11d30be6a9b15b3a8e18adb3224054724f6e1767 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 3 Oct 2026 13:04:28 +0800 Subject: [PATCH 202/228] Instantiate inferred runtime class values --- .../main/scala/hkmc2/codegen/Lowering.scala | 64 ++++--- .../scala/hkmc2/semantics/NewResolver.scala | 45 +++-- .../main/scala/hkmc2/semantics/Shape.scala | 14 ++ .../test/mlscript/codegen/PlainClasses.mls | 36 ++-- .../src/test/mlscript/newres/ClassValues.mls | 159 ++++++++++++++++-- 5 files changed, 239 insertions(+), 79 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 9c0aaf0ec2..c6a81abfb6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -188,9 +188,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu new TailOp(transfersControl = true): override def apply(k: Result): Block = Ret(castTo(k, returnType, N)) - def parentConstructor(parentClsPath: Path, cls: Term, args: Ls[Term], loc: Opt[Loc])(using LoweringCtx) = + def parentConstructor(ctorParamLists: Ls[ParamList], args: Ls[Term], loc: Opt[Loc])(using LoweringCtx) = lowerSuperCtorCall( - parentClsPath, + ctorParamLists, State.builtinOpsMap("super").asSimpleRef, isMlsFun = true, args, @@ -401,8 +401,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu assert(k isnt syntax.Mod) // modules can't extend things and can't have super calls val cfgOverride = defn.extraAnnotations.collectFirst: case Annot.Config(modify) => modify(config) - classOf(ext.cls, ext): clsp => - val pctor = inScopedBlock(parentConstructor(clsp, ext.cls, ext.args, ext.toLoc)) + classOf(ext.cls, ext): (clsp, params) => + val pctor = inScopedBlock(parentConstructor(params, ext.args, ext.toLoc)) Define( ClsLikeDefn( defn.owner, defn.sym, defn.bsym, defn.ctorSym, defn.kind, defn.paramsOpt, defn.auxParams, S(clsp), @@ -415,18 +415,37 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu blockImpl(imps ::: funs ::: rest, res) - def classOf(trm: Term, nw: PossiblyErroneous)(k: Path => Block)(using LoweringCtx): Block = + def classOf(trm: Term, nw: PossiblyErroneous)(k: (Path, Ls[ParamList]) => Block)(using LoweringCtx): Block = + def classValue: Block = nw match + case term: New if !term.isErroneous => + NewShape.classesOf(term) match + case cls :: Nil => + subTerm(trm): value => + // Read a stored class before evaluating arguments or building a + // partial constructor, even if its binding is assigned later. + val saved = loweringCtx.registerTempSymbol(S(trm), erasedType = N, "classValue") + Assign(saved, value, k(saved.asSimpleRef, classParamLists(cls.defn.get))) + case Nil => fail: + ErrorReport(msg"Cannot resolve the class instantiated here" -> trm.toLoc :: Nil, + source = Diagnostic.Source.Compilation) + case classes => fail: + ErrorReport(msg"The class instantiated here is ambiguous" -> trm.toLoc :: + classes.map(cls => msg"class '${cls.nme}' defined here" -> cls.toLoc), + source = Diagnostic.Source.Compilation) + case _ => compError if newResolution then trm.classHead match case resolved @ Resolved(_, _: ClassSymbol) => // Synthesized runtime constructors already carry an explicit class target. - subTerm(resolved)(k) + subTerm(resolved)(p => k(p, getClassParamLists(p))) + case _: SimpleRef => classValue case resl: NewResolvable => resl.resolvedTargets.distinct match case cls :: Nil => cls.defn match case S(clsDef: ClassLikeDef) => - subTerm(resl)(k) + subTerm(resl)(p => k(p, classParamLists(clsDef))) + case _ if nw.isInstanceOf[New] => classValue case _ => softAssert(resl.isErroneous, s"Unexpected `new` target: ${cls.showDbg}") compError @@ -444,17 +463,18 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case _ => softAssert(nw.isErroneous, "Unexpected class term shape") compError - else subTerm(trm)(k) + else subTerm(trm)(p => k(p, getClassParamLists(p))) + private def classParamLists(cls: ClassLikeDef): Ls[ParamList] = cls.paramsOpt.toList ::: cls.auxParams + def getClassParamLists(cls: Path): Ls[ParamList] = cls.targetSymbol match case S(clsSym: ClassSymbol) => - clsSym.defn.map(clsDef => clsDef.paramsOpt.toList ::: clsDef.auxParams).getOrElse(Nil) + clsSym.defn.map(classParamLists).getOrElse(Nil) case _ => Nil // * Lowers the `super(...)(...)` call we get from the `extends C(...)(...)` syntax - def lowerSuperCtorCall(parentClsPth: Path, fr: Path, isMlsFun: Bool, args: List[Term], loc: Opt[Loc])(k: Result => Block)(using LoweringCtx): Block = - val ctorParamLists = getClassParamLists(parentClsPth) + def lowerSuperCtorCall(ctorParamLists: Ls[ParamList], fr: Path, isMlsFun: Bool, args: List[Term], loc: Opt[Loc])(k: Result => Block)(using LoweringCtx): Block = args match case _ :: _ => def zipArgs(remainingParamss: Ls[ParamList], args: Ls[Term], acc: Ls[Ls[Arg]]): Block = (remainingParamss, args) match @@ -525,7 +545,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu * when they correspond to constructor parameter lists of the same class. * If fewer argument lists are provided than constructor parameter lists, eta-expands * the missing ones with fresh lambdas (avoiding reliance on mutable JS class curry semantics). */ - def lowerMultiInstantiate(mut: Bool, cls: Path, args: Ls[Term], annotations: Ls[Annot], loc: Opt[Loc])(k: Result => Block)(using LoweringCtx): Block = + def lowerMultiInstantiate(mut: Bool, cls: Path, ctorParamLists: Ls[ParamList], args: Ls[Term], annotations: Ls[Annot], loc: Opt[Loc])(k: Result => Block)(using LoweringCtx): Block = // Nullary instantiations are represented with one empty argument list, matching existing `Instantiate` usage. def buildInstantiate(argss: Ls[Ls[Arg]]): Instantiate = Instantiate(mut, cls, if argss.isEmpty then Nil :: Nil else argss)(InstantiateMetadata(annotations), loc) @@ -558,12 +578,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val freshArgs = freshSyms.map(s => Arg(N, s.asSimpleRef)) Lambda(freshParamList, Return(etaExpand(rest, accArgss :+ freshArgs)))(Nil, loc) k(etaExpand(remainingParamss, acc.reverse)) - // * Resolve the class definition to get the constructor param lists. - // * The class path typically resolves to a TermSymbol (the constructor function), - // * so we also look up the owner InnerSymbol via TermDefinition#owner. - // * Note: apparently, this can also be accessed through TermDefinition#companionClass - // * (what Copilot initially used), which is weird. - val ctorParamLists = getClassParamLists(cls) if ctorParamLists.isEmpty then // * Need to specially handle no-param classes args match @@ -1149,7 +1163,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu instantiated match // * Due to whacky JS semantics, we need to make sure that selections leading to a call // * are preserved in the call and not moved to a temporary variable. - case sel: NewSel if sel.isClassValue => classOf(sel.prefix, sel)(conclude) + case sel: NewSel if sel.isClassValue => classOf(sel.prefix, sel)((p, _) => conclude(p)) case sel: NewSel => newSelection(sel): (prefix, name, target) => subTerm_nonTail(prefix): p => conclude(selectionPath(sel, p, name, target)) @@ -1310,7 +1324,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case whltrm: st.SynthWhile => ucs.Normalization(this)(whltrm)(k) - case sel: NewSel if sel.isClassValue => classOf(sel.prefix, sel)(k) + case sel: NewSel if sel.isClassValue => classOf(sel.prefix, sel)((p, _) => k(p)) case sel: NewSel => newSelection(sel): (prefix, name, target) => if sel.tupleIndex.nonEmpty then subTerm_nonTail(prefix)(p => k(selectionPath(sel, p, name, target))) else setupNamedSelection(prefix, name, target, sel.toLoc)(k) @@ -1348,22 +1362,22 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case DynNew(cls, args) => - subTerm(cls)(sr => lowerMultiInstantiate(false, sr, args, annots, trm.toLoc)(k)) + subTerm(cls)(sr => lowerMultiInstantiate(false, sr, getClassParamLists(sr), args, annots, trm.toLoc)(k)) case Mut(DynNew(cls, args)) => - subTerm(cls)(sr => lowerMultiInstantiate(true, sr, args, annots, trm.toLoc)(k)) + subTerm(cls)(sr => lowerMultiInstantiate(true, sr, getClassParamLists(sr), args, annots, trm.toLoc)(k)) case nw @ (_: New | Mut(_: New)) => val (mut, cls, as, rft, nwtrm) = nw match case nwtrm @ New(c, a, r) => (false, c, a, r, nwtrm) case Mut(nwtrm @ New(c, a, r)) => (true, c, a, r, nwtrm) case _ => spuriousWarning - classOf(cls, nwtrm): sr => + classOf(cls, nwtrm): (sr, params) => rft match - case N => lowerMultiInstantiate(mut, sr, as, annots, trm.toLoc)(k) + case N => lowerMultiInstantiate(mut, sr, params, as, annots, trm.toLoc)(k) case S((isym, rft)) => val sym = new BlockMemberSymbol(isym.name, Nil) loweringCtx.collectScopedSym(sym) val (mtds, publicFlds, privateFlds, ctor) = gatherMembers(rft) - val pctor = parentConstructor(sr, cls, as, nw.toLoc) + val pctor = parentConstructor(params, as, nw.toLoc) val clsDef = ClsLikeDefn(N, isym, sym, N, syntax.Cls, N, Nil, S(sr), mtds, privateFlds, publicFlds, pctor, ctor, N, N)(N, Nil) val inner = new New(sym.ref().resolved(isym), Nil, N)(FlowSymbol.neww(), N) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 780f71c2da..95263d55aa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2625,19 +2625,21 @@ class NewResolver: // `new` and projections can follow inferred class values. `.class` selects // only declarations: evaluating a class reference as a term loses that syntax. private def listenClass(trm: Term, allowValueAliases: Bool)(selected: ShapeListener[ClassReference], reject: ShapeListener[TermShape])(using NewResolverState): Unit = - def select(ref: ClassReference)(using NewResolverState): Unit = - trm.classHead match - case target: NewResolvable => - rstate.recordResolution(target, target.resolvedTargets.contains(ref.definition.sym))( - target.resolvedTargets ::= ref.definition.sym) - case _ => () + def select(ref: ClassReference, target: Opt[DefinitionSymbol[?]])(using NewResolverState): Unit = + target.foreach(recordValueTarget(trm.classHead, _)) selected(ref) - def value(sh: TermShape)(using NewResolverState): Unit = + def value(sh: TermShape, origin: Opt[SymShape])(using NewResolverState): Unit = val ShapeParts(source, instances, supplied) = shapeParts(sh) val marks = source.applicationHead._2 def fromDefinition(ds: DefnShape): Unit = ds.clsDef match - case S(cls: ClassDef) => select(ClassReference(cls, marks, instances, supplied)) + case S(cls: ClassDef) => + // A stored class object must be read from its binding. Constructor + // aliases instead use the class interpretation of their reference. + val target = ds.defn match + case _: ClassDef => origin.flatMap(s => valueTarget(s.sym, false)) + case _ => S(cls.sym) + select(ClassReference(cls, marks, instances, supplied), target) case _ => reject(sh) source match case Marked(ds: DefnShape, _) if allowValueAliases => fromDefinition(ds) @@ -2645,21 +2647,26 @@ class NewResolver: trm match // Local variables store the term interpretation of their initializer, even // when their flow still contains its unconverted overload-set shape. - case _: SimpleRef if !allowValueAliases => listenTerm(trm)(value) + case _: SimpleRef => listenTerm(trm): shape => + // A constructor function is not a runtime class object. In particular, + // `let ctor = C` does not grant `new ctor` a class interpretation. + shapeParts(shape).value match + case Marked(ds: DefnShape, _) if ds.defn.isInstanceOf[ClassDef] => value(shape, N) + case _ => reject(shape) case application @ TyApp(base, args) => listenClass(base, allowValueAliases)(ref => val count = if ref.supplied.nonEmpty then 0 else ref.definition.tparams.length if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then resolError(trm, msg"Class '${ref.definition.sym.nme}' expected ${count} type ${ "argument".pluralized(count)}, but got ${args.length}" -> ref.definition.toLoc :: Nil) val supplied = args.map(arg => declaredType(typeResolution(arg), Map.empty).instantiate(rstate.instances)) - if ref.supplied.nonEmpty then select(ref) + if ref.supplied.nonEmpty then selected(ref) else val instances = instantiateParameters(ref.definition.sym, ref.definition.tparams.map(_.sym), rstate.typeApplicationSite(application), ref.marks, rstate.instances.withOverrides(ref.instances), S(supplied)) - select(ref.copy(instances = instances, supplied = S(supplied))) + selected(ref.copy(instances = instances, supplied = S(supplied))) , reject) case Capture(base, thru) => - listenClass(base, allowValueAliases)(ref => select(ref.copy(marks = + listenClass(base, allowValueAliases)(ref => selected(ref.copy(marks = ref.marks ::: EntryMark(ResolutionBoundary(thru), N, NoMarks) :: Nil)), reject) case _ => listen(trm): case sh: SymShape => @@ -2668,10 +2675,10 @@ class NewResolver: sh match case contextual: ContextualSymShape => contextual.instances case _ => TypeSubstitution.empty - , N)), - () => fromSymbol(sh, value, trm, + , N), S(cls.sym)), + () => fromSymbol(sh, shape => value(shape, S(sh)), trm, sym => if !allowValueAliases then recordValueTarget(trm, sym), !allowValueAliases)) - case sh: TermShape => value(sh) + case sh: TermShape => value(sh, N) def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") @@ -2785,10 +2792,10 @@ class NewResolver: def resolveNew(nw: Term.New)(using NewResolverState): Unit = nw.cls.classHead match case Term.Error() => rstate.markError(nw) - // Static construction records a resolved class on its reference for lowering. + // Class references resolve symbolically; variables must carry a class value. // Other expression forms require dynamic construction, even if they publish // no shapes (for example an unsupported reference in an extends clause). - case _: NewResolvable => resolveNewClass(nw) + case _: NewResolvable | _: SimpleRef => resolveNewClass(nw) case _ => rstate.markError(nw) resolError(nw, msg"Invalid class expression: ${nw.cls.describe}" -> nw.cls.toLoc :: Nil) @@ -2799,6 +2806,10 @@ class NewResolver: // that exit; adding a second one here would duplicate its instance boundary. listenClass(nw.cls, allowValueAliases = true)(ref => val cd = ref.definition + // Constructor arity is compiled from these shapes even when the runtime + // operand is a variable with no resolved class symbol of its own. + assert(rstate.canResolve(nw) || NewShape.classesOf(nw).contains(cd.sym), + "Inference changed a completed constructor target") val marks = ref.marks val callerInstances = rstate.instances listenExt(cd, extsh => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index d6345d0444..845b62ec0f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -412,6 +412,20 @@ class NewShape(val receiver: DefnShape, val cls: ClassLikeSymbol, val clsMarks: override def toString: String = s"NewNewShape(${cls.showDbg}, $argss)" def toLoc: Opt[Loc] = src.toLoc +object NewShape: + /** Constructor parameter lists are fixed when the originating unit is compiled. + * Read its original shapes, ignoring substitutions and capture paths, both + * for lowering and to check that later inference preserves this class identity. + */ + def classesOf(term: Term.New): Ls[ClassSymbol] = + def classes(event: ShapeEvent): Ls[ClassSymbol] = event match + case ActivatedShapeEvent(value, _) => classes(value) + case MarkedShape(value, _) => classes(value) + case ContextualShape(value, _) => classes(value) + case shape: NewShape => shape.cls.asCls.toList + case _ => Nil + term.getShapes.flatMap(classes).distinct + sealed abstract class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: Ls[Marks]) extends Shape: def describe: Str = s"${sym.describe} symbol '${sym.nme}'" def toLoc: Opt[Loc] = sym.toLoc diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index 603e1c7b00..bd416521be 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -125,18 +125,13 @@ let t = test() //│ > ok //│ t = class Foo -:e +:expect Foo new t -//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.129: new t -//│ ║ ^^^^^ -//│ ╟── Invalid class expression: reference -//│ ║ l.129: new t -//│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ throw "This code cannot be run as its compilation yielded an error." +//│ let classValue; classValue = t; return globalThis.Object.freeze(new classValue()) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ > hi +//│ = Foo new! t //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— @@ -145,18 +140,13 @@ new! t //│ > hi //│ = Foo -:e +:expect Foo new t() -//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.149: new t() -//│ ║ ^^^^^^^ -//│ ╟── Invalid class expression: reference -//│ ║ l.149: new t() -//│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ throw "This code cannot be run as its compilation yielded an error." +//│ let classValue1; classValue1 = t; return globalThis.Object.freeze(new classValue1()) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ > hi +//│ = Foo new! t() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— @@ -273,10 +263,10 @@ class Foo with val x = 2 //│ ╔══[COMPILATION ERROR] Multiple definitions of symbol 'x' //│ ╟── defined here -//│ ║ l.272: val x = 1 +//│ ║ l.262: val x = 1 //│ ║ ^^^^^^^^^ //│ ╟── defined here -//│ ║ l.273: val x = 2 +//│ ║ l.263: val x = 2 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo14; @@ -298,10 +288,10 @@ class Foo with val x = 1 let x = 2 //│ ╔══[COMPILATION ERROR] Name 'x' is already used -//│ ║ l.299: let x = 2 +//│ ║ l.289: let x = 2 //│ ║ ^^^^^ //│ ╟── by a member declared in the same block -//│ ║ l.298: val x = 1 +//│ ║ l.288: val x = 1 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo16; @@ -327,7 +317,7 @@ class Foo with :e class Foo with val x = 1 //│ ╔══[COMPILATION ERROR] Illegal body of class definition (should be a block; found term definition). -//│ ║ l.328: class Foo with val x = 1 +//│ ║ l.318: class Foo with val x = 1 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo18; diff --git a/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls index decf7768e2..83fa53c1ea 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls @@ -67,15 +67,15 @@ Companion.class //│ = 42 :expect 12 -(new Primary.class(12)).x +new Primary.class(12).x //│ = 12 :expect 13 -(new Auxiliary.class(13)).value() +new Auxiliary.class(13).value() //│ = 13 :expect 15 -(new Curried.class(14)(15)).y +new Curried.class(14)(15).y //│ = 15 // Evaluated terms do not preserve the class-selection syntax. @@ -100,10 +100,24 @@ raw.class //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +:expect fun +new raw +//│ = fun + +:e +new ctor +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.108: new ctor +//│ ║ ^^^^^^^^ +//│ ╟── Class 'Primary' cannot be instantiated with keyword 'new'. +//│ ║ l.7: class Primary(val x) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + :e ctor.class //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.104: ctor.class +//│ ║ l.118: ctor.class //│ ║ ^^^^^^^^^^ //│ ╟── Class 'Primary' does not contain member 'class' //│ ║ l.7: class Primary(val x) @@ -113,7 +127,7 @@ ctor.class :e saved.class //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.114: saved.class +//│ ║ l.128: saved.class //│ ║ ^^^^^^^^^^^ //│ ╟── Class 'Primary' does not contain member 'class' //│ ║ l.7: class Primary(val x) @@ -123,7 +137,7 @@ saved.class :e Primary.class.class //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.124: Primary.class.class +//│ ║ l.138: Primary.class.class //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Class value does not contain member 'class' //│ ║ l.7: class Primary(val x) @@ -133,7 +147,7 @@ Primary.class.class :e ({make: Primary}).make.class //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.134: ({make: Primary}).make.class +//│ ║ l.148: ({make: Primary}).make.class //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Primary' does not contain member 'class' //│ ║ l.7: class Primary(val x) @@ -185,17 +199,17 @@ Explosive.class :e Empty().class //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.186: Empty().class +//│ ║ l.200: Empty().class //│ ║ ^^^^^^^^^^^^^ //│ ╟── Instance of class 'Empty' does not contain member 'class' -//│ ║ l.186: Empty().class +//│ ║ l.200: Empty().class //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e set Bare.class = 1 //│ ╔══[COMPILATION ERROR] Assignment requires a term member -//│ ║ l.196: set Bare.class = 1 +//│ ║ l.210: set Bare.class = 1 //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -204,11 +218,11 @@ class Generic[T](val value: T) class Payload(val number: Int) :expect 31 -(new Generic[Payload].class(Payload(31))).value.number +new Generic[Payload].class(Payload(31)).value.number //│ = 31 :expect 32 -(new Generic.class[Payload](Payload(32))).value.number +new Generic.class[Payload](Payload(32)).value.number //│ = 32 :expect true @@ -273,7 +287,7 @@ record.class private fun classOfValue(value) = value.class classOfValue(Empty) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.273: private fun classOfValue(value) = value.class +//│ ║ l.287: private fun classOfValue(value) = value.class //│ ║ ^^^^^^^^^^^ //│ ╟── Class 'Empty' does not contain member 'class' //│ ║ l.6: class Empty() @@ -287,6 +301,123 @@ declare class String with declare fun String(value: Str): Str :expect 5 -(new String.class("hello")).length +new String.class("hello").length //│ = 5 +// Class values retain constructor arity and captured receivers through bindings. +:expect 16 +new raw(16).x +//│ = 16 + +let rawCurried = Curried.class +//│ rawCurried = class Curried + +:expect 18 +new rawCurried(17)(18).y +//│ = 18 + +let partial = new rawCurried(19) +//│ partial = fun partial + +:expect 20 +partial(20).y +//│ = 20 + +val storedClass = Primary.class +let classRecord = {make: Primary.class} +//│ classRecord = {make: class Primary} +//│ storedClass = class Primary + +:expect [21, 22] +[new storedClass(21).x, new classRecord.make(22).x] +//│ = [21, 22] + +let specializedClass = Generic[Payload].class +//│ specializedClass = class Generic + +:expect 33 +new specializedClass(Payload(33)).value.number +//│ = 33 + +let nestedClass = first.Inner.class +//│ nestedClass = class Inner + +:expect 41 +new nestedClass().result() +//│ = 41 + +// A partial construction captures the class object before its binding changes. +class Enclosing(val number) { + class Nested(val argument) { + fun result() = number + argument + } +} +let earlier = Enclosing(100) +let later = Enclosing(200) +let currentClass = earlier.Nested.class +let construct = new currentClass +set currentClass = later.Nested.class +//│ construct = fun construct +//│ currentClass = class Nested +//│ earlier = Enclosing(100) +//│ later = Enclosing(200) + +:noInline +:expect [101, 201] +[construct(1).result(), new currentClass(1).result()] +//│ = [101, 201] + +// Class values can flow through function parameters and getter results. +:expect 34 +private fun instantiate(cls, x) = new cls(x) +instantiate(Primary.class, 34).x +//│ = 34 + +fun storedGetter = Primary.class + +:expect 35 +new storedGetter(35).x +//│ = 35 + +// Inheritance uses the same class value and constructor parameter lists. +class Derived extends rawCurried(36)(37) + +:expect 37 +new Derived().y +//│ = 37 + +let bareClass = Bare +//│ bareClass = class Bare + +:expect true +new bareClass is Bare +//│ = true + +:e +let invalidClass = 0 +new invalidClass +//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation +//│ ║ l.398: new invalidClass +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Integer literal cannot be instantiated with keyword 'new'. +//│ ║ l.397: let invalidClass = 0 +//│ ╙── ^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ invalidClass = 0 + +:e +let ambiguousClass = Primary.class +set ambiguousClass = Curried.class +new ambiguousClass +//│ ╔══[COMPILATION ERROR] The class instantiated here is ambiguous +//│ ║ l.411: new ambiguousClass +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── class 'Primary' defined here +//│ ║ l.7: class Primary(val x) +//│ ║ ^^^^^^^ +//│ ╟── class 'Curried' defined here +//│ ║ l.12: class Curried(val x)(val y) +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ambiguousClass = class Curried + From ee685084ec5c677a1f9698ee0581d74a4649ce80 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 3 Oct 2026 13:32:11 +0800 Subject: [PATCH 203/228] Minor --- hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index f69008ef42..df269123e9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -168,9 +168,9 @@ abstract class Parser( @tailrec def loop(tokens: Ls[TokLoc], prefixes: Ls[Ls[TokLoc]]): Ls[TokLoc] = tokens match case (IDENT("new", false), l1) :: (IDENT("!", true), l2) :: rest => - loop(rest, List(IDENT("new!", false) -> (l1 ++ l2)) :: prefixes) + loop(rest, (IDENT("new!", false) -> (l1 ++ l2) :: Nil) :: prefixes) case (IDENT("yield", false), l1) :: (IDENT("*", true), l2) :: rest => - loop(rest, List(IDENT("yield*", false) -> (l1 ++ l2)) :: prefixes) + loop(rest, (IDENT("yield*", false) -> (l1 ++ l2) :: Nil) :: prefixes) // * Remove empty indented sections case (BRACKETS(Indent, toks), _) :: rest if toks.forall: From 747c8abf4852dfa1a27a611a08eab6c70939ce48 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 3 Oct 2026 13:47:29 +0800 Subject: [PATCH 204/228] Recover runtime classes uniformly from constructor shapes --- .../main/scala/hkmc2/codegen/Lowering.scala | 28 +-- .../scala/hkmc2/semantics/NewResolver.scala | 78 +++++---- .../hkmc2/semantics/NewResolverState.scala | 2 +- .../main/scala/hkmc2/semantics/Shape.scala | 39 +++-- .../mlscript/basics/DynamicInstantiation.mls | 7 +- .../test/mlscript/basics/ObjectExtensions.mls | 18 +- .../src/test/mlscript/codegen/ImportMLsJS.mls | 3 - .../src/test/mlscript/newres/ClassValues.mls | 160 +++++++++++------- .../mlscript/newres/ClonedConstructors.mls | 15 ++ .../shared/src/test/mlscript/newres/Opens.mls | 15 +- .../src/test/mlscript/newres/Projections.mls | 21 +++ .../newres/TypeInterfaceDiagnostics.mls | 9 +- 12 files changed, 229 insertions(+), 166 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index c6a81abfb6..7ad1341d6d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -417,23 +417,28 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu def classOf(trm: Term, nw: PossiblyErroneous)(k: (Path, Ls[ParamList]) => Block)(using LoweringCtx): Block = def classValue: Block = nw match - case term: New if !term.isErroneous => - NewShape.classesOf(term) match - case cls :: Nil => + case term: Term if !nw.isErroneous => + ClassValueShape.targetsOf(term) match + case target :: Nil => + val cls = ClassValueShape.definitionOf(target) subTerm(trm): value => + val classPath = target match + case _: ClassCtorSymbol => value.selSN("class") + case _ => value // Read a stored class before evaluating arguments or building a // partial constructor, even if its binding is assigned later. val saved = loweringCtx.registerTempSymbol(S(trm), erasedType = N, "classValue") - Assign(saved, value, k(saved.asSimpleRef, classParamLists(cls.defn.get))) + Assign(saved, classPath, k(saved.asSimpleRef, classParamLists(cls))) case Nil => fail: - ErrorReport(msg"Cannot resolve the class instantiated here" -> trm.toLoc :: Nil, + ErrorReport(msg"Cannot resolve the class referenced here" -> trm.toLoc :: Nil, source = Diagnostic.Source.Compilation) - case classes => fail: - ErrorReport(msg"The class instantiated here is ambiguous" -> trm.toLoc :: - classes.map(cls => msg"class '${cls.nme}' defined here" -> cls.toLoc), + case targets => fail: + ErrorReport(msg"The class interpretation here is ambiguous" -> trm.toLoc :: + targets.map(target => msg"${target.describeKind} '${target.nme}' defined here" -> target.toLoc), source = Diagnostic.Source.Compilation) case _ => compError - if newResolution then + if newResolution && nw.isErroneous then compError + else if newResolution then trm.classHead match case resolved @ Resolved(_, _: ClassSymbol) => // Synthesized runtime constructors already carry an explicit class target. @@ -445,10 +450,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu cls.defn match case S(clsDef: ClassLikeDef) => subTerm(resl)(p => k(p, classParamLists(clsDef))) - case _ if nw.isInstanceOf[New] => classValue - case _ => - softAssert(resl.isErroneous, s"Unexpected `new` target: ${cls.showDbg}") - compError + case _ => classValue case Nil => if !resl.isErroneous then raise: ErrorReport( diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 95263d55aa..e2977b5b06 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2008,7 +2008,9 @@ class NewResolver: // An ambiguous lookup can publish several constructors. Pair each // declaration only with its own references; the lookup diagnoses // competing candidates independently of this shape subscription. - case _: ClassDef => listenClass(lhs, allowValueAliases = true)(ref => + // The pattern already selected its class target above; this subscription + // recovers capture marks without adding the operand's term target. + case _: ClassDef => listenClass(lhs, recordTargets = false)(ref => if ref.definition eq cls then publish(ref.marks) , _ => ()) case _: ModuleOrObjectDef => publish(Nil) @@ -2619,41 +2621,36 @@ class NewResolver: /** Class interpretations wait for completed overload sets, independently of term * companions. Aliases can supply constructor shapes; applied instances cannot. * Capture marks are retained for subsequent instance-member lookup. */ - private case class ClassReference(definition: ClassDef, marks: Ls[Marks], - instances: TypeSubstitution, supplied: Opt[Ls[DeclaredType]]) - - // `new` and projections can follow inferred class values. `.class` selects - // only declarations: evaluating a class reference as a term loses that syntax. - private def listenClass(trm: Term, allowValueAliases: Bool)(selected: ShapeListener[ClassReference], reject: ShapeListener[TermShape])(using NewResolverState): Unit = - def select(ref: ClassReference, target: Opt[DefinitionSymbol[?]])(using NewResolverState): Unit = - target.foreach(recordValueTarget(trm.classHead, _)) + private case class ClassReference(target: ClassValueTarget, marks: Ls[Marks], + instances: TypeSubstitution, supplied: Opt[Ls[DeclaredType]]): + def definition: ClassDef = ClassValueShape.definitionOf(target) + + // Declaration references choose their class overload. Stored values instead + // retain their runtime representation, whether a class or its constructor. + private def listenClass(trm: Term, recordTargets: Bool)(selected: ShapeListener[ClassReference], reject: ShapeListener[TermShape])(using NewResolverState): Unit = + def select(ref: ClassReference)(using NewResolverState): Unit = + if recordTargets then recordValueTarget(trm.classHead, ref.definition.sym) selected(ref) - def value(sh: TermShape, origin: Opt[SymShape])(using NewResolverState): Unit = + def value(sh: TermShape)(using NewResolverState): Unit = val ShapeParts(source, instances, supplied) = shapeParts(sh) val marks = source.applicationHead._2 - def fromDefinition(ds: DefnShape): Unit = - ds.clsDef match + source match + case Marked(ds: DefnShape, _) => ds.clsDef match case S(cls: ClassDef) => - // A stored class object must be read from its binding. Constructor - // aliases instead use the class interpretation of their reference. - val target = ds.defn match - case _: ClassDef => origin.flatMap(s => valueTarget(s.sym, false)) - case _ => S(cls.sym) - select(ClassReference(cls, marks, instances, supplied), target) + val target: ClassValueTarget = ds.defn match + case td: TermDefinition => td.tsym match + case ctor: ClassCtorSymbol => ctor + case _ => lastWords("Class shape without a class or constructor definition") + case _: ClassDef => cls.sym + case _ => lastWords("Class shape without a class or constructor definition") + selected(ClassReference(target, marks, instances, supplied)) case _ => reject(sh) - source match - case Marked(ds: DefnShape, _) if allowValueAliases => fromDefinition(ds) case _ => reject(sh) trm match // Local variables store the term interpretation of their initializer, even // when their flow still contains its unconverted overload-set shape. - case _: SimpleRef => listenTerm(trm): shape => - // A constructor function is not a runtime class object. In particular, - // `let ctor = C` does not grant `new ctor` a class interpretation. - shapeParts(shape).value match - case Marked(ds: DefnShape, _) if ds.defn.isInstanceOf[ClassDef] => value(shape, N) - case _ => reject(shape) - case application @ TyApp(base, args) => listenClass(base, allowValueAliases)(ref => + case _: SimpleRef => listenTerm(trm)(value) + case application @ TyApp(base, args) => listenClass(base, recordTargets)(ref => val count = if ref.supplied.nonEmpty then 0 else ref.definition.tparams.length if count != args.length && rstate.typeArgumentArityErrors.add((new Identity(application), count)) then resolError(trm, msg"Class '${ref.definition.sym.nme}' expected ${count} type ${ @@ -2666,19 +2663,18 @@ class NewResolver: selected(ref.copy(instances = instances, supplied = S(supplied))) , reject) case Capture(base, thru) => - listenClass(base, allowValueAliases)(ref => selected(ref.copy(marks = + listenClass(base, recordTargets)(ref => selected(ref.copy(marks = ref.marks ::: EntryMark(ResolutionBoundary(thru), N, NoMarks) :: Nil)), reject) case _ => listen(trm): case sh: SymShape => - completedClass(sh)(cls => select(ClassReference(cls, + completedClass(sh)(cls => select(ClassReference(cls.sym, ExitMark(ResolutionBoundary(cls.sym), S(sh.resSym), NoMarks) :: sh.markss, sh match case contextual: ContextualSymShape => contextual.instances case _ => TypeSubstitution.empty - , N), S(cls.sym)), - () => fromSymbol(sh, shape => value(shape, S(sh)), trm, - sym => if !allowValueAliases then recordValueTarget(trm, sym), !allowValueAliases)) - case sh: TermShape => value(sh, N) + , N)), + () => fromSymbol(sh, value, trm, sym => if recordTargets then recordValueTarget(trm, sym), false)) + case sh: TermShape => value(sh) def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") @@ -2721,11 +2717,13 @@ class NewResolver: // Resolve the class overload directly, without selecting its constructor // or companion. The resolved class symbol also identifies this selection // during lowering; no ordinary member named `class` is involved. - listenClass(sel.prefix, allowValueAliases = false)(ref => + listenClass(sel.prefix, recordTargets = true)(ref => val cd = ref.definition rstate.recordResolution(sel, sel.resolvedTargets.contains(cd.sym))(sel.resolvedTargets ::= cd.sym) listenExt(cd, ext => - val shape = defnShapes.getOrElseUpdate(cd.sym, DefnShape(cd, ext)) + val shape = ClassValueShape(ref.target, ext) + assert(rstate.canResolve(sel) || ClassValueShape.targetsOf(sel).contains(ref.target), + "Inference changed a completed class-value interpretation") val specialized = ref.supplied.fold[TermShape](shape)(args => SpecializedShape(shape, args, ref.instances)) transportShape(specialized, ref.marks) match case value: TermShape => publishViewed(sel, value, ref.instances) @@ -2736,7 +2734,7 @@ class NewResolver: log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty) case S(cls) => - listenClass(cls, allowValueAliases = true)(ref => + listenClass(cls, recordTargets = true)(ref => val cd = ref.definition val marks = ref.marks val candidate = cd.sym -> marks @@ -2804,11 +2802,11 @@ class NewResolver: // listenClass preserves captures and supplies the same class-body exit as a // constructor value. In particular, a captured constructor already carries // that exit; adding a second one here would duplicate its instance boundary. - listenClass(nw.cls, allowValueAliases = true)(ref => + listenClass(nw.cls, recordTargets = true)(ref => val cd = ref.definition // Constructor arity is compiled from these shapes even when the runtime // operand is a variable with no resolved class symbol of its own. - assert(rstate.canResolve(nw) || NewShape.classesOf(nw).contains(cd.sym), + assert(rstate.canResolve(nw) || ClassValueShape.targetsOf(nw).contains(ref.target), "Inference changed a completed constructor target") val marks = ref.marks val callerInstances = rstate.instances @@ -2819,8 +2817,8 @@ class NewResolver: // unapplied constructor waits for its first term application. val instances = if ref.supplied.nonEmpty || (nw.args.isEmpty && dsh.unappliedParams.nonEmpty) then captured else instantiateDefinition(dsh, nw.resSym, marks, captured, N) - val sh = newShapes.getOrElseUpdate((cd.sym, marks, nw.resSym, ref.supplied), - NewShape(dsh, cd.sym, marks, nw.args, nw, ref.supplied)) + val sh = newShapes.getOrElseUpdate((ref.target, marks, nw.resSym, ref.supplied), + NewShape(dsh, ref.target, marks, nw.args, nw, ref.supplied)) if rstate.constructorApplications.add((sh, instances)) then dsh.unappliedParams.lazyZip(nw.args).foreach: case ((ps, _), args) => zipArgsIn(marks, ps.params, ps.restParam, args, nw, dsh, instances) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 3a61186225..f3ff561ccc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -239,7 +239,7 @@ final class NewResolverState private ( new Cache(inherited.map(_.reportedArities), _.clone()) val appShapes: Cache[(CoreTermShape, FlowSymbol), AppShape] = new Cache(inherited.map(_.appShapes), identity) - val newShapes: Cache[(ClassLikeSymbol, Ls[Marks], FlowSymbol, Opt[Ls[DeclaredType]]), NewShape] = + val newShapes: Cache[(ClassValueTarget, Ls[Marks], FlowSymbol, Opt[Ls[DeclaredType]]), NewShape] = new Cache(inherited.map(_.newShapes), identity) val constructorApplications: Seen[(NewShape, TypeSubstitution)] = new Seen(inherited.map(_.constructorApplications)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 845b62ec0f..213c59ae42 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -399,8 +399,9 @@ class AppShape(val receiver: CoreTermShape, val args: Term, val src: Term.App)(u override def toString: String = s"AppShape($receiver, ${args.showDbg})" // def target: Opt[AppTarget] -class NewShape(val receiver: DefnShape, val cls: ClassLikeSymbol, val clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New, +class NewShape(val receiver: DefnShape, val classTarget: ClassValueTarget, val clsMarks: Ls[Marks], val argss: Ls[Term], val src: Term.New, val supplied: Opt[Ls[DeclaredType]])(using DebugPrinter) extends CoreShape: + def cls: ClassSymbol = ClassValueShape.definitionOf(classTarget).sym protected def getMemberImpl(name: Str)(using NewResolverState): MemberLookup = if isSaturated then receiver.getInstanceMember(name).withMarks(clsMarks) else MemberLookup.Missing @@ -412,19 +413,33 @@ class NewShape(val receiver: DefnShape, val cls: ClassLikeSymbol, val clsMarks: override def toString: String = s"NewNewShape(${cls.showDbg}, $argss)" def toLoc: Opt[Loc] = src.toLoc -object NewShape: - /** Constructor parameter lists are fixed when the originating unit is compiled. - * Read its original shapes, ignoring substitutions and capture paths, both - * for lowering and to check that later inference preserves this class identity. +/** A class expression can denote a class directly or recover it from its + * generated constructor function. Keep the source definition in the inference + * shape so lowering does not need a separate mutable access-mode annotation. + */ +type ClassValueTarget = ClassSymbol | ClassCtorSymbol + +final case class ClassValueShape(target: ClassValueTarget, parent: Opt[TermShape]) + extends DefnShape(ClassValueShape.definitionOf(target), parent) + +object ClassValueShape: + def definitionOf(target: ClassValueTarget): ClassDef = target match + case cls: ClassSymbol => cls.defn.get + case ctor: ClassCtorSymbol => ctor.associatedCls.defn.get + + /** Read the originating unit's completed interpretation. Later inference must + * preserve both the class identity and whether its operand needs unwrapping. */ - def classesOf(term: Term.New): Ls[ClassSymbol] = - def classes(event: ShapeEvent): Ls[ClassSymbol] = event match - case ActivatedShapeEvent(value, _) => classes(value) - case MarkedShape(value, _) => classes(value) - case ContextualShape(value, _) => classes(value) - case shape: NewShape => shape.cls.asCls.toList + def targetsOf(term: Term): Ls[ClassValueTarget] = + def targets(event: ShapeEvent): Ls[ClassValueTarget] = event match + case ActivatedShapeEvent(value, _) => targets(value) + case MarkedShape(value, _) => targets(value) + case ContextualShape(value, _) => targets(value) + case SpecializedShape(value, _, _) => targets(value) + case shape: NewShape => shape.classTarget :: Nil + case shape: ClassValueShape => shape.target :: Nil case _ => Nil - term.getShapes.flatMap(classes).distinct + term.shapes.toList.flatMap(targets).distinct sealed abstract class SymShape(val sym: BlockMemberSymbol, val resSym: FlowSymbol, val markss: Ls[Marks]) extends Shape: def describe: Str = s"${sym.describe} symbol '${sym.nme}'" diff --git a/hkmc2/shared/src/test/mlscript/basics/DynamicInstantiation.mls b/hkmc2/shared/src/test/mlscript/basics/DynamicInstantiation.mls index f53e966056..fa9f579018 100644 --- a/hkmc2/shared/src/test/mlscript/basics/DynamicInstantiation.mls +++ b/hkmc2/shared/src/test/mlscript/basics/DynamicInstantiation.mls @@ -59,18 +59,15 @@ new id(C) //│ ║ in ../../mlscript-compile/Predef.mls //│ ║ l.19: fun id(x) = x //│ ╙── ^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.54: new id(C) -//│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e new (id(C)) //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.68: new (id(C)) +//│ ║ l.65: new (id(C)) //│ ║ ^^^^^^^^^^^ //│ ╟── Invalid class expression: application -//│ ║ l.68: new (id(C)) +//│ ║ l.65: new (id(C)) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls index c3250d17bf..b63a031826 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ObjectExtensions.mls @@ -12,9 +12,6 @@ new Foo //│ ╟── Object 'Foo' cannot be instantiated with keyword 'new'. //│ ║ l.5: object Foo //│ ╙── ^^^ -//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.8: new Foo -//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :re @@ -24,7 +21,7 @@ new! Foo :fixme // Support? new! Foo.class //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.25: new! Foo.class +//│ ║ l.22: new! Foo.class //│ ║ ^^^^^^^^^ //│ ╟── Object 'Foo' does not contain member 'class' //│ ║ l.5: object Foo @@ -37,34 +34,31 @@ new! Foo!class :fixme // Support? object Bar extends Foo //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.38: object Bar extends Foo +//│ ║ l.35: object Bar extends Foo //│ ║ ^^^ //│ ╟── Object 'Foo' cannot be instantiated with keyword 'new'. //│ ║ l.5: object Foo //│ ╙── ^^^ -//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.38: object Bar extends Foo -//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e :re object Bar extends Foo!class //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.52: object Bar extends Foo!class +//│ ║ l.46: object Bar extends Foo!class //│ ║ ^^^^^^^^^ //│ ╟── Invalid class expression: dynamic selection -//│ ║ l.52: object Bar extends Foo!class +//│ ║ l.46: object Bar extends Foo!class //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :fixme // Support? object Bar extends! Foo!class //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.62: object Bar extends! Foo!class +//│ ║ l.56: object Bar extends! Foo!class //│ ║ ^^^^^^^^^^^ //│ ╟── Invalid class expression: reference -//│ ║ l.62: object Bar extends! Foo!class +//│ ║ l.56: object Bar extends! Foo!class //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls index 5d2ce98a77..edc4b035b3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls @@ -11,9 +11,6 @@ Option.isDefined(new Option.Some(1)) //│ ║ l.9: Option.isDefined(new Option.Some(1)) //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╙── Dynamic value cannot be instantiated with keyword 'new'. -//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.9: Option.isDefined(new Option.Some(1)) -//│ ╙── ^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. Option.isDefined(new! Option.Some(1)) diff --git a/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls index 83fa53c1ea..243cd96d63 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls @@ -78,7 +78,7 @@ new Auxiliary.class(13).value() new Curried.class(14)(15).y //│ = 15 -// Evaluated terms do not preserve the class-selection syntax. +// Constructor functions and class values support the same class selection through bindings. let raw = Primary.class let ctor = Primary val saved = Primary @@ -90,69 +90,37 @@ val saved = Primary raw == Primary.class //│ = true -:e +:expect class Primary raw.class -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.94: raw.class -//│ ║ ^^^^^^^^^ -//│ ╟── Class value does not contain member 'class' -//│ ║ l.7: class Primary(val x) -//│ ╙── ^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = class Primary :expect fun new raw //│ = fun -:e +:expect fun new ctor -//│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.108: new ctor -//│ ║ ^^^^^^^^ -//│ ╟── Class 'Primary' cannot be instantiated with keyword 'new'. -//│ ║ l.7: class Primary(val x) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = fun -:e +:expect class Primary +:sr ctor.class -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.118: ctor.class -//│ ║ ^^^^^^^^^^ -//│ ╟── Class 'Primary' does not contain member 'class' -//│ ║ l.7: class Primary(val x) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— +//│ ctor⁰.class‹Primary⁰› +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = class Primary -:e +:expect class Primary saved.class -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.128: saved.class -//│ ║ ^^^^^^^^^^^ -//│ ╟── Class 'Primary' does not contain member 'class' -//│ ║ l.7: class Primary(val x) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = class Primary -:e +:expect class Primary Primary.class.class -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.138: Primary.class.class -//│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ╟── Class value does not contain member 'class' -//│ ║ l.7: class Primary(val x) -//│ ╙── ^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = class Primary -:e +:expect class Primary ({make: Primary}).make.class -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.148: ({make: Primary}).make.class -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── Class 'Primary' does not contain member 'class' -//│ ║ l.7: class Primary(val x) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = class Primary // Qualified, opened, and captured classes must retain their runtime receiver. module Namespace { @@ -199,17 +167,17 @@ Explosive.class :e Empty().class //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.200: Empty().class +//│ ║ l.168: Empty().class //│ ║ ^^^^^^^^^^^^^ //│ ╟── Instance of class 'Empty' does not contain member 'class' -//│ ║ l.200: Empty().class +//│ ║ l.168: Empty().class //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e set Bare.class = 1 //│ ╔══[COMPILATION ERROR] Assignment requires a term member -//│ ║ l.210: set Bare.class = 1 +//│ ║ l.178: set Bare.class = 1 //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -282,17 +250,11 @@ let record = {"class": 5} record.class //│ = 5 -// Passing a constructor as an argument does not preserve class-selection syntax. -:e +// Class selection also recovers constructor functions passed as arguments. +:expect class Empty private fun classOfValue(value) = value.class classOfValue(Empty) -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.287: private fun classOfValue(value) = value.class -//│ ║ ^^^^^^^^^^^ -//│ ╟── Class 'Empty' does not contain member 'class' -//│ ║ l.6: class Empty() -//│ ╙── ^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = class Empty // Foreign class declarations denote their native binding directly. declare class String with @@ -397,10 +359,10 @@ new bareClass is Bare let invalidClass = 0 new invalidClass //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.398: new invalidClass +//│ ║ l.360: new invalidClass //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Integer literal cannot be instantiated with keyword 'new'. -//│ ║ l.397: let invalidClass = 0 +//│ ║ l.359: let invalidClass = 0 //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ invalidClass = 0 @@ -409,8 +371,8 @@ new invalidClass let ambiguousClass = Primary.class set ambiguousClass = Curried.class new ambiguousClass -//│ ╔══[COMPILATION ERROR] The class instantiated here is ambiguous -//│ ║ l.411: new ambiguousClass +//│ ╔══[COMPILATION ERROR] The class interpretation here is ambiguous +//│ ║ l.373: new ambiguousClass //│ ║ ^^^^^^^^^^^^^^ //│ ╟── class 'Primary' defined here //│ ║ l.7: class Primary(val x) @@ -421,3 +383,71 @@ new ambiguousClass //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ambiguousClass = class Curried +// Storing a constructor function in a variable or a record preserves the same value. +:expect true +let constructors = {make: Primary} +ctor == constructors.make +//│ = true +//│ constructors = {make: fun Primary { class: class Primary }} + +:expect [35, 35] +[new ctor(35).x, new constructors.make(35).x] +//│ = [35, 35] + +:expect true +ctor.class == constructors.make.class +//│ = true + +let curriedCtor = Curried +//│ curriedCtor = fun Curried { class: class Curried } + +:expect 44 +new curriedCtor(43)(44).y +//│ = 44 + +let partialCtor = new curriedCtor(45) +//│ partialCtor = fun partialCtor + +:expect 46 +partialCtor(46).y +//│ = 46 + +let capturedCtor = earlier.Nested +let capturedRecord = {make: earlier.Nested} +//│ capturedCtor = fun Nested { class: class Nested } +//│ capturedRecord = {make: fun Nested { class: class Nested }} + +:noInline +:expect [101, 101, true] +[new capturedCtor(1).result(), new capturedRecord.make(1).result(), + capturedCtor.class == capturedRecord.make.class] +//│ = [101, 101, true] + +// Partial construction reads the constructor's class before a later assignment. +let currentCtor = earlier.Nested +let ctorFactory = new currentCtor +set currentCtor = later.Nested +//│ ctorFactory = fun ctorFactory +//│ currentCtor = fun Nested { class: class Nested } + +:noInline +:expect [101, 201] +[ctorFactory(1).result(), new currentCtor(1).result()] +//│ = [101, 201] + +:e +let mixedRepresentation = Primary +set mixedRepresentation = Primary.class +new mixedRepresentation +//│ ╔══[COMPILATION ERROR] The class interpretation here is ambiguous +//│ ║ l.441: new mixedRepresentation +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── function 'Primary' defined here +//│ ║ l.7: class Primary(val x) +//│ ║ ^^^^^^^ +//│ ╟── class 'Primary' defined here +//│ ║ l.7: class Primary(val x) +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ mixedRepresentation = class Primary + diff --git a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls index 352b425406..b8b0c45e73 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClonedConstructors.mls @@ -67,3 +67,18 @@ object Amazing extends constructors.make(35) Amazing.value //│ = 35 +// Selecting the class from a let-bound constructor also survives pattern cloning. +let ctor = Direct +fun throughVariableClass(x) = if x is @compile ctor.class(3) then true else false +//│ ctor = fun Direct { class: class Direct } + +:expect [true, false] +[throughVariableClass(Direct(3)), throughVariableClass(Direct(4))] +//│ = [true, false] + +// The pattern instantiator mistakes a local constructor value for a pattern +// parameter and looks it up in the pattern-argument substitution. +:fixme +fun throughVariable(x) = if x is @compile ctor(3) then true else false +//│ /!!!\ Uncaught error: java.util.NoSuchElementException: key not found: ctor + diff --git a/hkmc2/shared/src/test/mlscript/newres/Opens.mls b/hkmc2/shared/src/test/mlscript/newres/Opens.mls index 666990c39c..ea34eedd50 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Opens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Opens.mls @@ -282,9 +282,6 @@ new Shadowed //│ ╟── Integer literal cannot be instantiated with keyword 'new'. //│ ║ l.276: val Shadowed = 20 //│ ╙── ^^ -//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.278: new Shadowed -//│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ Shadowed = 20 @@ -313,19 +310,19 @@ fun ambiguousReversed() = { } ambiguousReversed() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous -//│ ║ l.312: extra +//│ ║ l.309: extra //│ ║ ^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.38: module Selected { val extra = 6 } //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.310: open Selected +//│ ║ l.307: open Selected //│ ║ ^^^^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.8: val extra = 3 //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.311: open Values +//│ ║ l.308: open Values //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -382,18 +379,18 @@ fun inheritedAmbiguity() = { } inheritedAmbiguity() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous -//│ ║ l.380: fun nested() = extra +//│ ║ l.377: fun nested() = extra //│ ║ ^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.8: val extra = 3 //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.378: open Values +//│ ║ l.375: open Values //│ ║ ^^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.38: module Selected { val extra = 6 } //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.379: open Selected +//│ ║ l.376: open Selected //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Projections.mls b/hkmc2/shared/src/test/mlscript/newres/Projections.mls index eae70322b5..f0fea9802c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Projections.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Projections.mls @@ -201,3 +201,24 @@ val AliasToNumber = SourceNumber //│ ╙── ^^^ //│ AliasToNumber = 123 //│ SourceNumber = 123 + +// A single stored field can still contain distinct captured class values. +:e +class CapturedLibrary(val number) { + class Entry { fun get() = number } +} +let leftLibrary = CapturedLibrary(1) +let rightLibrary = CapturedLibrary(2) +fun chooseEntry(flag) = if flag then leftLibrary.Entry.class else rightLibrary.Entry.class +val classFields = {entry: chooseEntry(true)} +open classFields +fun storedProjection(value) = value.entry#get() +//│ ╔══[COMPILATION ERROR] The projection class is ambiguous +//│ ║ l.215: fun storedProjection(value) = value.entry#get() +//│ ║ ^^^^^^ +//│ ╟── class: 'Entry' +//│ ║ l.208: class Entry { fun get() = number } +//│ ╙── ^^^^^ +//│ classFields = {entry: class Entry} +//│ leftLibrary = CapturedLibrary(1) +//│ rightLibrary = CapturedLibrary(2) diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls index 0b93af7cd3..504e7deda7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls @@ -275,9 +275,6 @@ new instance //│ ╟── Value with a declared type cannot be instantiated with keyword 'new'. //│ ║ l.270: val instance: Base = new Base //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.271: new instance -//│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ instance = Base @@ -287,11 +284,11 @@ declare type Hidden type Alias = Hidden fun inspect(value: Alias) = value.member //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.288: fun inspect(value: Alias) = value.member +//│ ║ l.285: fun inspect(value: Alias) = value.member //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'member' of a value with unknown shape. -//│ ║ l.288: fun inspect(value: Alias) = value.member +//│ ║ l.285: fun inspect(value: Alias) = value.member //│ ║ ^^^^^ //│ ╟── Type 'Hidden' is declared without a member interface. -//│ ║ l.286: declare type Hidden +//│ ║ l.283: declare type Hidden //│ ╙── ^^^^^^ From 475016b1cbc46b1298f17927bc774b985c96a143 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 14:01:28 +0800 Subject: [PATCH 205/228] Simplify class companions and distinguish Wasm constructors --- doc/new-resolution-design.md | 10 + .../scala/hkmc2/codegen/BlockSimplifier.scala | 4 + .../scala/hkmc2/codegen/js/JSBuilder.scala | 112 +++-------- .../scala/hkmc2/codegen/wasm/text/Ctx.scala | 15 +- .../scala/hkmc2/codegen/wasm/text/Wasm.scala | 8 +- .../hkmc2/codegen/wasm/text/WatBuilder.scala | 44 +++-- .../scala/hkmc2/semantics/Elaborator.scala | 29 ++- .../OverloadedClassLibrary.mls | 6 +- .../mlscript-compile/OverloadedDefault.mls | 2 +- .../OverloadedModulesInSignatures.mls | 4 +- .../src/test/mlscript/backlog/ToTriage.mls | 17 +- .../test/mlscript/basics/BadOverloading.mls | 51 ++--- .../src/test/mlscript/newres/Assignments.mls | 6 +- .../mlscript/newres/ConstructorResolution.mls | 4 +- .../test/mlscript/newres/DeclaredTypes.mls | 4 +- .../mlscript/newres/InterfaceExposure.mls | 23 ++- .../test/mlscript/newres/Interpretations.mls | 15 +- .../newres/InvalidClassCompanions.mls | 177 ++++++++++++++++++ .../shared/src/test/mlscript/newres/Opens.mls | 43 ++--- .../newres/OverloadedClassImports.mls | 14 +- .../mlscript/newres/OverloadedClassValues.mls | 63 ++++--- .../mlscript/newres/OverloadedClasses.mls | 12 +- .../src/test/mlscript/newres/Projections.mls | 35 ++-- .../mlscript/newres/ReceiverResolution.mls | 4 +- .../test/mlscript/newres/TypeResolution.mls | 7 +- .../newres/wasm/OverloadedClasses.mls | 52 +++++ 26 files changed, 495 insertions(+), 266 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/InvalidClassCompanions.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/wasm/OverloadedClasses.mls diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index 089c4c5775..d325ef97ee 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -211,6 +211,16 @@ companion modules. Preserve this distinction through nested captures and opens. `newres/OverloadedCalls.mls` covers direct, generic, stored-function, module-member, and captured uses. +Bare classes can share their name with a function definition that has a parameter +list. The generated function owns the class reference, so JS uses the existing +`.class` property for references, selections, and imports. Arbitrary values, +parameterless functions, and term declarations cannot supply that companion: +their results may be primitive, aliased, or frozen, and must never be augmented. +Instance methods cannot supply per-instance companions either, because their +function objects are shared. Inlining may replace calls to a companion function, +but must preserve its binding while the class is used. WASM identifies constructor +functions by their class symbols, separately from explicit function companions. + Foreign declarations expose call and constructor capabilities explicitly. The JS backend uses native class values for `new` and patterns without generated MLscript `.class` storage. Bodyless foreign methods use foreign-call normalization diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index fab852731a..eee15c7cd9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -1538,6 +1538,10 @@ class BlockSimplifier def canBeInlineEliminated: Bool = isPrivate && !isMethod && !defn.noInline && useCount <= 1 && !disallowElimination && !isLoopBreaker && !isPatternHelper && !hasDuplicateBindings + // Class/module references still need the function object that owns their `.class`. + // Term-only use counts cannot establish that this shared binding is dead; + // ordinary DCE can remove it once neither interpretation is referenced. + && defn.sym.asClsOrMod.isEmpty // false def inlineCost(newBlk: Block, threshold: Int): Opt[Int] = 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 d3255f343f..a1151971f2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -49,32 +49,6 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val freeze = if !config.noFreeze then "globalThis.Object.freeze" else "" lazy val freezeDefns = if freezeDefinitions && !config.noFreeze then "globalThis.Object.freeze" else "" private val privateAccessorSymbols = LinkedHashMap.empty[semantics.TermSymbol, semantics.TempSymbol] - // Filled only by reserveNames for the current program. Refreshed IR members keep - // their original syntax trees, so their live class symbols must come from IR definitions. - private val independentClassSymbols = LinkedHashMap.empty[BlockMemberSymbol, ClassSymbol] - - /** Bare classes overloaded with terms have independent storage: their term may be - * uninitialized, primitive, or frozen. A symbol key keeps member class slots separate - * from source fields, and stable across separately compiled modules and inheritance. */ - private def independentClass(sym: DefinitionSymbol[?]): Opt[ClassSymbol] = - if sym.asTrm.isDefined then N - else (sym.asCls orElse sym.asBlkMember.flatMap(_.asCls)).filter: - cls => cls.asBlkMember.exists(_.asTrm.isDefined) && - cls.defn.exists(d => d.paramsOpt.isEmpty && d.hasDeclareModifier.isEmpty) - - private def classField(cls: ClassSymbol): Document = - doc"[globalThis.Symbol.for(${makeStringLiteral("mlscript.class." + cls.nme)})]" - - private def independentClassBindings(ss: Iterable[ScopedSymbol])(using Raise, Scope): Iterator[(Symbol, Str)] = - ss.toList.sortBy(_.uid).iterator.flatMap: - case bms: BlockMemberSymbol => - independentClassSymbols.get(bms) - case _ => N - .map(cls => cls -> scope.lookup_!(cls, cls.toLoc)) - - private def classExportName(sym: BlockMemberSymbol)(using Raise): Document = - // A string export name cannot collide with the identifier exports allocated for terms. - makeStringLiteral("mlscript.class:" + modulePrivateExportName(sym)) // TODO use this to avoid parens when we generate recomposed expressions later enum Context: @@ -191,9 +165,8 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: private case class Imports( defaults: Ls[ImportSymbol -> Str], privates: Ls[BlockMemberSymbol -> Str], - classes: Ls[BlockMemberSymbol -> Str], ): - def symbols: Ls[Symbol] = defaults.map(_._1) ::: privates.map(_._1) ::: classes.map(_._1.asCls.get) + def symbols: Ls[ImportSymbol] = defaults.map(_._1) ::: privates.map(_._1) /** Recovers imports that are not necessarily recorded in `p.imports`. @@ -228,7 +201,6 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val defaultImports = collection.mutable.Map.empty[ImportSymbol, Str] val privateImports = collection.mutable.Map.empty[BlockMemberSymbol, Str] - val classImports = collection.mutable.Map.empty[BlockMemberSymbol, Str] /** State identity, rather than equal path strings, is the compilation-unit ownership * invariant: all symbols belonging to the current artifact were created by this builder's @@ -250,13 +222,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: override def applyValue(value: Value): Unit = value match case Value.SimpleRef(sym: TempSymbol) => note(sym) case Value.SimpleRef(sym: VarSymbol) => note(sym) - case Value.MemberRef(sym, disamb) => - note(sym) - if independentClass(disamb).isDefined && !localSymbols(sym) then - sym.getState.externalModuleImport(sym, State) match - case S(ExternalModuleImport.Default(_, path)) => classImports(sym) = path - case S(ExternalModuleImport.Private(_, path)) => classImports(sym) = path - case N => () + case Value.MemberRef(sym, _) => note(sym) case _ => ).applyBlock(p.main) @@ -265,7 +231,6 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: Imports( orderedExternalImports(defaultImports), orderedExternalImports(privateImports), - orderedExternalImports(classImports), ) end externalImports @@ -280,11 +245,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: scope.lookup(sym).isEmpty privates.foreach: (sym, _) => scope.allocateName(sym) - val classes = external.classes.filter: (sym, _) => - scope.lookup(sym.asCls.get).isEmpty - classes.foreach: (sym, _) => - scope.allocateName(sym.asCls.get) - Imports(defaults ::: externalDefaults, privates, classes) + Imports(defaults ::: externalDefaults, privates) private def ownCompilationUnitSymbols(p: Program): Ls[BlockMemberSymbol] = p.main match @@ -353,11 +314,8 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case Value.Lit(Tree.StrLit(value)) => makeStringLiteral(value) case Value.Lit(lit) => lit.idStr case Value.MemberRef(bms, disamb) => - independentClass(disamb) match - case S(cls) => scope.lookup_!(cls, r.toLoc) - case N => - if disamb.shouldBeLifted then doc"${scope.lookup_!(bms, bms.toLoc)}.class" - else scope.lookup_!(bms, r.toLoc) + if disamb.shouldBeLifted then doc"${scope.lookup_!(bms, bms.toLoc)}.class" + else scope.lookup_!(bms, r.toLoc) case Value.SimpleRef(l: BuiltinSymbol) => if l.nullary then l.nme else errExpr(msg"Illegal reference to builtin symbol '${l.nme}'") @@ -403,11 +361,10 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case s @ Select(qual, id) => val checkCurrentSelection = checkSelections && s.sanitize val dotClass = s.symbol match - case S(ds) if independentClass(ds).isEmpty && ds.shouldBeLifted => doc".class" + case S(ds) if ds.shouldBeLifted => doc".class" case _ => doc"" val field = s.symbol match case S(ts: semantics.TermSymbol) => selectPrivateField(ts, s.toLoc) - case S(ds) => independentClass(ds).map(classField) case _ => N val name = symbolicSuffixBase(id.name).getOrElse(id.name) val fieldDoc = field.getOrElse: @@ -597,12 +554,6 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: // * in that case, we can't really use it as an inner name, as this would result in unintended capture. case S(otherSym: FreeSymbol) if (otherSym isnt sym) && bod.freeVars.contains(otherSym) => doc"${varName} = $functionKeyword ($params) ${ braced(bodyDoc) };" - case S(cls: ClassSymbol) if independentClass(cls).isDefined && bod.freeVars.exists { - case bms: BlockMemberSymbol => independentClassSymbols.get(bms).orElse(bms.asCls).contains(cls) - case _ => false - } => - // The function's display name must not capture its sibling class binding. - doc"${varName} = $functionKeyword ($params) ${ braced(bodyDoc) };" case _ => doc"${varName} = $functionKeyword ${sym.nme}($params) ${ braced(bodyDoc) };" else @@ -622,14 +573,12 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val backendParamList = auxParams.head val ctorParams = backendParamList.paramSyms.map(p => p -> scope.allocateName(p)) val sourceParamsOpt = isym.defn.flatMap(_.paramsOpt) - val independentCls = independentClass(isym) - // * Whether the class should be "lifted" to a "class" property of the companion term - // * should currently be consistent with whether the class has source parameters. - // * This currently fails for faulty input programs (such as `object O(x)`); - // * we should make sure such programs fail compilation before they reach this point. - softTODO(sourceParamsOpt.isDefined === (isym.shouldBeLifted && independentCls.isEmpty), - s"Unexpected class storage for ${isym.nme}: parameters=$sourceParamsOpt, independent=$independentCls") + // Parameterized classes have a generated constructor function; bare classes can + // instead have an explicit function companion. Both own their `.class` property. + // Invalid parameterized objects can still reach this point without a constructor. + softTODO(sourceParamsOpt.isEmpty || isym.shouldBeLifted, + s"Missing constructor function for parameterized class ${isym.nme}") def mkMethodName(td: FunDefn, owner: InnerSymbol): Document = if td.dSym.isPrivate @@ -749,9 +698,6 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: then doc" # new $v" else ownr match - case S(owner) if independentCls.isDefined => - doc" # ${result(owner.asThis)}${classField(independentCls.get)} = $v" - case N if independentCls.isDefined => doc"" case S(owner) => doc" # ${result(owner.asThis)}.${sym.nme}$extraPath = $v" case N => @@ -845,8 +791,6 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: fun match case S(f) => doc"${scope.lookup_!(sym, sym.toLoc)} = ${f}; # $freezeDefns($clsJS);" - case N if independentCls.isDefined => - doc"${outerScope.lookup_!(isym, isym.toLoc)} = $freezeDefns($clsJS);" case N => doc"$freezeDefns(${clsJS});" @@ -1002,14 +946,12 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: * but the result has the same semantics. * */ def reserveNames(p: Program)(using Scope, Raise): Unit = - independentClassSymbols.clear() def go(blk: Block): Unit = tl.trace(s"avoidNames ${blk.toString.take(100)}..."): blk match case Define(defn, rest) => defn match case d: ClsLikeDefn => val nme = scope.allocateName(d.isym) - independentClass(d.isym).foreach(cls => independentClassSymbols(d.sym) = cls) d.companion match case N => () case S(comp) => @@ -1049,21 +991,15 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val privateImps = imports.privates.map: (sym, path) => val relPath = relativeImportPath(path, wd) doc"""import { ${modulePrivateExportName(sym)} as ${scope.lookup_!(sym, N)} } from "${relPath}";""" - val classImps = imports.classes.map: (sym, path) => - val relPath = relativeImportPath(path, wd) - doc"""import { ${classExportName(sym)} as ${scope.lookup_!(sym.asCls.get, N)} } from "${relPath}";""" val bodyDoc = nonNestedScoped(p.main)(block(_, endSemi = false)).stripBreaks - val privateExports = (if exportPrivates then ownCompilationUnitSymbols(p) else Nil).flatMap: sym => - doc"""export { ${scope.lookup_!(sym, sym.toLoc)} as ${modulePrivateExportName(sym)} };""" :: - independentClassSymbols.get(sym).toList.map: cls => - doc"""export { ${scope.lookup_!(cls, cls.toLoc)} as ${classExportName(sym)} };""" - withPrivateAccessorDecls((imps ::: privateImps ::: classImps).mkDocument(doc" # ")) + val privateExports = (if exportPrivates then ownCompilationUnitSymbols(p) else Nil).map: sym => + doc"""export { ${scope.lookup_!(sym, sym.toLoc)} as ${modulePrivateExportName(sym)} };""" + withPrivateAccessorDecls((imps ::: privateImps).mkDocument(doc" # ")) :/: bodyDoc :: (if privateExports.isEmpty then doc"" else doc" # " :: privateExports.mkDocument(doc" # ")) :: locally: exprt match case S(sym) => - // Export the allocated binding directly; the source name may belong to its class facet. doc"\nexport default ${scope.lookup_!(sym, sym.toLoc)};\n" case N => doc"" @@ -1074,15 +1010,13 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: val imports = bindImports(p) val importedScopedSymbols: Set[ScopedSymbol] = p.imports.iterator.map(_._1).toSet - val importBindings: Ls[Symbol -> Str] = + val importBindings: Ls[ImportSymbol -> Str] = imports.symbols.map(sym => sym -> scope.lookup_!(sym, N)) val imps = imports.defaults.map: (sym, path) => doc"""${scope.lookup_!(sym, N)} = await import("${path}").then(m => m.default ?? m);""" ::: imports.privates.map: (sym, path) => doc"""${scope.lookup_!(sym, N)} = await import("${path}").then(m => m.${modulePrivateExportName(sym)});""" - ::: imports.classes.map: (sym, path) => - doc"""${scope.lookup_!(sym.asCls.get, N)} = await import("${path}").then(m => m[${classExportName(sym)}]);""" p.main match case Scoped(syms, body) => val fvs = body.freeVars @@ -1090,7 +1024,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: !s.isInstanceOf[TempSymbol] // ^ VarSymbols and TermSymbols should be kept as their value will be acessed and printed by the worksheet || fvs(s))) - genLetDecls(importBindings.iterator ++ allocateScopedBindings(localSymbols) ++ independentClassBindings(localSymbols)) -> + genLetDecls(importBindings.iterator ++ allocateScopedBindings(localSymbols)) -> (withPrivateAccessorDecls(imps.mkDocument(doc" # ")) :/: block(body, endSemi = false).stripBreaks) case body => genLetDecls(importBindings.iterator) -> @@ -1112,7 +1046,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: l -> scope.allocateName(l) def blockPreamble(ss: Iterable[ScopedSymbol])(using Raise, Scope): Document = - genLetDecls(allocateScopedBindings(ss) ++ independentClassBindings(ss)) + genLetDecls(allocateScopedBindings(ss)) /** Specially handle top-level Scoped node: output the bindings, but do not add another pair of braces */ def nonBracedScoped(blk: Block)(k: Scope ?=> Block => Document)(using Raise, Scope): Document = blk match @@ -1241,15 +1175,15 @@ object JSBuilder: }.mkString extension (dsym: DefinitionSymbol[?]) - /** Whether a class-like definition has a term companion. Parameterized classes - * store the class value in the generated constructor function's `.class` property; - * explicitly overloaded bare classes use independent storage instead. + /** In JS, when a class is overloaded with a term (either explicitly, or because it has a primary parameter list), + * then its class value is stored in a `.class` property of the term. * - * After checking for independent storage, reference sites use this to append `.class` + * This helper is used at reference sites (MemberRef, Select) to decide whether to append `.class` * when accessing a class value. It returns true only for class/module/object symbols, * not for term symbols — so constructor calls like `Foo(args)` which resolve to the term - * symbol are not affected. Foreign declarations do not generate companion storage: - * their class value is the native binding even when constructor parameters are declared. */ + * symbol are not affected. Elaboration rejects arbitrary values and parameterless + * functions as companions: the class property belongs to a compiler-created function, + * never to an existing value. Foreign declarations use their native binding instead. */ def shouldBeLifted: Bool = val bsym = dsym.asBlkMember ( diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala index a5a0d79b97..5d385c43f3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala @@ -9,7 +9,7 @@ import hkmc2.utils.* import document.* import document.Document import semantics.{ - BlockMemberSymbol, Elaborator, + BlockMemberSymbol, ClassSymbol, Elaborator, InnerSymbol, LabelSymbol, LocalVarSymbol, ModuleOrObjectSymbol, ParamList, TempSymbol, }, Elaborator.State @@ -47,14 +47,19 @@ object SessionBinding: * The Wasm function type expected by the import. */ final case class SessionFunc( - sym: BlockMemberSymbol, + sym: BlockMemberSymbol | ClassSymbol, wrapId: Opt[Str] -> Opt[Str], moduleName: Str, exportName: Str, funcType: FunctionType, ) extends SessionBinding: def bindingKey: Str = s"func:$moduleName:$exportName" - def bindingSyms: Seq[SlotSymbol] = sym :: Nil + // MIR free variables use the block member even when the reference selects its class. + // Importing that member must recover both its function and its constructor, under distinct keys. + // Prefer the IR member: inlining refreshes symbols but retains their source definitions. + def bindingSyms: Seq[SlotSymbol] = sym match + case cls: ClassSymbol => cls :: cls.irClsLikeDefn.map(_.sym).orElse(cls.asBlkMember).toList + case sym: BlockMemberSymbol => sym :: Nil override def exportNameOpt: Opt[Str] = S(exportName) /** Metadata for an exported global that later Wasm REPL modules can import. @@ -971,9 +976,7 @@ class Ctx(using Elaborator.Ctx, State) extends ToWat: def addFunc(funcInfo: FuncInfo)(using Ctx, Raise): FuncIdx = val id = funcInfo.id funcs = funcs + (id -> funcInfo) - funcInfo.sym match - case bms: BlockMemberSymbol => namedFuncs(bms) = funcInfo - case _ => + namedFuncs(funcInfo.sym) = funcInfo val idx = FuncIdx(funcInfo.id) val refType = RefType(funcInfo.typeUse.typeIdx, nullable = false) elemSegments = elemSegments + diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Wasm.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Wasm.scala index d04b1d317a..644b420297 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Wasm.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Wasm.scala @@ -6,13 +6,13 @@ package text import hkmc2.utils.*, shorthands.* import document.* -import semantics.{BlockMemberSymbol, DefinitionSymbol, Elaborator, InnerSymbol, LocalVarSymbol, TempSymbol}, Elaborator.State +import semantics.{BlockMemberSymbol, ClassSymbol, DefinitionSymbol, Elaborator, InnerSymbol, LocalVarSymbol, TempSymbol}, Elaborator.State import utils.Scope import scala.collection.Map /** Symbols that can represent an importable or exportable Wasm construct. */ -private[text] type ExternSymbol = BlockMemberSymbol | TempSymbol +private[text] type ExternSymbol = BlockMemberSymbol | ClassSymbol | TempSymbol /** Symbols that can be lowered into a global or local slot in a Wasm function (i.e. parameters and locals). */ private[text] type SlotSymbol = InnerSymbol | ScopedSymbol @@ -28,7 +28,7 @@ extension (doc: Document) extension (scp: Scope) /** Convenience function for [[Scope.allocateOrGetName]] with an optional prefix and suffix. */ - private[text] def allocateOrGetNameWrapped(sym: ScopedSymbol, wrapId: Opt[Str] -> Opt[Str])(using Raise): Str = + private[text] def allocateOrGetNameWrapped(sym: ScopedSymbol | ClassSymbol, wrapId: Opt[Str] -> Opt[Str])(using Raise): Str = val prefix = wrapId._1.fold("")(prefix => s"${prefix}_") wrapId._2 match case S(suffix) => @@ -305,7 +305,7 @@ object ExternType: def toWat: Document = doc"""(global ${id.toWat} ${globalType.toWat})""" end ExternType -sealed abstract class ExternType(val sym: ScopedSymbol) extends ToWat: +sealed abstract class ExternType(val sym: ScopedSymbol | ClassSymbol) extends ToWat: /** Symbolic identifier for the extern declaration. */ val id: SymIdx diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala index 4ce5d1aa69..271c599fb5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala @@ -879,13 +879,26 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui private def initFuncSym(sym: BlockMemberSymbol): BlockMemberSymbol = initFuncSyms.getOrElseUpdate(sym, BlockMemberSymbol("init", Nil, nameIsMeaningful = false)) + /** Constructors belong to the class definition, not its overloaded term binding. + * Using the block member for both would overwrite an explicit function companion. + * The class symbol already supplies a stable identity across definitions and imports. + */ + private def constructorSymbol(defn: ClsLikeDefn): BlockMemberSymbol | ClassSymbol = defn.isym match + case cls: ClassSymbol => cls + case _ => defn.sym + + private def functionSymbol(bms: BlockMemberSymbol, disamb: DefinitionSymbol[?]): BlockMemberSymbol | ClassSymbol = + disamb match + case ctor: ClassCtorSymbol => ctor.associatedCls + case _ => bms + /** Registers a placeholder class-associated function so later lowering can overwrite it. */ private def predeclareClassFunc( defn: ClsLikeDefn, suffix: Str, params: Seq[WasmSlotSymbol -> SymIdx], results: Seq[Result], - sym: BlockMemberSymbol, + sym: BlockMemberSymbol | ClassSymbol, exportName: Opt[Str], thisType: Opt[ValType], )(using Raise): Unit = @@ -898,7 +911,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui suffix: Str, params: Seq[WasmSlotSymbol -> SymIdx], resultTypes: Seq[Result], - sym: BlockMemberSymbol, + sym: BlockMemberSymbol | ClassSymbol, exportName: Opt[Str], funcTy: TypeIdx, )(using Raise): Unit = @@ -953,13 +966,17 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui val ctorExportName = defn.sym .optionIf: sym => !(defn.k is syntax.Obj) && sym.nameIsMeaningful - .map(_.nme) + .map: sym => + // An explicit function companion exports the term under its source name. + // Give the constructor its own export so later worksheets can import both. + if defn.ctorSym.isEmpty && sym.hasTrmDef then s"mlscript.class:${sym.nme}" + else sym.nme predeclareClassFunc( defn, "ctor", ctorParams, Seq(Result(RefType(typeIdx, nullable = false))), - defn.sym, + constructorSymbol(defn), ctorExportName, thisType = N, ) @@ -1721,8 +1738,8 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui errExpr: Ls(msg"Plain class references are not supported in Wasm; instantiate the class instead." -> r.toLoc) else - ctx.getFunc(bms) match - case S(funcIdx) => ref.func(funcIdx, RefType(ctx.getFuncTypeUse_!(bms).typeIdx, nullable = false)) + ctx.getFunc(functionSymbol(bms, disamb)) match + case S(funcIdx) => ref.func(funcIdx, RefType(ctx.getFuncTypeUse_!(funcIdx).typeIdx, nullable = false)) case N => getVar(bms, r.toLoc) case Value.This(sym) => singletonInfoFor(sym) match @@ -1815,8 +1832,8 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui ) case N => fun match - case Value.MemberRef(l, _) => - val base = ctx.getFunc(l) + case Value.MemberRef(l, disamb) => + val base = ctx.getFunc(functionSymbol(l, disamb)) val baseFuncIdx = base match case S(idx) => idx case N => return errExpr( @@ -1987,7 +2004,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui s"Expected resolved class for an Instantiate(...) expression to be a BlockMemberSymbol, but got ${ ctorClsSym.getClass.getName }" - val ctorFuncIdx = ctx.getFunc(ctorClsBlkSym).getOrElse: + val ctorFuncIdx = ctx.getFunc(ctorClsSym.asCls.getOrElse(ctorClsBlkSym)).getOrElse: lastWords(s"Missing constructor definition for class ${ctorClsBlkSym.toString}") val ctorTypeIdx = ctx.getTypeInfo(ctx.getFuncTypeUse_!(ctorFuncIdx).typeIdx).getOrElse: lastWords(s"Missing type definition for class constructor ${ctorClsBlkSym.toString}") @@ -2338,9 +2355,10 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui exportName = predeclaredInit.exportName, )) - val predeclaredCtor = ctx.getFuncInfo_!(clsLikeDefn.sym) + val ctorSym = constructorSymbol(clsLikeDefn) + val predeclaredCtor = ctx.getFuncInfo_!(ctorSym) val ctorFuncInfo = FuncInfo( - sym = clsLikeDefn.sym, + sym = ctorSym, wrapId = S(clsLikeDefn.sym.nme) -> N, typeUse = predeclaredCtor.typeUse, params = ctorFnCtx.resolvedParams, @@ -2403,10 +2421,10 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui )) if !isSingletonObj && clsLikeDefn.sym.nameIsMeaningful then summon[SessionExportCtx].emit(SessionFunc( - sym = clsLikeDefn.sym, + sym = ctorSym, wrapId = ctorFuncInfo.wrapId, moduleName = SessionBinding.ReplModuleName, - exportName = clsLikeDefn.sym.nme, + exportName = ctorFuncInfo.exportName.get, funcType = FunctionType(ctorFuncInfo.getSignatureType), )) end if diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index ae5af58dc2..98e05fe4a2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1964,13 +1964,26 @@ extends Importer: log(s"Processing overloadings for '$name'") defns.iterator.foreach: defn => if defn.k > k then - val bareClassOverload = newResolution && ((k is Fun) || k.isInstanceOf[Val]) && (defn match + // Only function definitions with parameters create a fresh companion object. + // A val initializer or parameterless function can yield an existing value; + // attaching a class to that value would mutate it (and may fail if frozen). + val functionCompanion = (k is Fun) && mainDefn.paramLists.nonEmpty + val instanceMethodCompanion = functionCompanion && ctx.outer.inner.exists(_.isInstanceOf[ClassSymbol]) + val bareClassOverload = newResolution && functionCompanion && !instanceMethodCompanion && (defn match case td: TypeDef => (td.k is Cls) && td.paramLists.isEmpty case _ => false) - val functionModuleOverload = newResolution && (k is Fun) && (defn.k is Mod) + val functionModuleOverload = newResolution && functionCompanion && !instanceMethodCompanion && (defn.k is Mod) if !supportedOverloadings(k -> defn.k) && !bareClassOverload && !functionModuleOverload then raise: ErrorReport: - if notYetSupportedOverloadings(k -> defn.k) + if ((defn.k is Cls) || (defn.k is Mod)) && instanceMethodCompanion then + msg"Not yet supported: ${defn.k.desc} companion '$name' defined as an instance method" -> mainDefn.toLoc + :: msg"An instance method is shared by all instances, but its companion is created per instance" -> defn.toLoc + :: Nil + else if ((defn.k is Cls) || (defn.k is Mod)) && ((k is Fun) || k.isInstanceOf[Val]) && !functionCompanion then + msg"A ${defn.k.desc} companion must be a function with a parameter list" -> mainDefn.toLoc + :: msg"Arbitrary values cannot be overloaded with ${defn.k.desc} '$name'" -> defn.toLoc + :: Nil + else if notYetSupportedOverloadings(k -> defn.k) then msg"Not yet supported: overloading of ${k.desc} '$name'" -> mainDefn.toLoc :: msg"with ${defn.k.desc} of the same name" -> defn.toLoc :: Nil @@ -2651,6 +2664,16 @@ extends Importer: bms.symbols.foreach: case sym: (ClassLikeSymbol & InnerSymbol) => sym.defn.foreach: d => // erroneous code may not have a defn even after the BMS is completed + // A declaration supplies no fresh function object to hold a companion. + // Generated constructor functions also have no source body, but lowering creates them. + // Foreign class-like declarations produce no companion initialization at all. + if d.hasDeclareModifier.isEmpty then + bms.asTrm.filterNot(_.isInstanceOf[ClassCtorSymbol]).flatMap(_.defn).foreach: term => + if term.body.isEmpty || term.hasDeclareModifier.isDefined then raise: + ErrorReport: + msg"A term declaration cannot have an implemented ${sym.describeKind} companion" -> term.toLoc + :: msg"Companion '${sym.nme}' is defined here" -> d.toLoc + :: Nil d.ext match case S(ext) => listenTerm(ext): esh => diff --git a/hkmc2/shared/src/test/mlscript-compile/OverloadedClassLibrary.mls b/hkmc2/shared/src/test/mlscript-compile/OverloadedClassLibrary.mls index 6169be964c..db628334df 100644 --- a/hkmc2/shared/src/test/mlscript-compile/OverloadedClassLibrary.mls +++ b/hkmc2/shared/src/test/mlscript-compile/OverloadedClassLibrary.mls @@ -3,13 +3,13 @@ class Hidden { fun answer() = 51 } -val Hidden = 15 +fun Hidden() = 15 module OverloadedClassLibrary { fun fresh() = new Hidden - fun value() = Hidden + fun value() = Hidden() class Item { fun answer() = 52 } - val Item = 16 + fun Item() = 16 } diff --git a/hkmc2/shared/src/test/mlscript-compile/OverloadedDefault.mls b/hkmc2/shared/src/test/mlscript-compile/OverloadedDefault.mls index cffeeebc13..9d807e9351 100644 --- a/hkmc2/shared/src/test/mlscript-compile/OverloadedDefault.mls +++ b/hkmc2/shared/src/test/mlscript-compile/OverloadedDefault.mls @@ -3,4 +3,4 @@ class OverloadedDefault { fun answer() = 54 } -val OverloadedDefault = 19 +fun OverloadedDefault() = 19 diff --git a/hkmc2/shared/src/test/mlscript/OverloadedModulesInSignatures.mls b/hkmc2/shared/src/test/mlscript/OverloadedModulesInSignatures.mls index 0984173da8..27588b1b05 100644 --- a/hkmc2/shared/src/test/mlscript/OverloadedModulesInSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/OverloadedModulesInSignatures.mls @@ -14,10 +14,10 @@ module TypMod with fun TrmMod = 42 module TrmMod with val whoami = "module" -//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'TrmMod' +//│ ╔══[COMPILATION ERROR] A module companion must be a function with a parameter list //│ ║ l.14: fun TrmMod = 42 //│ ║ ^^^^^^^^^^^^^^^ -//│ ╟── with module of the same name +//│ ╟── Arbitrary values cannot be overloaded with module 'TrmMod' //│ ║ l.15: module TrmMod with //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ║ l.16: val whoami = "module" diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index f29dd4cf14..a1f1e341d7 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -82,7 +82,7 @@ fun main() = :ssjs object Cls(val x) with fun huh = x -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: Unexpected class storage for Cls: parameters=Some(ParamList(‹›,List(Param(‹val›,x,None,Modulefulness(None))),None)), independent=None +//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: Missing constructor function for parameterized class Cls //│ ╟── The compilation result may be incorrect. //│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— @@ -368,6 +368,13 @@ data class Foo(mut x) :e fun Foo class Foo +//│ ╔══[COMPILATION ERROR] A term declaration cannot have an implemented class companion +//│ ║ l.369: fun Foo +//│ ║ ^^^^^^^ +//│ ╟── Companion 'Foo' is defined here +//│ ║ l.370: class Foo +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] TypeError: Cannot set properties of undefined (setting 'class') // ——— ——— ——— @@ -375,16 +382,16 @@ class Foo :e class Id(name: UnboundName) //│ ╔══[COMPILATION ERROR] Name not found: UnboundName -//│ ║ l.376: class Id(name: UnboundName) +//│ ║ l.383: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.376: class Id(name: UnboundName) +//│ ║ l.383: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got ‹error› -//│ ║ l.376: class Id(name: UnboundName) +//│ ║ l.383: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.376: class Id(name: UnboundName) +//│ ║ l.383: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ // ——— ——— ——— diff --git a/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls b/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls index 6d2adaca7f..ffb9bb8907 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls @@ -43,17 +43,17 @@ object Foo //│ ╟── with object of the same name //│ ║ l.39: object Foo //│ ╙── ^^^^^^^^^^ -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: Unexpected class storage for Foo: parameters=None, independent=None -//│ ╟── The compilation result may be incorrect. -//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. -:fixme -// * Note that this does not report an error at the moment: +:e +// A term declaration does not create a function that can own its companion. fun Foo: Int object Foo -//│ ╔══[INTERNAL ERROR] Compiler reached an unsupported state: Unexpected class storage for Foo: parameters=None, independent=None -//│ ╟── The compilation result may be incorrect. -//│ ╙── This is a known compiler limitation; if it is a blocker for you, please report it to the maintainers. +//│ ╔══[COMPILATION ERROR] A term declaration cannot have an implemented object companion +//│ ║ l.49: fun Foo: Int +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Companion 'Foo' is defined here +//│ ║ l.50: object Foo +//│ ╙── ^^^^^^^^^^ :todo :e @@ -66,30 +66,37 @@ class Foo //│ ╟── with object of the same name //│ ║ l.61: object Foo //│ ╙── ^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.60: fun Foo = 1 +//│ ║ ^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Foo' +//│ ║ l.62: class Foo +//│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal overloading of class 'Foo' //│ ║ l.62: class Foo //│ ║ ^^^^^^^^^ //│ ╟── with object of the same name //│ ║ l.61: object Foo //│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] TypeError: Cannot assign to read only property 'class' of object '[object Object]' -// Class and module companions can have a function with the same name. +// Class and module companions require a function definition with parameters. class Foo -fun Foo = 1 +fun Foo() = 1 -fun Foo = 1 +fun Foo() = 1 module Foo -// TODO: support +// An existing value cannot own a companion. :e val Foo = 1 module Foo -//│ ╔══[COMPILATION ERROR] Not yet supported: overloading of value 'Foo' -//│ ║ l.86: val Foo = 1 +//│ ╔══[COMPILATION ERROR] A module companion must be a function with a parameter list +//│ ║ l.93: val Foo = 1 //│ ║ ^^^^^^^^^^^ -//│ ╟── with module of the same name -//│ ║ l.87: module Foo +//│ ╟── Arbitrary values cannot be overloaded with module 'Foo' +//│ ║ l.94: module Foo //│ ╙── ^^^^^^^^^^ //│ Foo = class Foo @@ -98,11 +105,11 @@ module Foo fun (**) f1(a, b) = "[" + a + " * " + b + "]" fun (**) f2(a, b) = "(" + a + " + " + b + ")" //│ ╔══[COMPILATION ERROR] Symbolic name '**' of definition 'f1' is already used -//│ ║ l.98: fun (**) f1(a, b) = "[" + a + " * " + b + "]" -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.105: fun (**) f1(a, b) = "[" + a + " * " + b + "]" +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── by sibling member 'f2' -//│ ║ l.99: fun (**) f2(a, b) = "(" + a + " + " + b + ")" -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.106: fun (**) f2(a, b) = "(" + a + " + " + b + ")" +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1 ** 2 //│ = "(1 + 2)" @@ -112,10 +119,10 @@ fun (**) t() = 0 fun (++) t() = 1 //│ ╔══[COMPILATION ERROR] Multiple definitions of symbol 't' //│ ╟── defined here -//│ ║ l.111: fun (**) t() = 0 +//│ ║ l.118: fun (**) t() = 0 //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── defined here -//│ ║ l.112: fun (++) t() = 1 +//│ ║ l.119: fun (++) t() = 1 //│ ╙── ^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/Assignments.mls b/hkmc2/shared/src/test/mlscript/newres/Assignments.mls index 9dd1117ef7..a2087e4dc4 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Assignments.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Assignments.mls @@ -6,7 +6,7 @@ fun writeLater(value) = { set Later.Item = value } module Later { - class Item { + class ItemClass { fun answer() = 42 } mut val Item = 0 @@ -18,7 +18,7 @@ Later.Item //│ = 7 :expect 42 -(new Later.Item).answer() +(new Later.ItemClass).answer() //│ = 42 @@ -43,7 +43,7 @@ Later.Item // Resolving the assignment must preserve captures through the nested module. fun replace(initial) = { module Local { - class Item + class ItemClass mut val Item = initial } set Local.Item = 9 diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls index e89a3bb95a..a3510dbf90 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorResolution.mls @@ -8,13 +8,13 @@ fun forward(x) = { } module Constructors { class Token - val Token = 41 + fun Token() = 41 class Box(val value) object Singleton } :expect [1, 0, 41] -[forward(new Constructors.Token), forward(0), Constructors.Token] +[forward(new Constructors.Token), forward(0), Constructors.Token()] //│ = [1, 0, 41] diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 2fb3a7f9c5..849327edd7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -408,13 +408,13 @@ class Box with //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' -// TODO: infer the selected term alternative of an overloaded member's missing type. +// TODO: infer an unannotated field selected through a module parameter. :fixme :expect 42 class Secret with val extra = 42 module Types with - class Item + class ItemClass val Item = new Secret fun hidden(module types: Types) = types.Item.extra hidden(Types) diff --git a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls index 2c488b93f9..1ed615498b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls @@ -311,7 +311,7 @@ recursiveHidden({a: 1}) class Shadowed { fun bad(x) = x.a } -val Shadowed = 0 +fun Shadowed() = 0 //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.312: fun bad(x) = x.a //│ ║ ^^^ @@ -329,9 +329,8 @@ val Shadowed = 0 //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ║ l.313: } //│ ║ ^ -//│ ║ l.314: val Shadowed = 0 -//│ ╙── ^^^^^^^^^^^^^^^^ -//│ Shadowed = 0 +//│ ║ l.314: fun Shadowed() = 0 +//│ ╙── ^^^^^^^^^^^^^^^^^^ // A constructor test narrows the class, but cannot close its unannotated fields. :e @@ -339,17 +338,17 @@ class Holder(val value) fun inspect(x) = if x is Holder(y) then y.a else 0 inspect(Holder({a: 1})) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.339: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.338: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This constructor field may contain a value of unknown shape. -//│ ║ l.338: class Holder(val value) +//│ ║ l.337: class Holder(val value) //│ ║ ^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.339: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.338: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ║ ^ //│ ╟── 'inspect' is exposed by this compilation unit. -//│ ║ l.339: fun inspect(x) = if x is Holder(y) then y.a else 0 +//│ ║ l.338: fun inspect(x) = if x is Holder(y) then y.a else 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -367,17 +366,17 @@ class GenericHolder[A](val value: A) fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 inspectGeneric(GenericHolder({a: 1})) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.367: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.366: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ║ ^^^ //│ ╟── Cannot resolve member 'a' of a value with unknown shape. //│ ╟── This constructor field may contain a value of unknown shape. -//│ ║ l.366: class GenericHolder[A](val value: A) +//│ ║ l.365: class GenericHolder[A](val value: A) //│ ║ ^^^^^ //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.367: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.366: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ║ ^ //│ ╟── 'inspectGeneric' is exposed by this compilation unit. -//│ ║ l.367: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 +//│ ║ l.366: fun inspectGeneric(x) = if x is GenericHolder(y) then y.a else 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Interpretations.mls b/hkmc2/shared/src/test/mlscript/newres/Interpretations.mls index c1c0c44b26..0b4eb8e8d0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Interpretations.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Interpretations.mls @@ -16,11 +16,11 @@ forwardValue() // Parentheses and locally opened blocks preserve a class target's interpretation. module Choices { class Item { fun answer() = 24 } - val Item = 25 + fun Item() = 25 } fun choices() = { open Choices - [Item, (new (Item)).answer(), (new (Choices.{Item})).answer()] + [Item(), (new (Item)).answer(), (new (Choices.{Item})).answer()] } :expect [25, 24, 24] @@ -32,11 +32,11 @@ choices() module Arguments { class Box(val value) class Item - val Item = 26 + fun Item() = 26 } fun construct() = { open Arguments - (new Box(Item)).value + (new Box(Item())).value } :expect 26 @@ -47,7 +47,7 @@ construct() // Pattern targets retain the class interpretation; guards and results are terms. fun matchBox(box) = { open Arguments - if box is Box(value) and value > 0 then Item else 0 + if box is Box(value) and value > 0 then Item() else 0 } :expect [26, 0] @@ -91,9 +91,8 @@ invalidValue() // The prefix of a class selection is a term, even if that name also has a class. class Carrier -val Carrier = Arguments -//│ Carrier = class Arguments { Box: fun Box { class: class Box }, Item: 26 } +fun Carrier() = Arguments :expect 26 -(new Carrier.Box(Carrier.Item)).value +(new (Carrier().Box)(Carrier().Item())).value //│ = 26 diff --git a/hkmc2/shared/src/test/mlscript/newres/InvalidClassCompanions.mls b/hkmc2/shared/src/test/mlscript/newres/InvalidClassCompanions.mls new file mode 100644 index 0000000000..af917e8ed1 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/InvalidClassCompanions.mls @@ -0,0 +1,177 @@ +:. +:!js + + +// Companions are definitions of fresh functions, never values to be augmented. +:e +class Primitive +val Primitive = 7 +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.8: val Primitive = 7 +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Primitive' +//│ ║ l.7: class Primitive +//│ ╙── ^^^^^^^^^^^^^^^ + +:e +val Reverse = 13 +class Reverse +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.17: val Reverse = 13 +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Reverse' +//│ ║ l.18: class Reverse +//│ ╙── ^^^^^^^^^^^^^ + +:e +class Getter +fun Getter = 14 +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.28: fun Getter = 14 +//│ ║ ^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Getter' +//│ ║ l.27: class Getter +//│ ╙── ^^^^^^^^^^^^ + +:e +module MutableMembers { + class Mutable + mut val Mutable = 17 +} +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.39: mut val Mutable = 17 +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Mutable' +//│ ║ l.38: class Mutable +//│ ╙── ^^^^^^^^^^^^^ + + +// Object-valued initializers are rejected too: they may be frozen or aliased. +:e +class Instance +val Instance = new Instance +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.52: val Instance = new Instance +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Instance' +//│ ║ l.51: class Instance +//│ ╙── ^^^^^^^^^^^^^^ + +val existing = { answer: 42 } + +:e +class Aliased +val Aliased = existing +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.64: val Aliased = existing +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Aliased' +//│ ║ l.63: class Aliased +//│ ╙── ^^^^^^^^^^^^^ + +:e +class FreshRecord +val FreshRecord = { answer: 42 } +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.74: val FreshRecord = { answer: 42 } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'FreshRecord' +//│ ║ l.73: class FreshRecord +//│ ╙── ^^^^^^^^^^^^^^^^^ + +:e +class FunctionValue +val FunctionValue = x => x +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.84: val FunctionValue = x => x +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'FunctionValue' +//│ ║ l.83: class FunctionValue +//│ ╙── ^^^^^^^^^^^^^^^^^^^ + + +// The same rule applies inside modules and local scopes. +:e +module Members { + class Item + val Item = existing +} +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.97: val Item = existing +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Item' +//│ ║ l.96: class Item +//│ ╙── ^^^^^^^^^^ + +:e +fun local() = { + class Item + val Item = 0 + Item +} +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.109: val Item = 0 +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Item' +//│ ║ l.108: class Item +//│ ╙── ^^^^^^^^^^ + + +// A prototype method cannot hold a class that differs between instances. +:e +class Container(val value: Int) { + class Item { fun answer() = value } + fun Item() = new Item +} +//│ ╔══[COMPILATION ERROR] Not yet supported: class companion 'Item' defined as an instance method +//│ ║ l.124: fun Item() = new Item +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── An instance method is shared by all instances, but its companion is created per instance +//│ ║ l.123: class Item { fun answer() = value } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +// Parameterless functions cannot own a module companion either. +:e +fun GetterModule = existing +module GetterModule { val answer = 42 } +//│ ╔══[COMPILATION ERROR] A module companion must be a function with a parameter list +//│ ║ l.136: fun GetterModule = existing +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with module 'GetterModule' +//│ ║ l.137: module GetterModule { val answer = 42 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +// Declarations do not allocate companion functions either. +:e +class Declared +fun Declared(): Int +//│ ╔══[COMPILATION ERROR] A term declaration cannot have an implemented class companion +//│ ║ l.149: fun Declared(): Int +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Companion 'Declared' is defined here +//│ ║ l.148: class Declared +//│ ╙── ^^^^^^^^^^^^^^ + +:e +class ForeignFunction +declare fun ForeignFunction(): Int +//│ ╔══[COMPILATION ERROR] A term declaration cannot have an implemented class companion +//│ ║ l.159: declare fun ForeignFunction(): Int +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Companion 'ForeignFunction' is defined here +//│ ║ l.158: class ForeignFunction +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ + +:e +class ModuleContainer(val value: Int) { + module Item { val answer = value } + fun Item() = value +} +//│ ╔══[COMPILATION ERROR] Not yet supported: module companion 'Item' defined as an instance method +//│ ║ l.170: fun Item() = value +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── An instance method is shared by all instances, but its companion is created per instance +//│ ║ l.169: module Item { val answer = value } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/Opens.mls b/hkmc2/shared/src/test/mlscript/newres/Opens.mls index 666990c39c..26936f1202 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Opens.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Opens.mls @@ -140,12 +140,13 @@ open Later { absent } // Wildcard lookup retains overload sets until the use chooses a definition. module Overloaded { class Item { fun answer() = 9 } - mut val Item = 10 + fun Item() = 11 + mut val value = 11 } fun overload() = { open Overloaded - set Item = 11 - [Item, (new Item).answer()] + // The call and constructor choose different definitions of Item. + [Item(), (new Item).answer()] } :expect [11, 9] @@ -187,11 +188,11 @@ chained() import "../../mlscript-compile/OverloadedClassLibrary.mls" as Library fun imported() = { open Library - [Item, (new Item).answer()] + [Item(), (new Item).answer()] } fun importedNamed() = { open Library { Item } - [Item, (new Item).answer()] + [Item(), (new Item).answer()] } :expect [[16, 52], [16, 52]] @@ -228,19 +229,19 @@ fun twoInstances() = { } twoInstances() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'value' is ambiguous -//│ ║ l.227: value +//│ ║ l.228: value //│ ║ ^^^^^ //│ ╟── candidate: value 'value' defined here -//│ ║ l.217: module M { val value = n } +//│ ║ l.218: module M { val value = n } //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.225: open first +//│ ║ l.226: open first //│ ║ ^^^^^ //│ ╟── candidate: value 'value' defined here -//│ ║ l.217: module M { val value = n } +//│ ║ l.218: module M { val value = n } //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.226: open second +//│ ║ l.227: open second //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ first = class M { value: 16 } @@ -277,13 +278,13 @@ val Shadowed = 20 open ClassSource new Shadowed //│ ╔══[COMPILATION ERROR] Resolution error in object instantiation -//│ ║ l.278: new Shadowed +//│ ║ l.279: new Shadowed //│ ║ ^^^^^^^^^^^^ //│ ╟── Integer literal cannot be instantiated with keyword 'new'. -//│ ║ l.276: val Shadowed = 20 +//│ ║ l.277: val Shadowed = 20 //│ ╙── ^^ //│ ╔══[COMPILATION ERROR] Cannot resolve the class instantiated here -//│ ║ l.278: new Shadowed +//│ ║ l.279: new Shadowed //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ Shadowed = 20 @@ -313,19 +314,19 @@ fun ambiguousReversed() = { } ambiguousReversed() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous -//│ ║ l.312: extra +//│ ║ l.313: extra //│ ║ ^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.38: module Selected { val extra = 6 } //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.310: open Selected +//│ ║ l.311: open Selected //│ ║ ^^^^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.8: val extra = 3 //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.311: open Values +//│ ║ l.312: open Values //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -333,8 +334,8 @@ ambiguousReversed() // Both the temporary assignment and restoration use the selected open member. fun temporary() = { open Overloaded - let during = set Item = 21 in Item - [during, Item] + let during = set value = 21 in value + [during, value] } :expect [21, 11] @@ -382,18 +383,18 @@ fun inheritedAmbiguity() = { } inheritedAmbiguity() //│ ╔══[COMPILATION ERROR] Wildcard-open reference 'extra' is ambiguous -//│ ║ l.380: fun nested() = extra +//│ ║ l.381: fun nested() = extra //│ ║ ^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.8: val extra = 3 //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.378: open Values +//│ ║ l.379: open Values //│ ║ ^^^^^^ //│ ╟── candidate: value 'extra' defined here //│ ║ l.38: module Selected { val extra = 6 } //│ ║ ^^^^^^^^^^^^^ //│ ╟── Opened here -//│ ║ l.379: open Selected +//│ ║ l.380: open Selected //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls index 5e022e7a58..754288762d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassImports.mls @@ -3,15 +3,14 @@ import "../../mlscript-compile/OverloadedClassLibrary.mls" -// Inlining must import the private class binding independently from its term. +// Inlining imports one companion binding for both calls and class references. :sjs :expect 51 OverloadedClassLibrary.fresh().answer() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ import { _$_modulePrivate_$_Hidden as Hidden } from "./../../mlscript-compile/OverloadedClassLibrary.mjs"; -//│ import { "mlscript.class:_$_modulePrivate_$_Hidden" as Hidden1 } from "./../../mlscript-compile/OverloadedClassLibrary.mjs"; //│ let inlinedVal; -//│ inlinedVal = globalThis.Object.freeze(new Hidden1()); +//│ inlinedVal = globalThis.Object.freeze(new Hidden.class()); //│ return inlinedVal.answer() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 51 @@ -26,7 +25,7 @@ OverloadedClassLibrary.value() //│ = 15 :expect 16 -OverloadedClassLibrary.Item +OverloadedClassLibrary.Item() //│ = 16 :expect 52 @@ -40,16 +39,15 @@ open OverloadedClassLibrary { Item } //│ = 52 :expect 16 -Item +Item() //│ = 16 -// The default export's term and class also have independent import bindings. +// The default export also carries its class on its function companion. import "../../mlscript-compile/OverloadedDefault.mls" -//│ OverloadedDefault = 19 :expect 19 -OverloadedDefault +OverloadedDefault() //│ = 19 :expect 54 diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls index 1b06d55072..0ac9b3cda1 100644 --- a/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClassValues.mls @@ -1,15 +1,14 @@ :. -// The term need not be an object that can carry a `.class` property. +// A function definition creates a fresh companion that can own its class. class Primitive { fun answer() = 42 } -val Primitive = 7 -//│ Primitive = 7 +fun Primitive() = 7 :expect 7 -Primitive +Primitive() //│ = 7 :expect 42 @@ -17,12 +16,11 @@ Primitive //│ = 42 -// Declaration order and nullary functions must not change class selection. -val Reverse = 13 +// Declaration order must not change class selection. +fun Reverse() = 13 class Reverse { fun answer() = 43 } -//│ Reverse = 13 :expect 43 (new Reverse()).answer() @@ -31,10 +29,10 @@ class Reverse { class Getter { fun answer() = 44 } -fun Getter = 14 +fun Getter() = 14 :expect 14 -Getter +Getter() //│ = 14 :expect 44 @@ -60,7 +58,7 @@ module Members { class Item { fun answer() = 45 } - val Item = new Item + fun Item() = new Item fun fresh() = new Item class Factory { fun answer() = 46 @@ -69,7 +67,7 @@ module Members { } :expect 45 -Members.Item.answer() +Members.Item().answer() //│ = 45 :expect 45 @@ -109,9 +107,8 @@ Aux(41).answer() class Parent { fun answer() = 47 } -val Parent = false +fun Parent() = false class Child extends Parent -//│ Parent = false // Inherited members retain their class interpretation despite the term overload. :expect 47 @@ -130,19 +127,19 @@ Factory().answer() //│ = 49 -// Bare classes still coexist with independent values inside class instances. +// A class instance can expose a nested class alongside an ordinary field. class Container { class Inner { fun answer() = 50 } - val Inner = 5 + val value = 5 fun fresh() = new Inner } let container = new Container -//│ container = Container { Inner: 5 } +//│ container = Container { Inner: class Inner, value: 5 } :expect 5 -container.Inner +container.value //│ = 5 :expect 50 @@ -158,12 +155,12 @@ module MutableMembers { class Item { fun answer() = 53 } - mut val Item = 17 + mut val value = 17 } -set MutableMembers.Item = 18 +set MutableMembers.value = 18 :expect 18 -MutableMembers.Item +MutableMembers.value //│ = 18 :expect 53 @@ -176,20 +173,20 @@ MutableMembers.Item class Parameterized() fun Parameterized() = 0 //│ ╔══[COMPILATION ERROR] Not yet supported: overloading of function 'Parameterized' -//│ ║ l.177: fun Parameterized() = 0 +//│ ║ l.174: fun Parameterized() = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── with class of the same name -//│ ║ l.176: class Parameterized() +//│ ║ l.173: class Parameterized() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ :e class ParameterizedValue(x) val ParameterizedValue = 0 -//│ ╔══[COMPILATION ERROR] Illegal overloading of value 'ParameterizedValue' -//│ ║ l.187: val ParameterizedValue = 0 +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.184: val ParameterizedValue = 0 //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── with class of the same name -//│ ║ l.186: class ParameterizedValue(x) +//│ ╟── Arbitrary values cannot be overloaded with class 'ParameterizedValue' +//│ ║ l.183: class ParameterizedValue(x) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ParameterizedValue = 0 @@ -198,11 +195,17 @@ val ParameterizedValue = 0 class Duplicate fun Duplicate() = 0 val Duplicate = 1 +//│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list +//│ ║ l.197: val Duplicate = 1 +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Arbitrary values cannot be overloaded with class 'Duplicate' +//│ ║ l.195: class Duplicate +//│ ╙── ^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal overloading of value 'Duplicate' -//│ ║ l.200: val Duplicate = 1 +//│ ║ l.197: val Duplicate = 1 //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── with function of the same name -//│ ║ l.199: fun Duplicate() = 0 +//│ ║ l.196: fun Duplicate() = 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^ @@ -215,8 +218,8 @@ val Duplicate = 1 fun unusedClass() = { class Unused - val Unused = 55 - Unused + fun Unused() = 55 + Unused() } :inlineThreshold 10000 diff --git a/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls b/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls index 1125b383f0..839d9cc1bc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/OverloadedClasses.mls @@ -4,8 +4,7 @@ class Bar { fun id(x) = x } -val Bar = new Bar -//│ Bar = Bar +fun Bar() = new Bar :sr :sir @@ -86,30 +85,27 @@ module Foo { class Bar { fun id(x) = x } - val Bar = new Bar + fun Bar() = new Bar }//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— //│ module Foo⁰::Foo¹ { //│ class Bar²::Bar³ { //│ fun id⁰::id¹(x²) ‹flow:id⁰› = x² //│ }, -//│ val Bar²::Bar⁴ ‹flow:Bar⁰› = new Foo¹.this.Bar²^Bar⁴ +//│ fun Bar²::Bar⁴() ‹flow:Bar⁰› = new Foo¹.this.Bar²^Bar⁴ //│ } //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define Foo⁰ as class Foo¹ //│ module Foo² { -//│ val Bar⁴; //│ constructor { -//│ let tmp; //│ define Bar⁴ as class Bar⁵ { //│ method id⁰ = fun id¹(x) { //│ return x //│ } //│ }; -//│ set tmp = new Foo².this.Bar⁵(); -//│ define Bar⁴ as val Bar⁶ = tmp; //│ end //│ } +//│ method Bar⁴ = fun Bar⁶() { return new Foo².this.Bar⁵() } //│ }; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/newres/Projections.mls b/hkmc2/shared/src/test/mlscript/newres/Projections.mls index eae70322b5..2148003475 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Projections.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Projections.mls @@ -14,10 +14,9 @@ read(a) + project(a) // The class interpretation is independent of its term overload. :expect 7 class Overloaded { fun get() = 7 } -val Overloaded = 99 +fun Overloaded() = 99 (new Overloaded).Overloaded#get() //│ = 7 -//│ Overloaded = 99 // Class aliases retain constructor shapes. :expect 8 @@ -49,7 +48,7 @@ cell.Cell#value :e fun missing(a) = a.Later#missing() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.50: fun missing(a) = a.Later#missing() +//│ ║ l.49: fun missing(a) = a.Later#missing() //│ ║ ^^^^^^^^^^^^^^^ //│ ╟── Class 'Later' does not contain member 'missing' //│ ║ l.7: class Later(val x) { fun get() = x } @@ -59,10 +58,10 @@ fun missing(a) = a.Later#missing() val NotClass = 1 fun invalid(a) = a.NotClass#get() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.60: fun invalid(a) = a.NotClass#get() +//│ ║ l.59: fun invalid(a) = a.NotClass#get() //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Integer literal cannot be used as a projection class. -//│ ║ l.59: val NotClass = 1 +//│ ║ l.58: val NotClass = 1 //│ ╙── ^ //│ NotClass = 1 @@ -74,13 +73,13 @@ open Left open Right fun ambiguous(a) = a.Item#get() //│ ╔══[COMPILATION ERROR] The projection class is ambiguous -//│ ║ l.75: fun ambiguous(a) = a.Item#get() +//│ ║ l.74: fun ambiguous(a) = a.Item#get() //│ ║ ^^^^^ //│ ╟── class: 'Item' -//│ ║ l.72: module Right { class Item extends Parent {} } +//│ ║ l.71: module Right { class Item extends Parent {} } //│ ║ ^^^^ //│ ╟── class: 'Item' -//│ ║ l.71: module Left { class Item extends Parent {} } +//│ ║ l.70: module Left { class Item extends Parent {} } //│ ╙── ^^^^ // Forwarded arguments carry shapes through the projected method's result. @@ -93,12 +92,12 @@ let identity = new Identity val apply = Identity::apply apply(identity, new Answer).get() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.94: apply(identity, new Answer).get() +//│ ║ l.93: apply(identity, new Answer).get() //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'get' of a value with unknown shape. //│ ╟── Rest parameter 'args' admits elements of unknown shape. //│ ╟── 'apply' is exposed by this compilation unit. -//│ ║ l.93: val apply = Identity::apply +//│ ║ l.92: val apply = Identity::apply //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '17', got: 'undefined' @@ -120,7 +119,7 @@ Counter.count :expect 21 fun captured() = { class Local { fun get() = 21 } - val Local = 0 + fun Local() = 0 fun read(value) = value.Local#get() read(new Local) } @@ -136,10 +135,10 @@ open first open second fun sameClass(value) = value.Entry#get() //│ ╔══[COMPILATION ERROR] The projection class is ambiguous -//│ ║ l.137: fun sameClass(value) = value.Entry#get() +//│ ║ l.136: fun sameClass(value) = value.Entry#get() //│ ║ ^^^^^^ //│ ╟── class: 'Entry' -//│ ║ l.132: class Library { class Entry { fun get() = 1 } } +//│ ║ l.131: class Library { class Entry { fun get() = 1 } } //│ ╙── ^^^^^ //│ first = Library { Entry: class Entry } //│ second = Library { Entry: class Entry } @@ -147,17 +146,17 @@ fun sameClass(value) = value.Entry#get() :e fun missingClass(value) = value.Unknown#get() //│ ╔══[COMPILATION ERROR] Cannot resolve the projection class -//│ ║ l.148: fun missingClass(value) = value.Unknown#get() +//│ ║ l.147: fun missingClass(value) = value.Unknown#get() //│ ╙── ^^^^^^^^ :e val instance = new Parent fun instanceAsClass(value) = value.instance#get() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.155: fun instanceAsClass(value) = value.instance#get() +//│ ║ l.154: fun instanceAsClass(value) = value.instance#get() //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Parent' cannot be used as a projection class. -//│ ║ l.154: val instance = new Parent +//│ ║ l.153: val instance = new Parent //│ ╙── ^^^^^^^^^^ //│ instance = Parent @@ -194,10 +193,10 @@ fun invalidAlias(value) = value.AliasToNumber#get() val SourceNumber = 123 val AliasToNumber = SourceNumber //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.193: fun invalidAlias(value) = value.AliasToNumber#get() +//│ ║ l.192: fun invalidAlias(value) = value.AliasToNumber#get() //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Integer literal cannot be used as a projection class. -//│ ║ l.194: val SourceNumber = 123 +//│ ║ l.193: val SourceNumber = 123 //│ ╙── ^^^ //│ AliasToNumber = 123 //│ SourceNumber = 123 diff --git a/hkmc2/shared/src/test/mlscript/newres/ReceiverResolution.mls b/hkmc2/shared/src/test/mlscript/newres/ReceiverResolution.mls index 208a37fc45..ae658de08c 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ReceiverResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ReceiverResolution.mls @@ -4,10 +4,10 @@ // Lexical member references resolve through their receiver, including members // declared after the method and overloaded class/term names. module Receiver { - fun values() = [value, Item, (new Item).answer()] + fun values() = [value, Item(), (new Item).answer()] val value = 31 class Item { fun answer() = value } - val Item = 32 + fun Item() = 32 } :expect [31, 32, 31] diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls b/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls index 73bbaf9f3e..7fc94d6ad2 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeResolution.mls @@ -41,11 +41,10 @@ fun ambiguous(n: Ambiguous) = n // A type annotation selects the class even when its name also has a value. class Box -val Box = 7 +fun Box() = 7 fun identity(box: Box): Box = box identity(new Box) is Box //│ = true -//│ Box = 7 // Forward aliases are checked even when the annotated function is unused. :ge @@ -53,9 +52,9 @@ fun unused(value: Invalid) = value type Invalid = notAType val notAType = 1 //│ ╔══[COMPILATION ERROR] Resolution error in member reference -//│ ║ l.53: type Invalid = notAType +//│ ║ l.52: type Invalid = notAType //│ ║ ^^^^^^^^ //│ ╟── Value symbol 'notAType' cannot be used as a type -//│ ║ l.54: val notAType = 1 +//│ ║ l.53: val notAType = 1 //│ ╙── ^^^^^^^^^^^^^^^^ //│ notAType = 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/OverloadedClasses.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/OverloadedClasses.mls new file mode 100644 index 0000000000..a4015432ce --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/OverloadedClasses.mls @@ -0,0 +1,52 @@ +:. + + +// A class constructor and an explicitly overloaded function have distinct identities. +:noInline +:expect 42 +class Factory { + val answer: Int = 21 +} +fun Factory(x: Int): Int = x + 1 +Factory(20) + (new Factory).answer +//│ Wasm result: +//│ = 42 + + +// Imported constructors and term functions remain distinct in later blocks. +:noInline +:expect 43 +Factory(21) + (new Factory).answer +//│ Wasm result: +//│ = 43 + + +// Declaration order and nominal class tests select the same constructor. +:noInline +:expect 42 +fun Reverse(): Int = 40 +class Reverse { val answer: Int = 2 } +Reverse() + (new Reverse).answer +//│ Wasm result: +//│ = 42 + + +:noInline +:expect 1 +if new Reverse is Reverse then 1 else 0 +//│ Wasm result: +//│ = 1 + + +// Duplicated local classes and their function companions keep distinct identities. +:lift +:inlineThreshold 10000 +:expect 45 +fun make(n: Int): Int = { + class Local { val answer: Int = 20 } + fun Local(x: Int): Int = x + 1 + Local(n) + (new Local).answer +} +make(1) + make(2) +//│ Wasm result: +//│ = 45 From df4f4d5782bff0ce45be157c983c3b00cc251ac9 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 3 Oct 2026 15:50:50 +0800 Subject: [PATCH 206/228] Resolve projection qualifiers by class identity --- .../main/scala/hkmc2/codegen/Lowering.scala | 7 +- .../scala/hkmc2/semantics/NewResolver.scala | 35 +++++++-- .../src/main/scala/hkmc2/semantics/Term.scala | 10 +-- .../test/mlscript/newres/CallSiteShapes.mls | 22 +++--- .../src/test/mlscript/newres/Projections.mls | 76 +++++++++++++------ 5 files changed, 98 insertions(+), 52 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 7ad1341d6d..d222480353 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -724,17 +724,16 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case N => Select(prefix, name)(target, sel.toLoc)(false) /** A projection must identify its class as well as its member: different - * classes can inherit the same definition, and wildcard receivers can differ. */ + * classes can inherit the same definition. Captures do not disambiguate classes. */ private def checkProjection(sel: NewSel): Bool = sel.cls match case N => true case S(cls) => if sel.isErroneous then false else if sel.hasAmbiguousClass then - // Different instance contexts can lead to the same class declaration. - // Preserve the ambiguity, but report each declaration's location once. + // Qualifiers denote class declarations; their runtime receivers are irrelevant. raise: ErrorReport(msg"The projection class is ambiguous" -> cls.toLoc :: - sel.resolvedClasses.map(_._1).distinct.map(sym => msg"class: '${sym.nme}'" -> sym.toLoc), + sel.resolvedClasses.map(sym => msg"class: '${sym.nme}'" -> sym.toLoc), source = Diagnostic.Source.Compilation) false else if sel.resolvedClasses.isEmpty then diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index e2977b5b06..7c7d07da47 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2676,6 +2676,23 @@ class NewResolver: () => fromSymbol(sh, value, trm, sym => if recordTargets then recordValueTarget(trm, sym), false)) case sh: TermShape => value(sh) + /** Locate a projected declaration in the actual receiver's class ancestry. + * Each parent shape carries the crossing from its class into the child, so + * keep those crossings before adding the receiver's outer captures. + */ + private def projectedMember(receiver: TermShape, cls: ClassDef, name: Str)(using NewResolverState): Opt[MemberLookup] = + val ShapeParts(source, instances, _) = shapeParts(receiver) + val (head, marks) = source.applicationHead + val member = head match + case definition: DefnShape => + if definition.clsDef.contains(cls) then S(definition.getInstanceMember(name)) + else definition.ext.flatMap(projectedMember(_, cls, name)) + case base: BaseShape => + if base.defn is cls then S(base.getMember(name)) + else base.ext.flatMap(projectedMember(_, cls, name)) + case _ => N + member.map(_.withMarks(marks).instantiate(instances)) + def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") def member(info: MemberLookup, description: Message, diagnostic: Message => Ls[(Message, Opt[Loc])], @@ -2736,13 +2753,11 @@ class NewResolver: case S(cls) => listenClass(cls, recordTargets = true)(ref => val cd = ref.definition - val marks = ref.marks - val candidate = cd.sym -> marks - rstate.recordResolution(sel, sel.resolvedClasses.contains(candidate))(sel.resolvedClasses ::= candidate) + rstate.recordResolution(sel, sel.resolvedClasses.contains(cd.sym))(sel.resolvedClasses ::= cd.sym) listenExt(cd, ext => - val info = DefnShape(cd, ext).getInstanceMember(sel.id.name).withMarks(marks) + val info = DefnShape(cd, ext).getInstanceMember(sel.id.name) info match - case MemberLookup.Found(bms: BlockMemberSymbol, _) => + case MemberLookup.Found(bms: BlockMemberSymbol, declarationMarks) => // Resolve the projection target even in an unused function. Wait // for the receiver before deciding whether its result may expose // implementation flow or only the declared member signature. @@ -2760,7 +2775,15 @@ class NewResolver: // it cannot recover that class's implementation arguments. val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, context :: Nil, nominal.annotation, true) - case _ => info + case _ => + // C# selects a declaration, not an instance of C. Transport + // the member through the actual receiver, never the qualifier. + projectedMember(receiver, cd, sel.id.name).getOrElse: + // Without a path into this class, only its declared + // interface is available; qualifier values add no evidence. + val opaque = abstractType(new TypeResolution(sel, msgs => resolError(sel, msgs))) + MemberLookup.Declared(bms, cd.tparams.map(_.sym -> opaque).toMap, + declarationMarks ::: ExitMark(ResolutionBoundary(cd.sym), N, NoMarks) :: Nil, N, true) member(selected, msg"Class '${cd.sym.nme}'", message => (message -> cd.toLoc) :: Nil, TypeSubstitution.empty) case _ => member(info, msg"Class '${cd.sym.nme}'", message => (message -> cd.toLoc) :: Nil, TypeSubstitution.empty)) , sh => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index ad78e961aa..fc2d4c9952 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -350,8 +350,9 @@ sealed trait NewSelImpl extends NewResolvableImpl: // access rather than looking for a nominal field symbol. var tupleIndex: Opt[Int] = N var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution - // Class identity and captures must survive even when candidates share an inherited member. - var resolvedClasses: Ls[(ClassSymbol, Ls[Marks])] = Nil + // Projection qualifiers disambiguate declarations, independently of runtime captures. + // Keep class identities because different classes can select the same inherited member. + var resolvedClasses: Ls[ClassSymbol] = Nil // Unlike a field whose value happens to be a class, C.class has a class // target without an ordinary member. Its receiver remains the class reference. def isClassValue(using Erasure): Bool = @@ -359,10 +360,7 @@ sealed trait NewSelImpl extends NewResolvableImpl: !hasDynamicTarget && resolvedTargets.exists(_.isInstanceOf[ClassSymbol]) def hasAmbiguousClass(using Erasure): Bool = hasAmbiguousClassImpl // Also used by the guarded symbol lookup for completed imports. - private[semantics] def hasAmbiguousClassImpl: Bool = resolvedClasses.sizeCompare(1) > 0 || self.cls.exists: - _.withoutCaptures match - case ref: Term.UnresolvedRef => ref.resolvedMembers.distinct.sizeCompare(1) > 0 - case _ => false + private[semantics] def hasAmbiguousClassImpl: Bool = resolvedClasses.sizeCompare(1) > 0 sealed trait UnresolvedRefImpl extends NewResolvableImpl: self: Term.UnresolvedRef => diff --git a/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls index d7ee7c474a..ca5897cc7f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/CallSiteShapes.mls @@ -42,18 +42,16 @@ apply(id, new A).get() + apply(id, new B).get() //│ ║ l.40: apply(id, new A).get() + apply(id, new B).get() //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'get' of a value with unknown shape. -//│ ╟── Rest parameter 'args' admits elements of unknown shape. -//│ ╟── 'apply' is exposed by this compilation unit. +//│ ╟── The shape of this value is unknown. //│ ║ l.39: val apply = Id::apply -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.40: apply(id, new A).get() + apply(id, new B).get() //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'get' of a value with unknown shape. -//│ ╟── Rest parameter 'args' admits elements of unknown shape. -//│ ╟── 'apply' is exposed by this compilation unit. +//│ ╟── The shape of this value is unknown. //│ ║ l.39: val apply = Id::apply -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '40', got: 'undefined' //│ apply = fun @@ -65,24 +63,24 @@ apply(id, new A).get() + apply(id, new B).get() fun restIdentity(ignored, ...args) = identity(...args) restIdentity(0, new A).get() + restIdentity(0, new B).get() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.66: restIdentity(0, new A).get() + restIdentity(0, new B).get() +//│ ║ l.64: restIdentity(0, new A).get() + restIdentity(0, new B).get() //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'get' of a value with unknown shape. //│ ╟── Rest parameter 'args' admits elements of unknown shape. -//│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) +//│ ║ l.63: fun restIdentity(ignored, ...args) = identity(...args) //│ ║ ^^^^^^^ //│ ╟── 'restIdentity' is exposed by this compilation unit. -//│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) +//│ ║ l.63: fun restIdentity(ignored, ...args) = identity(...args) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.66: restIdentity(0, new A).get() + restIdentity(0, new B).get() +//│ ║ l.64: restIdentity(0, new A).get() + restIdentity(0, new B).get() //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'get' of a value with unknown shape. //│ ╟── Rest parameter 'args' admits elements of unknown shape. -//│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) +//│ ║ l.63: fun restIdentity(ignored, ...args) = identity(...args) //│ ║ ^^^^^^^ //│ ╟── 'restIdentity' is exposed by this compilation unit. -//│ ║ l.65: fun restIdentity(ignored, ...args) = identity(...args) +//│ ║ l.63: fun restIdentity(ignored, ...args) = identity(...args) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '40', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/Projections.mls b/hkmc2/shared/src/test/mlscript/newres/Projections.mls index f0fea9802c..ff7fb92d77 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Projections.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Projections.mls @@ -96,10 +96,9 @@ apply(identity, new Answer).get() //│ ║ l.94: apply(identity, new Answer).get() //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'get' of a value with unknown shape. -//│ ╟── Rest parameter 'args' admits elements of unknown shape. -//│ ╟── 'apply' is exposed by this compilation unit. +//│ ╟── The shape of this value is unknown. //│ ║ l.93: val apply = Identity::apply -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '17', got: 'undefined' //│ apply = fun @@ -127,37 +126,33 @@ fun captured() = { captured() //│ = 21 -// Two instances exposing the same class symbol are still ambiguous. -:e +// Different wildcard receivers exposing the same class symbol agree on the qualifier. +:expect [1, 1] class Library { class Entry { fun get() = 1 } } let first = new Library let second = new Library open first open second fun sameClass(value) = value.Entry#get() -//│ ╔══[COMPILATION ERROR] The projection class is ambiguous -//│ ║ l.137: fun sameClass(value) = value.Entry#get() -//│ ║ ^^^^^^ -//│ ╟── class: 'Entry' -//│ ║ l.132: class Library { class Entry { fun get() = 1 } } -//│ ╙── ^^^^^ +[sameClass(new first.Entry), sameClass(new second.Entry)] +//│ = [1, 1] //│ first = Library { Entry: class Entry } //│ second = Library { Entry: class Entry } :e fun missingClass(value) = value.Unknown#get() //│ ╔══[COMPILATION ERROR] Cannot resolve the projection class -//│ ║ l.148: fun missingClass(value) = value.Unknown#get() +//│ ║ l.143: fun missingClass(value) = value.Unknown#get() //│ ╙── ^^^^^^^^ :e val instance = new Parent fun instanceAsClass(value) = value.instance#get() //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.155: fun instanceAsClass(value) = value.instance#get() +//│ ║ l.150: fun instanceAsClass(value) = value.instance#get() //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Instance of class 'Parent' cannot be used as a projection class. -//│ ║ l.154: val instance = new Parent +//│ ║ l.149: val instance = new Parent //│ ╙── ^^^^^^^^^^ //│ instance = Parent @@ -194,16 +189,16 @@ fun invalidAlias(value) = value.AliasToNumber#get() val SourceNumber = 123 val AliasToNumber = SourceNumber //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.193: fun invalidAlias(value) = value.AliasToNumber#get() +//│ ║ l.188: fun invalidAlias(value) = value.AliasToNumber#get() //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Integer literal cannot be used as a projection class. -//│ ║ l.194: val SourceNumber = 123 +//│ ║ l.189: val SourceNumber = 123 //│ ╙── ^^^ //│ AliasToNumber = 123 //│ SourceNumber = 123 -// A single stored field can still contain distinct captured class values. -:e +// Stored class values with different captures still identify one class declaration. +:expect [1, 2] class CapturedLibrary(val number) { class Entry { fun get() = number } } @@ -213,12 +208,45 @@ fun chooseEntry(flag) = if flag then leftLibrary.Entry.class else rightLibrary.E val classFields = {entry: chooseEntry(true)} open classFields fun storedProjection(value) = value.entry#get() -//│ ╔══[COMPILATION ERROR] The projection class is ambiguous -//│ ║ l.215: fun storedProjection(value) = value.entry#get() -//│ ║ ^^^^^^ -//│ ╟── class: 'Entry' -//│ ║ l.208: class Entry { fun get() = number } -//│ ╙── ^^^^^ +[storedProjection(new leftLibrary.Entry), storedProjection(new rightLibrary.Entry)] +//│ = [1, 2] //│ classFields = {entry: class Entry} //│ leftLibrary = CapturedLibrary(1) //│ rightLibrary = CapturedLibrary(2) + +// The qualifier's outer object must not supply the selected method's result shape. +:expect 42 +let recordLibrary = CapturedLibrary({answer: 42}) +let scalarLibrary = CapturedLibrary(0) +val EntryAlias = scalarLibrary.Entry.class +private fun recordProjection(value) = value.EntryAlias#get().answer +recordProjection(new recordLibrary.Entry) +//│ = 42 +//│ EntryAlias = class Entry +//│ recordLibrary = CapturedLibrary({answer: 42}) +//│ scalarLibrary = CapturedLibrary(0) + +// Distinct class symbols remain ambiguous even when both inherit the same method. +:e +class OtherEntry extends Parent {} +private fun chooseClass(flag) = if flag then Parent.class else OtherEntry.class +let classAlias = chooseClass(true) +fun ambiguousAlias(value) = value.classAlias#get() +//│ ╔══[COMPILATION ERROR] The projection class is ambiguous +//│ ║ l.234: fun ambiguousAlias(value) = value.classAlias#get() +//│ ║ ^^^^^^^^^^^ +//│ ╟── class: 'OtherEntry' +//│ ║ l.231: class OtherEntry extends Parent {} +//│ ║ ^^^^^^^^^^ +//│ ╟── class: 'Parent' +//│ ║ l.34: class Parent { fun get() = 9 } +//│ ╙── ^^^^^^ +//│ classAlias = class Parent + +// An unknown receiver still exposes the inherited field's declared type. +:expect 6 +class FieldBase(val item: Int) +class FieldChild extends FieldBase(5) {} +fun readInherited(value) = value.FieldChild#item + 1 +readInherited(new FieldChild) +//│ = 6 From 14fbf20936b95ec73497fc26c3f0f1fc90f3fc46 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 15:48:17 +0800 Subject: [PATCH 207/228] Resolve Wasm session imports by definition identity --- doc/new-resolution-design.md | 4 +++ .../src/main/scala/hkmc2/codegen/Block.scala | 5 ++- .../scala/hkmc2/codegen/wasm/text/Ctx.scala | 36 +++++++++++++++---- .../scala/hkmc2/codegen/wasm/text/Wasm.scala | 11 +++++- .../hkmc2/codegen/wasm/text/WatBuilder.scala | 13 ++----- .../src/test/mlscript/backlog/ToTriage.mls | 22 +++--------- .../test/mlscript/basics/BadOverloading.mls | 33 +++++++++-------- .../newres/InvalidClassCompanions.mls | 12 +++++++ .../newres/wasm/OverloadedClasses.mls | 26 ++++++++++++++ .../src/test/scala/hkmc2/WasmDiffMaker.scala | 23 +----------- 10 files changed, 109 insertions(+), 76 deletions(-) diff --git a/doc/new-resolution-design.md b/doc/new-resolution-design.md index d325ef97ee..082c503019 100644 --- a/doc/new-resolution-design.md +++ b/doc/new-resolution-design.md @@ -220,6 +220,10 @@ Instance methods cannot supply per-instance companions either, because their function objects are shared. Inlining may replace calls to a companion function, but must preserve its binding while the class is used. WASM identifies constructor functions by their class symbols, separately from explicit function companions. +Session imports follow the selected definition and cast target, rather than MIR +`freeVars`, which tracks lexical bindings introduced by `Scoped`. Importing a +class must not also import its function companion: the two have independent +signatures and can be shadowed independently in later worksheet blocks. Foreign declarations expose call and constructor capabilities explicitly. The JS backend uses native class values for `new` and patterns without generated diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index a4c9bbc8d5..227dbcccab 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -67,7 +67,10 @@ type ValueSymbol = SimpleSymbol | TermSymbol | BlockMemberSymbol | InnerSymbol */ type BoundSymbol = ScopedSymbol | TermSymbol -/** Symbols that may occur in MIR free-variable sets. +/** Symbols of lexical bindings required by MIR, including assignment destinations. + * A `MemberRef` requires its block member binding regardless of the selected definition: + * `Scoped` introduces that binding, and JS can store its class on the companion function. + * Analyses needing individual definitions must instead inspect the reference's `disamb`. */ type FreeSymbol = SimpleSymbol | BlockMemberSymbol | LabelSymbol diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala index 5d385c43f3..6d9a1a40dc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala @@ -10,7 +10,7 @@ import document.* import document.Document import semantics.{ BlockMemberSymbol, ClassSymbol, Elaborator, - InnerSymbol, LabelSymbol, LocalVarSymbol, ModuleOrObjectSymbol, ParamList, TempSymbol, + InnerSymbol, LabelSymbol, LocalVarSymbol, ModuleOrObjectSymbol, ParamList, Symbol, TempSymbol, }, Elaborator.State import text.Param as WasmParam @@ -35,6 +35,33 @@ sealed trait SessionBinding: object SessionBinding: val ReplModuleName: Str = "repl" + /** Dependencies to look up among previously exported session bindings. + * MIR `freeVars` describes lexical bindings, which cannot distinguish a class from + * its function companion. Follow resolved references and cast targets instead. + * Symbols defined in this block cannot match earlier exports: their identities + * are distinct, including after inlining refreshes local definitions. + */ + def referencedSymbols(block: Block)(using Elaborator.Ctx, State): Ls[Symbol] = + val symbols = LinkedHashSet.empty[Symbol] + new BlockTraverser: + override def applySymbol(sym: Symbol): Unit = symbols += sym + override def applyValue(value: Value): Unit = value match + case Value.MemberRef(bms, disamb) => applySymbol(ExternSymbol.forReference(bms, disamb)) + case _ => super.applyValue(value) + override def applyResult(result: codegen.Result): Unit = + result match + case Cast(_, target, _) => target.canonicalize match + case ErasedType.AnyRef(_, tpeSym) => applySymbol(tpeSym) + case _ => () + case _ => () + super.applyResult(result) + override def applyPath(path: Path): Unit = path match + // ValDefn traverses its RHS as a Path, whereas Return and Assign use Result. + case cast: Cast => applyResult(cast) + case _ => super.applyPath(path) + .applyBlock(block) + symbols.toList + /** Metadata for an exported function that later Wasm REPL modules can import. * * @param sym @@ -54,12 +81,7 @@ final case class SessionFunc( funcType: FunctionType, ) extends SessionBinding: def bindingKey: Str = s"func:$moduleName:$exportName" - // MIR free variables use the block member even when the reference selects its class. - // Importing that member must recover both its function and its constructor, under distinct keys. - // Prefer the IR member: inlining refreshes symbols but retains their source definitions. - def bindingSyms: Seq[SlotSymbol] = sym match - case cls: ClassSymbol => cls :: cls.irClsLikeDefn.map(_.sym).orElse(cls.asBlkMember).toList - case sym: BlockMemberSymbol => sym :: Nil + def bindingSyms: Seq[SlotSymbol] = sym :: Nil override def exportNameOpt: Opt[Str] = S(exportName) /** Metadata for an exported global that later Wasm REPL modules can import. diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Wasm.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Wasm.scala index 644b420297..a83761f4c1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Wasm.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Wasm.scala @@ -6,7 +6,7 @@ package text import hkmc2.utils.*, shorthands.* import document.* -import semantics.{BlockMemberSymbol, ClassSymbol, DefinitionSymbol, Elaborator, InnerSymbol, LocalVarSymbol, TempSymbol}, Elaborator.State +import semantics.{BlockMemberSymbol, ClassCtorSymbol, ClassSymbol, DefinitionSymbol, Elaborator, InnerSymbol, LocalVarSymbol, TempSymbol}, Elaborator.State import utils.Scope import scala.collection.Map @@ -14,6 +14,15 @@ import scala.collection.Map /** Symbols that can represent an importable or exportable Wasm construct. */ private[text] type ExternSymbol = BlockMemberSymbol | ClassSymbol | TempSymbol +private[text] object ExternSymbol: + /** Class constructors and explicit function companions share a source name but + * have distinct Wasm functions. Imports and calls must use the same identity. */ + def forReference(bms: BlockMemberSymbol, disamb: DefinitionSymbol[?]): BlockMemberSymbol | ClassSymbol = + disamb match + case cls: ClassSymbol => cls + case ctor: ClassCtorSymbol => ctor.associatedCls + case _ => bms + /** Symbols that can be lowered into a global or local slot in a Wasm function (i.e. parameters and locals). */ private[text] type SlotSymbol = InnerSymbol | ScopedSymbol diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala index 271c599fb5..049b2d588c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala @@ -887,11 +887,6 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui case cls: ClassSymbol => cls case _ => defn.sym - private def functionSymbol(bms: BlockMemberSymbol, disamb: DefinitionSymbol[?]): BlockMemberSymbol | ClassSymbol = - disamb match - case ctor: ClassCtorSymbol => ctor.associatedCls - case _ => bms - /** Registers a placeholder class-associated function so later lowering can overwrite it. */ private def predeclareClassFunc( defn: ClsLikeDefn, @@ -1738,7 +1733,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui errExpr: Ls(msg"Plain class references are not supported in Wasm; instantiate the class instead." -> r.toLoc) else - ctx.getFunc(functionSymbol(bms, disamb)) match + ctx.getFunc(ExternSymbol.forReference(bms, disamb)) match case S(funcIdx) => ref.func(funcIdx, RefType(ctx.getFuncTypeUse_!(funcIdx).typeIdx, nullable = false)) case N => getVar(bms, r.toLoc) case Value.This(sym) => @@ -1833,7 +1828,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui case N => fun match case Value.MemberRef(l, disamb) => - val base = ctx.getFunc(functionSymbol(l, disamb)) + val base = ctx.getFunc(ExternSymbol.forReference(l, disamb)) val baseFuncIdx = base match case S(idx) => idx case N => return errExpr( @@ -2415,9 +2410,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui objectTag = typeinfo.objectTag, rttiTypeInfo = rttiTypeInfo, rttiGlobalExportName = rttiGlobalInfo.exportName.get, - aliasSyms = clsLikeDefn.isym match - case mos: ModuleOrObjectSymbol => mos :: Nil - case _ => Nil, + aliasSyms = clsLikeDefn.isym :: Nil, )) if !isSingletonObj && clsLikeDefn.sym.nameIsMeaningful then summon[SessionExportCtx].emit(SessionFunc( diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index a1f1e341d7..352d3ee422 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -364,34 +364,20 @@ data class Foo(mut x) // ——— ——— ——— -:breakme // FIXME should report a compilation error -:e -fun Foo -class Foo -//│ ╔══[COMPILATION ERROR] A term declaration cannot have an implemented class companion -//│ ║ l.369: fun Foo -//│ ║ ^^^^^^^ -//│ ╟── Companion 'Foo' is defined here -//│ ║ l.370: class Foo -//│ ╙── ^^^^^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: Cannot set properties of undefined (setting 'class') - -// ——— ——— ——— - // * TODO: improve errors (don't cascade meaningless errors) :e class Id(name: UnboundName) //│ ╔══[COMPILATION ERROR] Name not found: UnboundName -//│ ║ l.383: class Id(name: UnboundName) +//│ ║ l.369: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.383: class Id(name: UnboundName) +//│ ║ l.369: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got ‹error› -//│ ║ l.383: class Id(name: UnboundName) +//│ ║ l.369: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.383: class Id(name: UnboundName) +//│ ║ l.369: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ // ——— ——— ——— diff --git a/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls b/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls index ffb9bb8907..2fbac81aca 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadOverloading.mls @@ -33,15 +33,14 @@ class Foo //│ ║ l.27: object Foo //│ ╙── ^^^^^^^^^^ -:todo // TODO: silence such internal errors when a compilation error is already reported? :e fun Foo = 1 object Foo //│ ╔══[COMPILATION ERROR] Illegal overloading of function 'Foo' -//│ ║ l.38: fun Foo = 1 +//│ ║ l.37: fun Foo = 1 //│ ║ ^^^^^^^^^^^ //│ ╟── with object of the same name -//│ ║ l.39: object Foo +//│ ║ l.38: object Foo //│ ╙── ^^^^^^^^^^ :e @@ -49,10 +48,10 @@ object Foo fun Foo: Int object Foo //│ ╔══[COMPILATION ERROR] A term declaration cannot have an implemented object companion -//│ ║ l.49: fun Foo: Int +//│ ║ l.48: fun Foo: Int //│ ║ ^^^^^^^^^^^^ //│ ╟── Companion 'Foo' is defined here -//│ ║ l.50: object Foo +//│ ║ l.49: object Foo //│ ╙── ^^^^^^^^^^ :todo @@ -61,22 +60,22 @@ fun Foo = 1 object Foo class Foo //│ ╔══[COMPILATION ERROR] Illegal overloading of function 'Foo' -//│ ║ l.60: fun Foo = 1 +//│ ║ l.59: fun Foo = 1 //│ ║ ^^^^^^^^^^^ //│ ╟── with object of the same name -//│ ║ l.61: object Foo +//│ ║ l.60: object Foo //│ ╙── ^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] A class companion must be a function with a parameter list -//│ ║ l.60: fun Foo = 1 +//│ ║ l.59: fun Foo = 1 //│ ║ ^^^^^^^^^^^ //│ ╟── Arbitrary values cannot be overloaded with class 'Foo' -//│ ║ l.62: class Foo +//│ ║ l.61: class Foo //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal overloading of class 'Foo' -//│ ║ l.62: class Foo +//│ ║ l.61: class Foo //│ ║ ^^^^^^^^^ //│ ╟── with object of the same name -//│ ║ l.61: object Foo +//│ ║ l.60: object Foo //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] TypeError: Cannot assign to read only property 'class' of object '[object Object]' @@ -93,10 +92,10 @@ module Foo val Foo = 1 module Foo //│ ╔══[COMPILATION ERROR] A module companion must be a function with a parameter list -//│ ║ l.93: val Foo = 1 +//│ ║ l.92: val Foo = 1 //│ ║ ^^^^^^^^^^^ //│ ╟── Arbitrary values cannot be overloaded with module 'Foo' -//│ ║ l.94: module Foo +//│ ║ l.93: module Foo //│ ╙── ^^^^^^^^^^ //│ Foo = class Foo @@ -105,10 +104,10 @@ module Foo fun (**) f1(a, b) = "[" + a + " * " + b + "]" fun (**) f2(a, b) = "(" + a + " + " + b + ")" //│ ╔══[COMPILATION ERROR] Symbolic name '**' of definition 'f1' is already used -//│ ║ l.105: fun (**) f1(a, b) = "[" + a + " * " + b + "]" +//│ ║ l.104: fun (**) f1(a, b) = "[" + a + " * " + b + "]" //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── by sibling member 'f2' -//│ ║ l.106: fun (**) f2(a, b) = "(" + a + " + " + b + ")" +//│ ║ l.105: fun (**) f2(a, b) = "(" + a + " + " + b + ")" //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1 ** 2 @@ -119,10 +118,10 @@ fun (**) t() = 0 fun (++) t() = 1 //│ ╔══[COMPILATION ERROR] Multiple definitions of symbol 't' //│ ╟── defined here -//│ ║ l.118: fun (**) t() = 0 +//│ ║ l.117: fun (**) t() = 0 //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── defined here -//│ ║ l.119: fun (++) t() = 1 +//│ ║ l.118: fun (++) t() = 1 //│ ╙── ^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/InvalidClassCompanions.mls b/hkmc2/shared/src/test/mlscript/newres/InvalidClassCompanions.mls index af917e8ed1..3d4e694b91 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InvalidClassCompanions.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InvalidClassCompanions.mls @@ -175,3 +175,15 @@ class ModuleContainer(val value: Int) { //│ ╟── An instance method is shared by all instances, but its companion is created per instance //│ ║ l.169: module Item { val answer = value } //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +// A bare declaration is rejected even when it precedes its companion. +:e +fun Bare +class Bare +//│ ╔══[COMPILATION ERROR] A term declaration cannot have an implemented class companion +//│ ║ l.182: fun Bare +//│ ║ ^^^^^^^^ +//│ ╟── Companion 'Bare' is defined here +//│ ║ l.183: class Bare +//│ ╙── ^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/OverloadedClasses.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/OverloadedClasses.mls index a4015432ce..9e94ed78ac 100644 --- a/hkmc2/shared/src/test/mlscript/newres/wasm/OverloadedClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/OverloadedClasses.mls @@ -8,6 +8,8 @@ class Factory { val answer: Int = 21 } fun Factory(x: Int): Int = x + 1 +fun originalFactory() = new Factory +type OriginalFactory = Factory Factory(20) + (new Factory).answer //│ Wasm result: //│ = 42 @@ -50,3 +52,27 @@ fun make(n: Int): Int = { make(1) + make(2) //│ Wasm result: //│ = 45 + + +// A class-only import must not require its old companion function's signature. +:noInline +:expect 99 +fun Factory(): Int = 99 +Factory() +//│ Wasm result: +//│ = 99 + +:inlineThreshold 10000 +:expect 21 +originalFactory().answer +//│ Wasm result: +//│ = 21 + + +// A cast depends on the original class too, without referring to its constructor. +:noInline +:expect 21 +fun checkOriginal(x): OriginalFactory = x +checkOriginal(originalFactory()).answer +//│ Wasm result: +//│ = 21 diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala index 7b33282f05..de8a7059be 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala @@ -79,29 +79,8 @@ abstract class WasmDiffMaker extends InvalMLDiffMaker: errored = true outerRaise(d) case d => outerRaise(d) - val sessionImportSymbols = mutable.LinkedHashSet.from[Symbol](pgrm.main.freeVars) - new BlockTraverser: - override def applyResult(r: Result): Unit = - // Cast targets are type dependencies rather than value references, so freeVars omits them. - r match - case Cast(_, target, _) => target.canonicalize match - case ErasedType.AnyRef(_, tpeSym) => sessionImportSymbols += tpeSym.bms.get - case _ => () - case _ => () - super.applyResult(r) - override def applyPath(p: Path): Unit = p match - // ValDefn traverses its RHS as a Path, whereas Return and Assign traverse a Result. - case c: Cast => applyResult(c) - case sel: Select => - sel.symbol.foreach: - case sym: ModuleOrObjectSymbol => sessionImportSymbols += sym - case _ => () - super.applyPath(sel) - case _ => - super.applyPath(p) - .applyBlock(pgrm.main) val sessionImports = mutable.LinkedHashMap.empty[Str, SessionBinding] - sessionImportSymbols.iterator.foreach: sym => + SessionBinding.referencedSymbols(pgrm.main).foreach: sym => sessionImportsBySymbol.get(sym).foreach: bindings => bindings.foreach: (bindingKey, binding) => sessionImports.update(bindingKey, binding) From f8c40da4661b81b863c1a38ebfe7b0f50927eeb1 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 3 Oct 2026 16:01:30 +0800 Subject: [PATCH 208/228] Derive projection classes from normalized qualifiers --- .../main/scala/hkmc2/codegen/Lowering.scala | 4 +-- .../scala/hkmc2/semantics/Elaborator.scala | 5 +++- .../scala/hkmc2/semantics/NewResolver.scala | 26 ++++++++++++------- .../src/main/scala/hkmc2/semantics/Term.scala | 10 +++---- .../src/test/mlscript/deforest/fusibility.mls | 6 ++--- .../src/test/mlscript/newres/Projections.mls | 22 ++++++++++++++++ 6 files changed, 52 insertions(+), 21 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index d222480353..8e22d7dd5a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -733,10 +733,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu // Qualifiers denote class declarations; their runtime receivers are irrelevant. raise: ErrorReport(msg"The projection class is ambiguous" -> cls.toLoc :: - sel.resolvedClasses.map(sym => msg"class: '${sym.nme}'" -> sym.toLoc), + sel.projectionClasses.map(sym => msg"class: '${sym.nme}'" -> sym.toLoc), source = Diagnostic.Source.Compilation) false - else if sel.resolvedClasses.isEmpty then + else if sel.projectionClasses.isEmpty then raise: ErrorReport(msg"Cannot resolve the projection class" -> cls.toLoc :: Nil, source = Diagnostic.Source.Compilation) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 3e0d1853a4..4559ea9d0e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1266,7 +1266,10 @@ extends Importer: res def elaborateProjection(prefix: Term, cls: Term, name: Ident): Term = - val res = new Term.NewSel(prefix, name, S(cls))(FlowSymbol.selProj(name.name)).withLocOf(tree) + // The qualifier denotes a class even when its source is a stored constructor. + // Resolve that interpretation on the qualifier itself, as for explicit `.class`. + val qualifier = new Term.NewSel(cls, new Ident("class").withLocOf(cls), N)(FlowSymbol.sel("class")).withLocOf(cls) + val res = new Term.NewSel(prefix, name, S(qualifier))(FlowSymbol.selProj(name.name)).withLocOf(tree) newSel(res) interpretRef(res, interp) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 7c7d07da47..18b9b08d23 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2693,6 +2693,17 @@ class NewResolver: case _ => N member.map(_.withMarks(marks).instantiate(instances)) + /** Class selections own their resolved class targets, including for aliases. + * Projection qualifiers use this same representation without evaluating it. + * The caller decides whether non-class values permit ordinary field selection. + */ + private def classSelection(sel: NewSel)(selected: ShapeListener[ClassReference], reject: ShapeListener[TermShape])(using NewResolverState): Unit = + listenClass(sel.prefix, recordTargets = true)(ref => + val cls = ref.definition.sym + rstate.recordResolution(sel, sel.resolvedTargets.contains(cls))(sel.resolvedTargets ::= cls) + selected(ref) + , reject) + def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") def member(info: MemberLookup, description: Message, diagnostic: Message => Ls[(Message, Opt[Loc])], @@ -2731,13 +2742,8 @@ class NewResolver: case _ => () sel.cls match case N if sel.id.name == "class" => - // Resolve the class overload directly, without selecting its constructor - // or companion. The resolved class symbol also identifies this selection - // during lowering; no ordinary member named `class` is involved. - listenClass(sel.prefix, recordTargets = true)(ref => - val cd = ref.definition - rstate.recordResolution(sel, sel.resolvedTargets.contains(cd.sym))(sel.resolvedTargets ::= cd.sym) - listenExt(cd, ext => + classSelection(sel)(ref => + listenExt(ref.definition, ext => val shape = ClassValueShape(ref.target, ext) assert(rstate.canResolve(sel) || ClassValueShape.targetsOf(sel).contains(ref.target), "Inference changed a completed class-value interpretation") @@ -2750,10 +2756,9 @@ class NewResolver: case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty) - case S(cls) => - listenClass(cls, recordTargets = true)(ref => + case S(cls: NewSel) => + classSelection(cls)(ref => val cd = ref.definition - rstate.recordResolution(sel, sel.resolvedClasses.contains(cd.sym))(sel.resolvedClasses ::= cd.sym) listenExt(cd, ext => val info = DefnShape(cd, ext).getInstanceMember(sel.id.name) info match @@ -2790,6 +2795,7 @@ class NewResolver: rstate.markError(sel) resolError(sel, sh.diagnostic(msg"${sh.describe.capitalize} cannot be used as a projection class.")) ) + case S(_) => lastWords("A projection qualifier must be a class selection") /** Both constructor references and explicit `new` use the same definition and * parameter lists, including auxiliary constructor(...) lists. A partial `new` diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index fc2d4c9952..f2fdc3a411 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -350,9 +350,6 @@ sealed trait NewSelImpl extends NewResolvableImpl: // access rather than looking for a nominal field symbol. var tupleIndex: Opt[Int] = N var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution - // Projection qualifiers disambiguate declarations, independently of runtime captures. - // Keep class identities because different classes can select the same inherited member. - var resolvedClasses: Ls[ClassSymbol] = Nil // Unlike a field whose value happens to be a class, C.class has a class // target without an ordinary member. Its receiver remains the class reference. def isClassValue(using Erasure): Bool = @@ -360,7 +357,11 @@ sealed trait NewSelImpl extends NewResolvableImpl: !hasDynamicTarget && resolvedTargets.exists(_.isInstanceOf[ClassSymbol]) def hasAmbiguousClass(using Erasure): Bool = hasAmbiguousClassImpl // Also used by the guarded symbol lookup for completed imports. - private[semantics] def hasAmbiguousClassImpl: Bool = resolvedClasses.sizeCompare(1) > 0 + private[semantics] def hasAmbiguousClassImpl: Bool = projectionClassesImpl.sizeCompare(1) > 0 + def projectionClasses(using Erasure): Ls[ClassSymbol] = projectionClassesImpl + private def projectionClassesImpl: Ls[ClassSymbol] = self.cls.toList.flatMap: + case qualifier: Term.NewSel => qualifier.resolvedTargets.collect { case cls: ClassSymbol => cls }.distinct + case _ => lastWords("A projection qualifier must be a class selection") sealed trait UnresolvedRefImpl extends NewResolvableImpl: self: Term.UnresolvedRef => @@ -648,7 +649,6 @@ enum Term extends Statement, AutoLocated, ShapePublisher: case term @ NewSel(prefix, id, cls) => val copy = NewSel(prefix.mkClone, id, cls.map(_.mkClone))(term.resSym) copy.resolvedMembers = term.resolvedMembers - copy.resolvedClasses = term.resolvedClasses copy.hasDynamicTarget = term.hasDynamicTarget copyNewResolution(term, copyShapes(term, copy)) case term @ UnresolvedRef(prefixes, id) => diff --git a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls index dc71fa1982..4f6b044c49 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls @@ -162,9 +162,9 @@ fun c2(x) = if x is AA(AA(_)) then 1 else 0 let p = AA(AA(AA(10))) in c1(p) + c2(p) //│ deforest > >>> non-affine syms >>> -//│ deforest > p@1 -//│ deforest > tmp@2 -//│ deforest > tmp@3 +//│ deforest > p@4 +//│ deforest > tmp@1 +//│ deforest > tmp@5 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< diff --git a/hkmc2/shared/src/test/mlscript/newres/Projections.mls b/hkmc2/shared/src/test/mlscript/newres/Projections.mls index ff7fb92d77..0e5418e094 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Projections.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Projections.mls @@ -250,3 +250,25 @@ class FieldChild extends FieldBase(5) {} fun readInherited(value) = value.FieldChild#item + 1 readInherited(new FieldChild) //│ = 6 + +// Constructor functions and raw class objects agree on the disambiguating symbol. +:expect 5 +class Identified(val item) +let qualifier = Identified +set qualifier = Identified.class +fun readMixed(value) = value.qualifier#item +readMixed(Identified(5)) +//│ = 5 +//│ qualifier = class Identified + +// A projection qualifier requires a class interpretation, not a field named class. +:e +let recordQualifier = {"class": Parent} +fun invalidRecordQualifier(value) = value.recordQualifier#get() +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.267: fun invalidRecordQualifier(value) = value.recordQualifier#get() +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Record literal cannot be used as a projection class. +//│ ║ l.266: let recordQualifier = {"class": Parent} +//│ ╙── ^^^^^^^^^^^^^^^ +//│ recordQualifier = {class: class Parent} From 001df84505df94fbcbb4d54f42529da184f795a5 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 3 Oct 2026 16:12:33 +0800 Subject: [PATCH 209/228] Encode projection qualifier selection type statically --- .../scala/hkmc2/semantics/NewResolver.scala | 3 +- .../src/main/scala/hkmc2/semantics/Term.scala | 28 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 18b9b08d23..ad64215067 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2756,7 +2756,7 @@ class NewResolver: case N => listenReceiver(sel.prefix): shape => log(s"newSel: sel = ${sel.showDbg}, shape = ${shape.shwDbg}") member(shape.getMember(sel.id.name), msg"${shape.describe.capitalize}", shape.diagnostic, TypeSubstitution.empty) - case S(cls: NewSel) => + case S(cls) => classSelection(cls)(ref => val cd = ref.definition listenExt(cd, ext => @@ -2795,7 +2795,6 @@ class NewResolver: rstate.markError(sel) resolError(sel, sh.diagnostic(msg"${sh.describe.capitalize} cannot be used as a projection class.")) ) - case S(_) => lastWords("A projection qualifier must be a class selection") /** Both constructor references and explicit `new` use the same definition and * parameter lists, including auxiliary constructor(...) lists. A partial `new` diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index f2fdc3a411..e832cf637c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -359,9 +359,21 @@ sealed trait NewSelImpl extends NewResolvableImpl: // Also used by the guarded symbol lookup for completed imports. private[semantics] def hasAmbiguousClassImpl: Bool = projectionClassesImpl.sizeCompare(1) > 0 def projectionClasses(using Erasure): Ls[ClassSymbol] = projectionClassesImpl - private def projectionClassesImpl: Ls[ClassSymbol] = self.cls.toList.flatMap: - case qualifier: Term.NewSel => qualifier.resolvedTargets.collect { case cls: ClassSymbol => cls }.distinct - case _ => lastWords("A projection qualifier must be a class selection") + private def projectionClassesImpl: Ls[ClassSymbol] = self.cls.toList.flatMap: qualifier => + qualifier.resolvedTargets.collect { case cls: ClassSymbol => cls }.distinct + + /** Preserve the selection subtype when cloning a projection's class qualifier. */ + def mkSelClone(using State, Erasure): Term.NewSel = + val copy = new Term.NewSel(self.prefix.mkClone, self.id, self.cls.map(_.mkSelClone))(self.resSym) + copy.withLocOf(self) + copy.resolvedMembers = resolvedMembers + copy.hasDynamicTarget = hasDynamicTarget + copy.tupleIndex = tupleIndex + copy.resolvedTargets = resolvedTargets + copy.isErroneous = isErroneous + copy.shapes ++= self.shapes + copy.typeInterpretation = self.typeInterpretation + copy sealed trait UnresolvedRefImpl extends NewResolvableImpl: self: Term.UnresolvedRef => @@ -385,8 +397,8 @@ enum Term extends Statement, AutoLocated, ShapePublisher: case SimpleRef(sym: codegen.SimpleSymbol)(val tree: Tree.Ident) extends Term, NewRefImpl case SelfRef(sym: InnerSymbol)(val tree: Tree.Ident) extends Term, NewRefImpl case MemberRef(sym: MemberSymbol)(val tree: Tree.Ident, val resSym: FlowSymbol) extends Term, NewResolvableImpl, NewRefImpl - /** An optional class fixes the lookup scope for an explicit member projection. */ - case NewSel(prefix: Term, id: Tree.Ident, cls: Opt[Term])(val resSym: FlowSymbol) extends Term, NewSelImpl, ShapeHost + /** An optional class selection fixes the lookup scope for an explicit member projection. */ + case NewSel(prefix: Term, id: Tree.Ident, cls: Opt[NewSel])(val resSym: FlowSymbol) extends Term, NewSelImpl, ShapeHost case UnresolvedRef(prefixes: Ls[Term], id: Tree.Ident)(val resSym: FlowSymbol) extends Term, UnresolvedRefImpl, ShapeHost case Capture(base: Term, thru: AnyDefinitionSymbol) extends Term // --- LEGACY --- @@ -646,11 +658,7 @@ enum Term extends Statement, AutoLocated, ShapePublisher: case term @ SimpleRef(sym) => SimpleRef(sym)(term.tree) case term @ SelfRef(sym) => SelfRef(sym)(term.tree) case term @ MemberRef(sym) => copyNewResolution(term, MemberRef(sym)(term.tree, term.resSym)) - case term @ NewSel(prefix, id, cls) => - val copy = NewSel(prefix.mkClone, id, cls.map(_.mkClone))(term.resSym) - copy.resolvedMembers = term.resolvedMembers - copy.hasDynamicTarget = term.hasDynamicTarget - copyNewResolution(term, copyShapes(term, copy)) + case term: NewSel => return term.mkSelClone case term @ UnresolvedRef(prefixes, id) => val clonedPrefixes = prefixes.map(_.mkClone) val copy = UnresolvedRef(clonedPrefixes, id)(term.resSym) From 72755ed6f25cb7bd9cf14aaa4e03bf4ab852d967 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 3 Oct 2026 16:19:30 +0800 Subject: [PATCH 210/228] Share metadata copying for typed selection clones --- .../src/main/scala/hkmc2/semantics/Term.scala | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index e832cf637c..833e5c7486 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -362,18 +362,6 @@ sealed trait NewSelImpl extends NewResolvableImpl: private def projectionClassesImpl: Ls[ClassSymbol] = self.cls.toList.flatMap: qualifier => qualifier.resolvedTargets.collect { case cls: ClassSymbol => cls }.distinct - /** Preserve the selection subtype when cloning a projection's class qualifier. */ - def mkSelClone(using State, Erasure): Term.NewSel = - val copy = new Term.NewSel(self.prefix.mkClone, self.id, self.cls.map(_.mkSelClone))(self.resSym) - copy.withLocOf(self) - copy.resolvedMembers = resolvedMembers - copy.hasDynamicTarget = hasDynamicTarget - copy.tupleIndex = tupleIndex - copy.resolvedTargets = resolvedTargets - copy.isErroneous = isErroneous - copy.shapes ++= self.shapes - copy.typeInterpretation = self.typeInterpretation - copy sealed trait UnresolvedRefImpl extends NewResolvableImpl: self: Term.UnresolvedRef => @@ -631,6 +619,10 @@ enum Term extends Statement, AutoLocated, ShapePublisher: * identity and completed results. Listeners belong to elaboration and are not copied. */ override def mkClone(using State, Erasure): Term = + def copyMetadata[T <: Term](source: Term, copy: T): T = + copy.withLocOf(source) + copy.typeInterpretation = source.typeInterpretation + copy def copyShapes[T <: ShapeHost](source: ShapeHost, copy: T): T = copy.shapes ++= source.shapes copy @@ -645,6 +637,13 @@ enum Term extends Statement, AutoLocated, ShapePublisher: copy.resolvedTargets = source.resolvedTargets copy.isErroneous = source.isErroneous copy + // Keep class qualifiers typed as selections while sharing all clone metadata copying. + def cloneSel(term: NewSel): NewSel = + val copy = new NewSel(term.prefix.mkClone, term.id, term.cls.map(cloneSel))(term.resSym) + copy.resolvedMembers = term.resolvedMembers + copy.hasDynamicTarget = term.hasDynamicTarget + copy.tupleIndex = term.tupleIndex + copyMetadata(term, copyNewResolution(term, copyShapes(term, copy))) val that = this match case Error() => Error() case UnitVal() => UnitVal() @@ -658,7 +657,7 @@ enum Term extends Statement, AutoLocated, ShapePublisher: case term @ SimpleRef(sym) => SimpleRef(sym)(term.tree) case term @ SelfRef(sym) => SelfRef(sym)(term.tree) case term @ MemberRef(sym) => copyNewResolution(term, MemberRef(sym)(term.tree, term.resSym)) - case term: NewSel => return term.mkSelClone + case term: NewSel => return cloneSel(term) case term @ UnresolvedRef(prefixes, id) => val clonedPrefixes = prefixes.map(_.mkClone) val copy = UnresolvedRef(clonedPrefixes, id)(term.resSym) @@ -732,9 +731,7 @@ enum Term extends Statement, AutoLocated, ShapePublisher: case Annotated(annot, target) => Annotated(annot, target.mkClone) case Handle(lhs, rhs, args, derivedClsSym, defs, body) => Handle(lhs, rhs.mkClone, args.map(_.mkClone), derivedClsSym, defs, body.mkClone) - that.withLocOf(this) - that.typeInterpretation = typeInterpretation - that + copyMetadata(this, that) // // private[semantics] val reslListeners: Buffer[Resolution] = MutSet.empty // private[semantics] val shapeListeners: Buffer[Shape => Unit] = Buffer.empty From ab32e937a920d785e1a84fea910b2fec01353d6d Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 3 Oct 2026 16:24:06 +0800 Subject: [PATCH 211/228] Use singleton return types for clone copying helpers --- hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 833e5c7486..f215b946a2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -619,21 +619,21 @@ enum Term extends Statement, AutoLocated, ShapePublisher: * identity and completed results. Listeners belong to elaboration and are not copied. */ override def mkClone(using State, Erasure): Term = - def copyMetadata[T <: Term](source: Term, copy: T): T = + def copyMetadata(source: Term, copy: Term): copy.type = copy.withLocOf(source) copy.typeInterpretation = source.typeInterpretation copy - def copyShapes[T <: ShapeHost](source: ShapeHost, copy: T): T = + def copyShapes(source: ShapeHost, copy: ShapeHost): copy.type = copy.shapes ++= source.shapes copy - def copyResolution[T <: Resolvable](source: Resolvable, copy: T): T = + def copyResolution(source: Resolvable, copy: Resolvable): copy.type = copy.isErroneous = source.isErroneous source.expansion.foreach(expansion => copy.expand(expansion.map(_.mkClone))) copyShapes(source, copy) - def copySelection[T <: Term & AnySel](source: Term & AnySel, copy: T): T = + def copySelection(source: Term & AnySel, copy: Term & AnySel): copy.type = copy.resolvedTargets = source.resolvedTargets copyResolution(source, copy) - def copyNewResolution[T <: NewResolvable](source: NewResolvable, copy: T): T = + def copyNewResolution(source: NewResolvable, copy: NewResolvable): copy.type = copy.resolvedTargets = source.resolvedTargets copy.isErroneous = source.isErroneous copy From 895918ec253803da664490a51359c90db8fd030e Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 17:57:45 +0800 Subject: [PATCH 212/228] Resolve override relationships and support explicit super references --- .../src/main/scala/hkmc2/codegen/Block.scala | 16 + .../hkmc2/codegen/BlockTransformer.scala | 6 + .../scala/hkmc2/codegen/BlockTraverser.scala | 2 + .../main/scala/hkmc2/codegen/Erasure.scala | 88 +++- .../src/main/scala/hkmc2/codegen/Lifter.scala | 9 + .../main/scala/hkmc2/codegen/Lowering.scala | 56 ++- .../main/scala/hkmc2/codegen/Printer.scala | 2 + .../codegen/ReflectionInstrumenter.scala | 3 + .../scala/hkmc2/codegen/SymbolRefresher.scala | 5 + .../codegen/flowAnalysis/FlowAnalysis.scala | 7 +- .../scala/hkmc2/codegen/js/JSBuilder.scala | 10 + .../scala/hkmc2/codegen/wasm/text/Ctx.scala | 16 +- .../hkmc2/codegen/wasm/text/WatBuilder.scala | 100 +++-- .../scala/hkmc2/semantics/Elaborator.scala | 27 ++ .../scala/hkmc2/semantics/Inheritance.scala | 75 ++++ .../scala/hkmc2/semantics/NewResolver.scala | 53 ++- .../main/scala/hkmc2/semantics/Symbol.scala | 10 + .../src/main/scala/hkmc2/semantics/Term.scala | 9 + .../main/scala/hkmc2/semantics/TermImpl.scala | 2 +- .../src/main/scala/hkmc2/syntax/Keyword.scala | 2 +- .../main/scala/hkmc2/syntax/ParseRule.scala | 1 + .../src/main/scala/hkmc2/syntax/Tree.scala | 5 + .../src/test/mlscript-compile/Block.mls | 10 +- .../shared/src/test/mlscript-compile/Iter.mls | 14 +- .../src/test/mlscript-compile/LazyArray.mls | 26 +- .../test/mlscript-compile/LazyFingerTree.mls | 26 +- .../src/test/mlscript-compile/Runtime.mls | 2 +- .../test/mlscript/basics/BadTypeClasses.mls | 36 +- .../src/test/mlscript/basics/Classes.mls | 22 +- .../test/mlscript/basics/GenericClasses.mls | 2 +- .../src/test/mlscript/basics/Indentation.mls | 2 +- .../src/test/mlscript/basics/Inheritance.mls | 4 +- .../shared/src/test/mlscript/basics/Super.mls | 9 + .../src/test/mlscript/basics/Underscores.mls | 2 +- .../src/test/mlscript/codegen/BasicTerms.mls | 4 +- .../codegen/CurriedClassInheritance.mls | 8 +- .../src/test/mlscript/codegen/ErasedTypes.mls | 31 +- .../src/test/mlscript/codegen/Getters.mls | 2 +- .../shared/src/test/mlscript/codegen/Lazy.mls | 2 +- .../test/mlscript/codegen/PlainClasses.mls | 4 +- .../src/test/mlscript/codegen/While.mls | 8 +- .../src/test/mlscript/handlers/Debugging.mls | 2 +- .../mlscript/handlers/EffectInHandler.mls | 2 +- .../src/test/mlscript/handlers/Effects.mls | 2 +- .../test/mlscript/handlers/GeneratorStack.mls | 2 +- .../mlscript/handlers/MultiResumption.mls | 2 +- .../test/mlscript/handlers/NestedHandlers.mls | 4 +- .../mlscript/handlers/UserThreadsSafe.mls | 2 +- .../mlscript/handlers/UserThreadsUnsafe.mls | 2 +- .../test/mlscript/interop/Virtualization.mls | 4 +- .../shared/src/test/mlscript/lifter/Loops.mls | 3 +- .../src/test/mlscript/newres/BadApps.mls | 14 +- .../src/test/mlscript/newres/BareClasses.mls | 2 +- .../test/mlscript/newres/BuiltinFunction.mls | 2 +- .../mlscript/newres/ConstructorInstances.mls | 2 +- .../test/mlscript/newres/DeclaredTypes.mls | 63 +-- .../src/test/mlscript/newres/Dynamic.mls | 2 +- .../mlscript/newres/GetterReadEffects.mls | 17 +- .../mlscript/newres/InterfaceExposure.mls | 2 +- .../test/mlscript/newres/ObjectMethods.mls | 4 +- .../src/test/mlscript/newres/Overrides.mls | 421 ++++++++++++++++++ .../src/test/mlscript/newres/ParamClasses.mls | 2 +- .../mlscript/newres/PartialSignatures.mls | 4 +- .../src/test/mlscript/newres/Records.mls | 2 +- .../src/test/mlscript/newres/ShapeFixture.mls | 4 +- .../src/test/mlscript/newres/SpreadCalls.mls | 6 +- .../test/mlscript/newres/loose/Overrides.mls | 49 ++ .../test/mlscript/newres/loose/Targets.mls | 2 +- .../src/test/mlscript/newres/wasm/Super.mls | 99 ++++ .../test/mlscript/syntax/annotations/Call.mls | 2 +- .../ucs/patterns/ConjunctionPattern.mls | 2 +- hkmc2/shared/src/test/mlscript/wasm/Casts.mls | 13 +- .../test/mlscript/wasm/ClassInheritance.mls | 3 + .../src/test/mlscript/wasm/ClassMethods.mls | 4 +- .../src/test/mlscript/wasm/VirtualMethods.mls | 54 +-- 75 files changed, 1268 insertions(+), 250 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala create mode 100644 hkmc2/shared/src/test/mlscript/basics/Super.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/Overrides.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/loose/Overrides.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/wasm/Super.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 227dbcccab..f41687666e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -1015,6 +1015,7 @@ sealed abstract class Result extends Located, HasErasedType: case Value.This(sym) => s"this[${sym.showDbg}]" case Value.Lit(lit) => lit.idStr case Select(q, n) => s"Select(${q.showDbg}, ${n.showDbg})" + case SuperSelect(q, base, n) => s"SuperSelect(${q.showDbg}, ${base.showDbg}, ${n.showDbg})" case DynSelect(q, fld, arrayIdx) => s"DynSelect(${q.showDbg}, ${fld.showDbg}, $arrayIdx)" case Call(fun, argss) => s"Call(${fun.showDbg}, [${ argss.map(_.map(a => a.value.showDbg).mkString("[", ", ", "]")).mkString(", ")}])" @@ -1043,6 +1044,7 @@ sealed abstract class Result extends Located, HasErasedType: case _: Value => true case sel @ Select(q, n) => q.isPure && sel.symbol.exists(_.isPure) + case sel @ SuperSelect(q, base, _) => q.isPure && base.isPure && sel.symbol.isPure case c @ Call(fun, ass) if c.isKnownUnsaturatedCall => fun.isPure && ass.forall(_.forall(a => a.spread.isEmpty && a.value.isPure)) case Call(Value.SimpleRef(bs: BuiltinSymbol), ass) if bs.isPure => @@ -1059,6 +1061,7 @@ sealed abstract class Result extends Located, HasErasedType: case Call(fun, argss) => fun.subBlocks ::: argss.flatten.flatMap(_.value.subBlocks) case Instantiate(mut, cls, argss) => argss.flatten.flatMap(_.value.subBlocks) case Select(qual, name) => qual.subBlocks + case SuperSelect(qual, base, _) => qual.subBlocks ::: base.subBlocks case Lambda(params, body) => body :: Nil case Tuple(mut, elems) => elems.flatMap(_.value.subBlocks) case _ => Nil @@ -1068,6 +1071,7 @@ sealed abstract class Result extends Located, HasErasedType: case Instantiate(mut, cls, argss) => cls.freeVars ++ argss.flatten.flatMap(_.value.freeVars).toSet case Cast(value, _, _) => value.freeVars case Select(qual, name) => qual.freeVars + case SuperSelect(qual, base, _) => qual.freeVars ++ base.freeVars case Lambda(params, body) => body.freeVars -- params.paramSyms case Tuple(mut, elems) => elems.flatMap(_.value.freeVars).toSet case Record(mut, args) => @@ -1083,6 +1087,7 @@ sealed abstract class Result extends Located, HasErasedType: case Instantiate(mut, cls, argss) => cls.size + argss.iterator.flatten.map(_.value.size).sum case Cast(value, _, _) => value.size case Select(qual, name) => qual.size + case SuperSelect(qual, base, _) => qual.size + base.size case Lambda(params, body) => 1 + body.size case Tuple(mut, elems) => elems.iterator.map(_.value.size).sum case Record(mut, args) => args.iterator.map(arg => arg.idx.fold(0)(_.size) + arg.value.size).sum @@ -1128,6 +1133,7 @@ sealed abstract class Result extends Located, HasErasedType: case _ => N // * A resolved selection has the type of the member it refers to (e.g. `this.field`); an // * unresolved selection (dynamic field access) stays unknown. + case sel: SuperSelect => sel.symbol.erasedType case sel @ Select(_, _) => sel.symbol match case S(ts: TermSymbol) => ts.erasedType // * A class reference is the class object, so it stays unknown. @@ -1331,6 +1337,7 @@ sealed abstract class Path extends TrivialResult: def targetSymbol: Opt[DefinitionSymbol[?]] = this match case ref: Value.MemberRef => S(ref.disamb) case sel: Select => sel.symbol + case sel: SuperSelect => S(sel.symbol) case _ => N /** @@ -1340,6 +1347,14 @@ case class Select(qual: Path, name: Tree.Ident)(val symbol: Opt[DefinitionSymbol def withLoc(loc: Opt[Loc]): Select = if loc == toLoc then this else copy()(symbol, loc)(sanitize) def extraInfo(using DebugPrinter): Str = symbol.map(s => s"sym=${s.showAsPlain}").mkString +/** Direct lookup on the lexical parent's prototype, with the original receiver as `this`. + * `base` is captured when the class is defined, so an extends expression is never re-evaluated + * at a super call. Explicit operands preserve this meaning through lifting and inlining. + * For Wasm's static dispatch, `base` is a class reference and `symbol` names the called function. + */ +case class SuperSelect(qual: Path, base: Path, name: Tree.Ident)(val symbol: TermSymbol, val toLoc: Opt[Loc]) extends Path: + def withLoc(loc: Opt[Loc]): SuperSelect = if loc == toLoc then this else copy()(symbol, loc) + case class DynSelect(qual: Path, fld: Path, arrayIdx: Bool)(val toLoc: Opt[Loc]) extends Path: def withLoc(loc: Opt[Loc]): DynSelect = if loc == toLoc then this else copy()(loc) @@ -1411,6 +1426,7 @@ object TermSymbolPath: case selection: Select => selection.symbol match case S(sym: TermSymbol) => S(sym) case _ => N + case selection: SuperSelect => S(selection.symbol) case _ => N case class Arg(spread: Opt[SpreadKind], value: Path) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index 0f863beadc..cb27059c40 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -180,6 +180,12 @@ class BlockTransformer(subst: SymbolSubst): case p: Path => applyPath(p)(k) def applyPath(p: Path)(k: Path => Block): Block = p match + case p @ SuperSelect(qual, base, name) => + applyPath(qual): qual2 => + applyPath(base): base2 => + val sym2 = p.symbol.subst + k(if (qual2 is qual) && (base2 is base) && (sym2 is p.symbol) then p + else SuperSelect(qual2, base2, name)(sym2, p.toLoc)) case DynSelect(qual, fld, arrayIdx) => applyPath(qual): qual2 => applyPath(fld): fld2 => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala index ecfe9446b0..da0de4c039 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala @@ -69,6 +69,8 @@ class BlockTraverser: case p: Path => applyPath(p) def applyPath(p: Path): Unit = p match + case p @ SuperSelect(qual, base, _) => + applyPath(qual); applyPath(base); p.symbol.traverse case DynSelect(qual, fld, arrayIdx) => applyPath(qual); applyPath(fld) case p @ Select(qual, name) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala index 56324e3a0e..fd9a3bc9dd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala @@ -7,11 +7,12 @@ import hkmc2.utils.*, shorthands.* import semantics.* import semantics.Elaborator.{Ctx, State} import semantics.Term.* +import hkmc2.Message.MessageContext /** The capability to inspect completed resolution. Created only after elaboration/resolution; * executable lowering additionally requires this instance's whole-program pass to have finished. */ -final class Erasure private (using Config, Ctx, State): +final class Erasure private (using Config, Ctx, State)(using Raise): private given Erasure = this private var completed: Opt[Statement] = N @@ -107,6 +108,15 @@ final class Erasure private (using Config, Ctx, State): val visited = mutable.Set.empty[Identity[Statement]] val statements = mutable.ArrayBuffer.empty[Statement] val parameters = mutable.ArrayBuffer.empty[Param] + val parentConstructions = mutable.Set.empty[Identity[Statement]] + def parentHead(term: Term): Unit = if parentConstructions.add(Identity(term)) then term match + // Legacy resolution can expand a parent reference to an implicit constructor call. + // Follow only the class head; constructions in argument expressions are real allocations. + case ref: Resolvable if ref.expanded isnt ref => parentHead(ref.expanded) + case New(cls, _, _) => parentHead(cls) + case App(lhs, _) => parentHead(lhs) + case TyApp(lhs, _) => parentHead(lhs) + case _ => () def params(ps: ParamList): Unit = ps.foreach(parameters += _) // Handler continuations and rejected class rest parameters can survive as references after their // parameter list has been removed. The symbol retains the source declaration needed for erasure. @@ -124,7 +134,9 @@ final class Erasure private (using Config, Ctx, State): cls.auxParams.foreach: ps => params(ps) ps.subTerms.foreach(visit) - cls.ext.foreach(visit) + cls.ext.foreach: parent => + parentHead(parent) + visit(parent) cls match case pat: PatternDef => parameters ++= pat.parameters @@ -137,7 +149,8 @@ final class Erasure private (using Config, Ctx, State): visit(d.td) case Ref(sym: VarSymbol) => referencedParameter(sym) case SimpleRef(sym: VarSymbol) => referencedParameter(sym) - case New(_, _, refinement) => refinement.foreach(r => visit(r._2.blk)) + case New(_, _, refinement) => refinement.foreach: (symbol, body) => + symbol.defn.fold(visit(body.blk))(visit) case Rcd(_, stats) => stats.foreach(visit) case _ => () statement.subStatements.foreach(visit) @@ -148,6 +161,12 @@ final class Erasure private (using Config, Ctx, State): case _ => () visit(root) + def parentClass(parent: New): Opt[ClassLikeDef] = + parent.cls.resolvedSym.flatMap(_.asClsOrMod).flatMap(_.defn).orElse: + ClassValueShape.targetsOf(parent) match + case target :: Nil => S(ClassValueShape.definitionOf(target)) + case _ => N + // No erased representation is read until every interpretation has been validated and every // nominal parent header has been published, including forward and external declarations. statements.foreach: @@ -158,10 +177,69 @@ final class Erasure private (using Config, Ctx, State): case sym: ClassLikeSymbol if sym.irClassHeader.isEmpty => sym.irClassHeader = cls.ext match case N => S(ClassHeader(N)) - case S(parent) => parent.cls.resolvedSym.flatMap(_.asClsOrMod).map(p => ClassHeader(S(p))) + case S(parent) => parentClass(parent).flatMap(_.sym.asClsOrMod).map(p => ClassHeader(S(p))) case _ => () case _ => () + // Public members replace inherited names; private members retain their declaration identity. + // A subclass cannot implement a private declaration by introducing the same name. + type MemberKey = Either[TermSymbol, Str] + val effectiveMembers = mutable.Map.empty[ClassLikeDef, Map[MemberKey, TermSymbol]] + def checkConstruction(term: Term & PossiblyErroneous, cls: ClassDef): Unit = + if Inheritance.hasModifier(cls, syntax.Keyword.`abstract`) then + term.isErroneous = true + raise(ErrorReport( + msg"Cannot instantiate abstract class '${cls.sym.nme}'" -> term.toLoc :: + (msg"Abstract class defined here" -> cls.toLoc) :: Nil, source = Diagnostic.Source.Compilation)) + def members(cls: ClassLikeDef, visiting: Set[ClassLikeDef]): Map[MemberKey, TermSymbol] = + effectiveMembers.getOrElseUpdate(cls, + if visiting(cls) then + raise(ErrorReport(msg"Inheritance cycle involving '${cls.sym.nme}'" -> cls.toLoc :: Nil, + source = Diagnostic.Source.Compilation)) + Map.empty + else + val inherited = cls.ext.flatMap(parentClass) + .fold(Map.empty[MemberKey, TermSymbol])(members(_, visiting + cls)) + inherited ++ cls.body.members.toList.flatMap: (name, member) => + member.asTrm.map: symbol => + val key: MemberKey = if symbol.isPrivate then Left(symbol) else Right(name) + key -> symbol + ) + statements.foreach: + case td: TermDefinition => Inheritance.validateMember(td) + case cls: ClassLikeDef if !Inheritance.isExternal(cls) && + !Inheritance.hasModifier(cls, syntax.Keyword.`abstract`) => + val missing = members(cls, Set.empty).values.toList + .distinct.filter(_.defn.exists(Inheritance.isAbstract)) + .sortBy(_.nme) + if missing.nonEmpty then raise(ErrorReport( + msg"${cls.sym.describeKind.capitalize} '${cls.sym.nme}' has unimplemented members" -> cls.toLoc :: + missing.map(sym => msg"Member '${sym.nme}' requires an implementation" -> sym.toLoc), + source = Diagnostic.Source.Compilation)) + case nw: New if nw.rft.isEmpty && !parentConstructions(Identity(nw)) => + parentClass(nw).collect { case cls: ClassDef => cls }.foreach(checkConstruction(nw, _)) + case app: App if !parentConstructions(Identity(app)) => + // Constructor functions can flow through variables and parameters; their call shapes + // retain the declaration even when the callee has no single resolved symbol. + def constructed(event: ShapeEvent): Ls[ClassDef] = event match + case ActivatedShapeEvent(value, _) => constructed(value) + case MarkedShape(value, _) => constructed(value) + case ContextualShape(value, _) => constructed(value) + case shape: AppShape if shape.src is app => shape.applicationHead._1 match + case definition: DefnShape => definition.defn match + case td: TermDefinition => td.tsym match + case ctor: ClassCtorSymbol => ctor.associatedCls.defn.toList + case _ => Nil + case cls: ClassDef => cls :: Nil + case _ => Nil + case _ => Nil + case _ => Nil + val direct = app.lhs.resolvedSym.toList.flatMap: + case ctor: ClassCtorSymbol => ctor.associatedCls.defn.toList + case _ => Nil + (direct ::: app.shapes.toList.flatMap(constructed)).distinct.foreach(checkConstruction(app, _)) + case _ => () + parameters.foreach: p => if !p.sym.isErased then p.sym.erasedType = declaredType(p.sign, p.modulefulness) // Public fields have a BlockMemberSymbol; private/auxiliary constructor fields have a TermSymbol. @@ -182,7 +260,7 @@ final class Erasure private (using Config, Ctx, State): object Erasure: /** Prelude declarations use the same pass even though they have no executable program. */ - def apply(root: Statement)(using Config, Ctx, State): Erasure = + def apply(root: Statement)(using Config, Ctx, State)(using Raise): Erasure = val erasure = new Erasure erasure.run(root) erasure diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index ea7bc9afb8..d94606529a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -212,6 +212,15 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // search for unliftable classes and build the extends graph new BlockTraverser: this.applyScopedObject(s.obj) + override def applyPath(p: Path): Unit = p match + // Wasm's super operand identifies a static class, just like the operand of + // Instantiate. It does not escape as a first-class runtime class value. + case sel @ SuperSelect(qual, RefOfDefn(S(cls: ClassSymbol), _), _) + if config.target == CompilationTarget.Wasm => + applyPath(qual) + cls.traverse + sel.symbol.traverse + case _ => super.applyPath(p) override def applyCase(cse: Case): Unit = cse match case Case.Cls(cls: (ClassSymbol | ModuleOrObjectSymbol), _) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 8e22d7dd5a..1b69589547 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -90,6 +90,21 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val newResolution: Bool = config.language.useNewResolution val strictResolution: Bool = config.language.strictResolution + // Methods are lowered before their class's extends expression. Reserve the captured parent + // here and initialize it at the class definition, in the surrounding lexical scope. + private val superBases = scala.collection.mutable.Map.empty[InnerSymbol, TempSymbol] + private def superBase(owner: InnerSymbol): TempSymbol = + superBases.getOrElseUpdate(owner, TempSymbol(N, erasedType = N, "superBase")) + private def captureSuperBase(owner: InnerSymbol, parent: Path)(body: => Block)(using LoweringCtx): Block = + superBases.get(owner) match + case S(sym) => + // classOf evaluates computed class values into temporaries. The remaining path is + // stable across this assignment and the immediately following class definition. + softAssert(parent.isPure, "Superclass operand must be evaluated before it is captured") + loweringCtx.collectScopedSym(sym) + Assign(sym, parent, body) + case N => body + extension (t: Term) def instantiated = t match case r: Resolvable => @@ -386,7 +401,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case N => val cfgOverride = defn.extraAnnotations.collectFirst: case Annot.Config(modify) => modify(config) - Define( + captureSuperBase(defn.sym, + Select(State.globalThisSymbol.asThis, Tree.Ident("Object"))(S(ctx.builtins.Object), defn.toLoc)(false))(Define( ClsLikeDefn(defn.owner, defn.sym, defn.bsym, defn.ctorSym, defn.kind, defn.paramsOpt, defn.auxParams, N, mtds, privateFlds, @@ -396,20 +412,20 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu mod, bufferable, )(cfgOverride, defn.annotations), - blockImpl(stats, res)) + blockImpl(stats, res))) case S(ext) => assert(k isnt syntax.Mod) // modules can't extend things and can't have super calls val cfgOverride = defn.extraAnnotations.collectFirst: case Annot.Config(modify) => modify(config) classOf(ext.cls, ext): (clsp, params) => val pctor = inScopedBlock(parentConstructor(params, ext.args, ext.toLoc)) - Define( + captureSuperBase(defn.sym, clsp)(Define( ClsLikeDefn( defn.owner, defn.sym, defn.bsym, defn.ctorSym, defn.kind, defn.paramsOpt, defn.auxParams, S(clsp), mtds, privateFlds, publicFlds, pctor, ctor, mod, bufferable, )(cfgOverride, defn.annotations), blockImpl(stats, res) - ) + )) case td: TypeDef => // * Type definitions are erased blockImpl(stats, res) @@ -691,7 +707,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu ErrorReport(msg"This selection has both tuple and nominal member targets" -> sel.toLoc :: Nil, source = Diagnostic.Source.Compilation) else if sel.hasDynamicTarget || sel.tupleIndex.nonEmpty then k(prefix, memberIdent(id, N), N) - else sel.resolvedTargets.distinct match + else Inheritance.commonMember(sel.resolvedTargets).fold(sel.resolvedTargets.distinct)(_ :: Nil) match case Nil => fail: ErrorReport(msg"This selection of member '${id.name}' has no resolved target" -> sel.toLoc :: Nil, source = Diagnostic.Source.Compilation) @@ -723,6 +739,26 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case S(index) => DynSelect(prefix, Value.Lit(Tree.IntLit(BigInt(index)))(N), true)(sel.toLoc) case N => Select(prefix, name)(target, sel.toLoc)(false) + private def superReference(sup: st.Super)(k: Path => Block)(using LoweringCtx): Block = + if sup.isErroneous then compError else sup.resolvedTargets.distinct match + case (target: TermSymbol) :: Nil if !target.isPrivate && target.defn.exists(d => !Inheritance.isAbstract(d)) => + subTerm_nonTail(sup.receiver): receiver => + // Fields live on the receiver; only methods/getters have a base implementation + // on the prototype. In particular Reflect.get(base.prototype, ...) cannot read fields. + if target.k is syntax.Fun then + // Wasm dispatches to a statically named function and only supports static parents; + // its class operand, like an Instantiate operand, need not be a runtime class value. + val base = if config.target == CompilationTarget.Wasm then + val owner = target.owner.get + owner.asBlkMember.get.asMemberRef(owner.asDefnSym) + else superBase(sup.owner).asSimpleRef + k(SuperSelect(receiver, base, target.id)(target, sup.toLoc)) + else k(Select(receiver, target.id)(S(target), sup.toLoc)(false)) + case targets => fail(ErrorReport( + msg"Super reference '${sup.id.name}' requires one inherited implementation" -> sup.toLoc :: + targets.map(target => msg"Candidate '${target.nme}' is declared here" -> target.toLoc), + source = Diagnostic.Source.Compilation)) + /** A projection must identify its class as well as its member: different * classes can inherit the same definition. Captures do not disambiguate classes. */ private def checkProjection(sel: NewSel): Bool = sel.cls match @@ -1071,7 +1107,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu msg"Unexpected arguments for builtin symbol '${sym.nme}'" -> arg.toLoc :: Nil, S(arg), source = Diagnostic.Source.Compilation) case st.TyApp(f, ts) => term(f)(k) // * Type arguments are erased - case st.App(f, arg) => + case app @ st.App(f, arg) => + if app.isErroneous then return compError // Collect chains of App nodes: `f(a)(b)(c)` → (f, [a, b, c]) // This allows lowering curried calls as a single `Call` with multiple arg lists. @@ -1164,6 +1201,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu instantiated match // * Due to whacky JS semantics, we need to make sure that selections leading to a call // * are preserved in the call and not moved to a temporary variable. + case sup: st.Super => superReference(sup)(conclude) case sel: NewSel if sel.isClassValue => classOf(sel.prefix, sel)((p, _) => conclude(p)) case sel: NewSel => newSelection(sel): (prefix, name, target) => subTerm_nonTail(prefix): p => @@ -1326,6 +1364,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case whltrm: st.SynthWhile => ucs.Normalization(this)(whltrm)(k) case sel: NewSel if sel.isClassValue => classOf(sel.prefix, sel)((p, _) => k(p)) + case sup: st.Super => superReference(sup)(k) case sel: NewSel => newSelection(sel): (prefix, name, target) => if sel.tupleIndex.nonEmpty then subTerm_nonTail(prefix)(p => k(selectionPath(sel, p, name, target))) else setupNamedSelection(prefix, name, target, sel.toLoc)(k) @@ -1375,14 +1414,14 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu rft match case N => lowerMultiInstantiate(mut, sr, params, as, annots, trm.toLoc)(k) case S((isym, rft)) => - val sym = new BlockMemberSymbol(isym.name, Nil) + val sym = isym.defn.get.bsym loweringCtx.collectScopedSym(sym) val (mtds, publicFlds, privateFlds, ctor) = gatherMembers(rft) val pctor = parentConstructor(params, as, nw.toLoc) val clsDef = ClsLikeDefn(N, isym, sym, N, syntax.Cls, N, Nil, S(sr), mtds, privateFlds, publicFlds, pctor, ctor, N, N)(N, Nil) val inner = new New(sym.ref().resolved(isym), Nil, N)(FlowSymbol.neww(), N) - Define(clsDef, term_nonTail(if mut then Mut(inner) else inner)(k)) + captureSuperBase(isym, sr)(Define(clsDef, term_nonTail(if mut then Mut(inner) else inner)(k))) case Try(sub, finallyDo) => val l = loweringCtx.registerTempSymbol(S(sub), erasedType = N) @@ -1803,6 +1842,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case Annot.Modifier(syntax.Keyword.`public` | syntax.Keyword.`private` | syntax.Keyword.`virtual`) => () case Annot.Modifier(syntax.Keyword("staged")) => () case Annot.Pure() => () + case Annot.Modifier(syntax.Keyword.`open` | syntax.Keyword.`override`) => () case a: Annot.Affine => target match case TermDefinition(k = syntax.Fun) => () case _ => warn(a) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala index 24383617d8..32773e3c7c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala @@ -209,6 +209,8 @@ class Printer(using Config, Ctx, Raise, ShowCfg, State, SymbolPrinter): case Value.Lit(lit) => doc"${lit.idStr}" def print(path: Path)(using Scope): Document = path match + case sel @ SuperSelect(qual, base, name) => + doc"super[${print(base)}](${print(qual)}).${showMemberSymbol(name.name, S(sel.symbol))}" case sel @ Select(qual, name) => val docQual = print(qual) doc"${docQual}.${showMemberSymbol(name.name, sel.symbol)}" diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala index e266a55503..984dc75eee 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ReflectionInstrumenter.scala @@ -191,6 +191,9 @@ class ReflectionInstrumenter(using State, Raise, Ctx) extends BlockTransformer(n transformResult(value): v => blockCtor("Symbol", Ls(toValue(target.describe)), "target"): t => blockCtor("Cast", Ls(v, t, toValue(check)), "cast")(k) + case _: SuperSelect => + raise(ErrorReport(msg"Super selection is not supported in staged modules." -> p.toLoc :: Nil)) + End() case _: Value.This => raise(ErrorReport(msg"Value.This not supported in staged module." -> p.toLoc :: Nil)) End() diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index b923b0e69e..929ddc6407 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -157,7 +157,12 @@ private class SymbolRefresherInternal(m: MutMap[Symbol, Symbol])(using State) ex // We have a pretty weird setup here, where we store a mutable state inside the SymbolRefresher // We must initialize the SymbolRefresher by walking before applyBlock def apply(b: Block) = + val existing = m.keySet.toSet SymbolRefresherWalker(m).applyBlock(b) + m.foreach: + case (source: MemberSymbol, target: MemberSymbol) if !existing(source) => + target.copyOverrideLinksFrom(source)(using SymbolRefresher.initSymbolSubst(m)) + case _ => () applyBlock(b) // Although the types created during walking can always be symbol substituted, the user may pass in extra symbol that map across different types diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala index 6941682f6d..f63c290a8f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala @@ -154,6 +154,7 @@ object MemberRefTo: locally: p match case Select(qual, _) => S(qual) + case SuperSelect(qual, _, _) => S(qual) case _ => N object CtorProducer: @@ -597,7 +598,7 @@ class FlowPreAnalyzer(val pgrm: Program)(using if ctxTracker.isEnclosingMatchScrutSym(disamb) => applyValueMemberRef(v, recordAffinity = false) case _ => applyPath(qual) - case p: Select => + case p: (Select | SuperSelect) => super.applyPath(p) case c: Cast => applyResult(c.value) case v: Value => applyValue(v) @@ -1060,6 +1061,10 @@ class FlowConstraintsCollector( case Some(fScheme) => fScheme.instantiate(refSite.uid, f) case None => generatedVars(f) + case s @ SuperSelect(qual, base, _) => + cc.constrain(processResult(qual), UnknownCons) + cc.constrain(processResult(base), UnknownCons) + generatedVars(s.symbol) case s@Select(qual, name) => cc.constrain(processResult(qual), UnknownCons) s.symbol.fold(UnknownProd): selSym => 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 a1151971f2..c205df5952 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -340,6 +340,14 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case Call(s @ Select(_, Elaborator.ctx.builtins.BuiltInOpIdent(jsOp)), (lhs :: rhs :: Nil) :: Nil) => val res = doc"${operand(lhs)} ${jsOp} ${operand(rhs)}" if needsParens(jsOp) then doc"(${res})" else res + case c @ Call(fun: SuperSelect, argss) => + val first = argss.head.map(argument) + val base = doc"${result(fun)}.call(${(result(fun.qual) :: first).mkDocument(", ")})" + val calls = argss.tail.foldLeft(base): (acc, args) => + doc"${acc}(${args.map(argument).mkDocument(", ")})" + if c.metadata.isMlsFun then + if checkMLsCalls then doc"$runtimeVar.checkCall($calls)" else calls + else doc"$runtimeVar.safeCall($calls)" case c @ Call(fun, argss) => val base = subexpression(fun) val calls = argss.foldLeft(base): (acc, args) => @@ -358,6 +366,8 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: // lexical-`this` behavior of the Lambda IR. doc"(function* ($params) ${ braced(bodyDoc) }).bind(this)" else doc"($params) => ${ braced(bodyDoc) }" + case s @ SuperSelect(qual, base, id) => + doc"Reflect.get(${resultQual(base)}.prototype, ${makeStringLiteral(id.name)}, ${result(qual)})" case s @ Select(qual, id) => val checkCurrentSelection = checkSelections && s.sanitize val dotClass = s.symbol match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala index 6d9a1a40dc..8fc57aa879 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Ctx.scala @@ -9,8 +9,8 @@ import hkmc2.utils.* import document.* import document.Document import semantics.{ - BlockMemberSymbol, ClassSymbol, Elaborator, - InnerSymbol, LabelSymbol, LocalVarSymbol, ModuleOrObjectSymbol, ParamList, Symbol, TempSymbol, + BlockMemberSymbol, ClassSymbol, ClassLikeSymbol, ClassCtorSymbol, Elaborator, + InnerSymbol, LabelSymbol, LocalVarSymbol, ModuleOrObjectSymbol, ParamList, Symbol, TempSymbol, TermSymbol, }, Elaborator.State import text.Param as WasmParam @@ -43,8 +43,18 @@ object SessionBinding: */ def referencedSymbols(block: Block)(using Elaborator.Ctx, State): Ls[Symbol] = val symbols = LinkedHashSet.empty[Symbol] + val visited = scala.collection.mutable.Set.empty[Symbol] new BlockTraverser: - override def applySymbol(sym: Symbol): Unit = symbols += sym + override def applySymbol(sym: Symbol): Unit = if visited.add(sym) then + // An imported subclass layout refers to its ancestors even when no source expression + // names them. Parents must precede children in Wasm's type declaration order. + sym match + case cls: ClassLikeSymbol => cls.irClassHeader.flatMap(_.parent).foreach(applySymbol) + case ctor: ClassCtorSymbol => applySymbol(ctor.associatedCls) + case member: BlockMemberSymbol => member.asClsOrMod.foreach(applySymbol) + case member: TermSymbol => member.owner.foreach(applySymbol) + case _ => () + symbols += sym override def applyValue(value: Value): Unit = value match case Value.MemberRef(bms, disamb) => applySymbol(ExternSymbol.forReference(bms, disamb)) case _ => super.applyValue(value) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala index 049b2d588c..89a8d75746 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala @@ -668,24 +668,24 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui /** Returns the elaborated source methods for this class. */ private def semanticMethodDefs(defn: ClsLikeDefn)(using Raise): List[TermDefinition] = - semanticClassDef(defn).body.methods.filter(_.body.nonEmpty) + semanticClassDef(defn).body.methods /** Resolves the exact overridden parent method symbol for `methodDef`, if any. */ private def overriddenParentMethodSym( defn: ClsLikeDefn, methodDef: TermDefinition, )(using Raise): Opt[BlockMemberSymbol] = - resolveParentSym(defn).flatMap(_.asClsOrMod.flatMap(_.defn)) match - case S(parentDef: hkmc2.semantics.ClassLikeDef) => - parentDef.body.members.get(methodDef.sym.nme).flatMap(_.asTrm.flatMap(_.defn)) match - case S(parentMethodDef: TermDefinition) if parentMethodDef.k is syntax.Fun => S(parentMethodDef.sym) - case _ => N - case _ => N + def lookup(parent: ClassLikeSymbol, seen: Set[ClassLikeSymbol]): Opt[BlockMemberSymbol] = + if seen(parent) then N else + parent.defn.flatMap(_.body.members.get(methodDef.sym.nme)) match + case S(member) => member.asTrm.filter(_.k is syntax.Fun).flatMap(_.bms) + case N => parent.irClassHeader.flatMap(_.parent).flatMap(_.asClsOrMod).flatMap(lookup(_, seen + parent)) + resolveParentSym(defn).flatMap(_.asClsOrMod).flatMap(lookup(_, Set.empty)) /** True when a method introduces a new virtual slot at its declaring class if not already inherited. */ private def declaresVirtualSlot(methodDef: TermDefinition): Bool = - methodDef.annotations.exists: - case Annot.Modifier(syntax.Keyword.`virtual`) => true + methodDef.body.isEmpty || methodDef.annotations.exists: + case Annot.Modifier(syntax.Keyword.`virtual` | syntax.Keyword.`open`) => true case _ => false /** The declared Wasm parameter types of `methodDef`, excluding `this`. */ @@ -713,6 +713,9 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui }`, but the method it overrides declares `${expected.toWat.mkString()}`" -> methodDef.sym.toLoc), source = Diagnostic.Source.Compilation, )) + if declaredParamTypes(methodDef).size != slotInfo.paramTypes.size then + raise(ErrorReport(msg"Overriding method '${methodDef.sym.nme}' must preserve the number of parameters" -> + methodDef.sym.toLoc :: Nil, source = Diagnostic.Source.Compilation)) declaredParamTypes(methodDef).zip(slotInfo.paramTypes).zipWithIndex.foreach: case ((actual, expected), idx) => if !conformsToSlotType(actual, expected) then report(s"parameter ${idx + 1}", actual, expected) @@ -770,13 +773,23 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui case other => lastWords(s"Parent type must be a struct, found ${other.toWat.mkString()}") val classFields = ctx.elabCtx.givenIn: - (defn.publicFields.map(_._2) ++ defn.privateFields).map: f => + (defn.publicFields.map(_._2) ++ defn.privateFields).flatMap: f => // Reference fields need to be nullable so that `struct.new_default` has a valid value to write val fieldType: ValType = f.erasedType.flatMap(_.wasmType) match case S(ty: RefType) => ty.copy(nullable = true) case S(ty) => ty case N => RefType.anyref - f -> Field(fieldType, mutable = true, id = f.nme) + inheritedFields.collectFirst: + case (sym: TermSymbol, field) if !sym.isPrivate && !f.isPrivate && sym.nme == f.nme => field + match + case S(field) => + // Overrides denote the same public property, including abstract fields. Reuse its + // storage so a selection through the base symbol sees the overriding initializer. + if !conformsToSlotType(fieldType, field.ty) then raise(ErrorReport( + msg"Overriding field '${f.nme}' must preserve its inherited storage type" -> f.toLoc :: Nil, + source = Diagnostic.Source.Compilation)) + Nil + case N => (f -> Field(fieldType, mutable = true, id = f.nme)) :: Nil val allFields = inheritedFields ++ classFields val runtimeTag = ctx.getFreshObjectTag() @@ -1214,10 +1227,11 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui i32.const(tagValue), parentTypeInfo, ) ++ vtSlots.map: method => - ref.func( - ctx.getFunc_!(method.sym), - RefType(ctx.getFuncTypeUse_!(method.sym).typeIdx, nullable = false), - ) + // Abstract classes retain the slot and its calling convention without an implementation. + // Erasure rejects constructing them; each concrete subclass must fill every such slot. + ctx.getFunc(method.sym) match + case S(func) => ref.func(func, RefType(ctx.getFuncTypeUse_!(method.sym).typeIdx, nullable = false)) + case N => ref.`null`(virtualMethodFuncType(method.owner, method.paramTypes, method.resultType)) val globalInfo = GlobalInfo( globalType = GlobalType(RefType(typeInfoTypeIdx, nullable = false), mutable = false), init = struct.`new`(typeInfoTypeIdx, initFields), @@ -1630,31 +1644,40 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui case ts: TermSymbol => ts.owner.flatMap(_.asBlkMember) case ms: MemberSymbol => ms.asTrm.flatMap(_.owner.flatMap(_.asBlkMember)) - def fieldSelect(thisSym: BlockMemberSymbol, sym: DefinitionSymbol[?])(using Ctx, Raise): FieldIdx = + private def classField(thisSym: BlockMemberSymbol, sym: DefinitionSymbol[?])(using Ctx, Raise): Field = val structInfo = ctx.getTypeInfo_!(thisSym) - val symToField = structInfo.compType match - case ty: StructType => ty.fieldsBySym + val field = structInfo.compType match + case ty: StructType => ty.fieldsBySym.get(sym).orElse: + // A subclass symbol can name an inherited public slot. Resolve the alias from the + // immutable layout, which also works for class layouts imported from an earlier session. + sym.asTrm.filterNot(_.isPrivate).flatMap: target => + ty.fields.collectFirst: + case (base: TermSymbol, field) if !base.isPrivate && base.nme == target.nme => field case _ => lastWords(s"Cannot select field from non-struct type: ${structInfo.compType.toWat.mkString()}") - val fieldIdx = symToField.get(sym).fold(lastWords( + field.getOrElse(lastWords( s"Missing field `${sym.toString}` in struct `${thisSym.toString}` with type `${structInfo.toWat.mkString()}`", - )): field => - field.id - FieldIdx(SymIdx(fieldIdx)) + )) + end classField + + def fieldSelect(thisSym: BlockMemberSymbol, sym: DefinitionSymbol[?])(using Ctx, Raise): FieldIdx = + FieldIdx(SymIdx(classField(thisSym, sym).id)) end fieldSelect /** Resolves `sym` to a predeclared class method symbol, if any. */ private def predeclaredClassMethodSym(sym: DefinitionSymbol[?]): Opt[BlockMemberSymbol] = sym.asBlkMember.filter: methodSym => - methodSym.asTrm.exists(_.owner.exists(_.asCls.isDefined)) && ctx.getFunc(methodSym).nonEmpty + methodSym.asTrm.exists(_.owner.exists(_.asCls.isDefined)) && + (ctx.getFunc(methodSym).nonEmpty || fieldOwner(methodSym).flatMap(ctx.getVirtualTable).exists(_.slotOf.contains(methodSym))) /** Lowers a class method call, using virtual dispatch only when the selected owner class has a virtual slot. */ private def lowerClassMethodCall( qual: Path, methodSym: BlockMemberSymbol, args: Seq[Arg], + virtual: Bool, )(using FunctionCtx, Raise, SessionExportCtx): Ls[Expr] = val ownerCls = fieldOwner(methodSym).get - ctx.getVirtualTable(ownerCls).flatMap(vt => vt.slotOf.get(methodSym).map(slot => (vt, slot))) match + ctx.getVirtualTable(ownerCls).filter(_ => virtual).flatMap(vt => vt.slotOf.get(methodSym).map(slot => (vt, slot))) match case S((vt, slot)) => val baseSym = vt.slots(slot).owner val paramTypes = vt.slots(slot).paramTypes @@ -1770,7 +1793,16 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui ) val args = argss.flatten val methodSym = sel.symbol.flatMap(predeclaredClassMethodSym).get - lowerClassMethodCall(qual, methodSym, args).mergeAsBlock_! + lowerClassMethodCall(qual, methodSym, args, virtual = true).mergeAsBlock_! + + case c @ Call(sel: SuperSelect, argss) => + if argss.length != 1 then errExpr(Ls(msg"Super calls with multiple parameter lists are not supported by WASM" -> c.toLoc)) + else lowerClassMethodCall(sel.qual, sel.symbol.bms.get, argss.head, virtual = false).mergeAsBlock_! + + case sel: SuperSelect => + if sel.symbol.defn.exists(_.params.isEmpty) then + lowerClassMethodCall(sel.qual, sel.symbol.bms.get, Nil, virtual = false).mergeAsBlock_! + else errExpr(Ls(msg"Super method values are not supported by WASM" -> sel.toLoc)) case c @ Call(fun, argss) => if argss.length > 1 then @@ -1890,7 +1922,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui val methodSym = predeclaredClassMethodSym(selSym).get methodSym.asTrm.flatMap(_.defn) match case S(defn: TermDefinition) if defn.params.isEmpty => - lowerClassMethodCall(qual, methodSym, Nil).mergeAsBlock_! + lowerClassMethodCall(qual, methodSym, Nil, virtual = true).mergeAsBlock_! case _ => errExpr( Ls( @@ -1912,17 +1944,19 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui val fieldidx = fieldSelect(selCls, selSym) val structInfo = ctx.getTypeInfo_!(selCls) val fieldTy = structInfo.compType match - case st: StructType => st.fieldsBySym(selSym).ty + case st: StructType => classField(selCls, selSym).ty case other => lastWords(s"Expected struct type for $selCls, found ${other.toWat.mkString()}") val getExpr = struct.get( fieldidx, ref = castConserve(qualRes, RefType(ctx.getType_!(selCls), nullable = false)), ty = fieldTy, ) - fieldTy match - case rt: RefType if rt.nullable && rt.heapType != HeapType.Any => - castConserve(getExpr, rt.copy(nullable = false)) - case _ => getExpr + selSym.asTrm.flatMap(_.erasedType).flatMap(_.wasmType) match + case S(declared) => castConserve(getExpr, declared) + case N => fieldTy match + case rt: RefType if rt.nullable && rt.heapType != HeapType.Any => + castConserve(getExpr, rt.copy(nullable = false)) + case _ => getExpr case N => errExpr( Ls( @@ -2140,7 +2174,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui val objRef = castConserve(lhsExpr, RefType(ctx.getType_!(selCls), nullable = false)) val structInfo = ctx.getTypeInfo_!(selCls) val fieldType = structInfo.compType match - case st: StructType => st.fieldsBySym(fieldSym).ty + case st: StructType => classField(selCls, fieldSym).ty case other => lastWords(s"Expected struct type for $selCls, found ${other.toWat.mkString()}") val rhsCasted = fieldType match case rt: RefType => castConserve(rhsExpr, rt) @@ -2208,7 +2242,7 @@ class WatBuilder(private val ctx: Ctx)(using TraceLogger, State) extends CodeBui val fieldIdx = fieldSelect(ownerBlkMem, tsym) val structInfo = ctx.getTypeInfo_!(ownerBlkMem) val fieldType = structInfo.compType match - case st: StructType => st.fieldsBySym(tsym).ty + case st: StructType => classField(ownerBlkMem, tsym).ty case other => lastWords(s"Expected struct type for $ownerBlkMem, found ${other.toWat.mkString()}") val pCasted = castToValType(p, fieldType) val assignInstr = struct.set( diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 6d45a4647c..5b351d99f7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -778,6 +778,8 @@ extends Importer: | Keyword.`data` | Keyword.`staged` | Keyword.`virtual` + | Keyword.`open` + | Keyword.`override` | Keyword.`public` | Keyword.`private` )) => S(Annot.Modifier(kw)(tree.toLoc)) @@ -1262,6 +1264,18 @@ extends Importer: def mkNew(cls: Term, args: Ls[Term], rft: Opt[ClassSymbol -> ObjBody])(typ: Opt[typing.Type]): Term.New = val res = new Term.New(cls, args, rft)(FlowSymbol.neww(), typ).withLocOf(tree) + rft.foreach: (symbol, body) => + // An anonymous refinement is a concrete subclass too. Publish its declaration + // so override lookup, super references and implementation checks use the same graph. + // The written class and arguments occur outside the anonymous class's scope. + // Its synthetic extends clause crosses that boundary just like a named class. + def captured(term: Term): Term = if newResolution then Term.Capture(term, symbol) else term + val parent = new Term.New(captured(cls), args.map(captured), N)(FlowSymbol.neww(), typ).withLocOf(tree) + if newResolution then resolveNew(parent) + val definition = ClassDef.Plain(ctx.getOuter, syntax.Cls, symbol, + BlockMemberSymbol(symbol.nme, Nil), Nil, S(parent), body, N, Nil, Nil, N)(tree.toLoc) + symbol.defn = S(definition) + registerInheritance(definition) if newResolution then resolveNew(res) res @@ -1417,6 +1431,9 @@ extends Importer: raise: ErrorReport(msg"Cannot use 'this' outside of an object scope" -> tree.toLoc :: Nil) error + case Ident("super") => + raise(ErrorReport(msg"'super' must be followed by a member selection" -> tree.toLoc :: Nil)) + error case id @ Ident(name) => ident(id, interp).map(interpretRef(_, interp)).getOrElse: raise(ErrorReport(msg"Name not found: $name" -> id.toLoc :: Nil)) error @@ -1624,6 +1641,15 @@ extends Importer: mkNonLocalContinueInvocation(binding, nme) case LabelLookup.NotFound => elaborateSelection(sel) + case sel @ Sel(id @ Ident("super"), nme) => + (ctx.getOuter, ctx.getReceiver) match + case (S(owner: (ClassSymbol | ModuleOrObjectSymbol)), S(receiver)) => + val res = new Term.Super(receiver.ref(id), owner, nme)(FlowSymbol.sel(nme.name)).withLocOf(sel) + resolveSuper(res) + interpretRef(res, interp) + case _ => + raise(ErrorReport(msg"Cannot use 'super' outside of an object scope" -> sel.toLoc :: Nil)) + error case sel @ Sel(pre, nme) => elaborateSelection(sel) case MemberProj(ct, nme) => @@ -2669,6 +2695,7 @@ extends Importer: bms.symbols.foreach: case sym: (ClassLikeSymbol & InnerSymbol) => sym.defn.foreach: d => // erroneous code may not have a defn even after the BMS is completed + registerInheritance(d) // A declaration supplies no fresh function object to hold a companion. // Generated constructor functions also have no source body, but lowering creates them. // Foreign class-like declarations produce no companion initialization at all. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala new file mode 100644 index 0000000000..4a1a32a740 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala @@ -0,0 +1,75 @@ +package hkmc2 +package semantics + +import scala.collection.mutable +import hkmc2.utils.*, shorthands.* +import syntax.Keyword +import hkmc2.Message.MessageContext +import codegen.Erasure + +/** Declaration edges are separate from callable shapes: selecting a base declaration must + * not replace the implementation flow inferred for an ordinary virtual selection. + */ +private[semantics] final class OverrideLinks extends Host[TermSymbol]: + def showDbg(using DebugPrinter): Str = "overridden members" + def members(using Erasure): Ls[TermSymbol] = shapes.toList + def copyFrom(source: OverrideLinks)(using SymbolSubst): Unit = + assert(shapes.isEmpty, "Override links must be copied onto a fresh symbol") + shapes ++= source.shapes.iterator.map(_.subst) + +object Inheritance: + def hasModifier(d: Definition, keyword: Keyword.Modifier): Bool = d.annotations.exists: + case Annot.Modifier(kw) => kw == keyword + case _ => false + + def isAbstract(d: TermDefinition): Bool = + d.body.isEmpty && !isExternal(d) && !d.tsym.isInstanceOf[ClassCtorSymbol] + + /** A foreign declaration also supplies the implementation of its nested declarations. */ + def isExternal(d: Definition): Bool = d.hasDeclareModifier.nonEmpty || (d match + case td: TermDefinition => td.owner.exists(_.asDefnSym.defn.exists(isExternal)) + case cd: ClassLikeDef => cd.owner.exists(_.asDefnSym.defn.exists(isExternal)) + case _ => false) + + def isOpen(d: TermDefinition): Bool = d.body.isEmpty || hasModifier(d, Keyword.`open`) + + /** An ancestor may itself be a target (one receiver overrides a member, another inherits it). + * Discard less-specific common ancestors, so a chain of overrides is not an ambiguity. + */ + def commonMember(targets: Ls[DefinitionSymbol[?]])(using Erasure): Opt[DefinitionSymbol[?]] = + def ancestors(sym: DefinitionSymbol[?]): Set[DefinitionSymbol[?]] = + val seen = mutable.Set.empty[DefinitionSymbol[?]] + def visit(current: DefinitionSymbol[?]): Unit = if seen.add(current) then + current.overriddenMembers.foreach(visit) + visit(sym) + seen.toSet + targets.distinct match + case Nil => N + case target :: Nil => S(target) + case first :: rest => + val common = rest.foldLeft(ancestors(first))((acc, sym) => acc intersect ancestors(sym)) + common.filterNot(base => common.exists(other => (other isnt base) && ancestors(other)(base))).toList match + case target :: Nil => S(target) + case _ => N + + /** Check only completed edges; an empty list during resolution can still acquire a parent. */ + def validateMember(td: TermDefinition)(using Erasure, Raise): Unit = + val bases = td.tsym.overriddenMembers + def fail(message: Message, base: Opt[TermSymbol]): Unit = raise(ErrorReport( + (message -> td.toLoc) :: base.toList.map(sym => msg"Inherited member '${sym.nme}' is defined here" -> sym.toLoc), + source = Diagnostic.Source.Compilation)) + if hasModifier(td, Keyword.`override`) && bases.isEmpty then + fail(msg"Member '${td.tsym.nme}' is marked 'override' but overrides no inherited member", N) + bases.foreach: base => + base.defn.foreach: inherited => + if !isOpen(inherited) then + fail(msg"Cannot override non-open member '${base.nme}'", S(base)) + else if inherited.body.nonEmpty && !hasModifier(td, Keyword.`override`) then + fail(msg"Overriding member '${base.nme}' requires the 'override' modifier", S(base)) + if td.tsym.isPrivate then + fail(msg"Overriding member '${base.nme}' cannot be private", S(base)) + // Getter evaluation and method selection have different effects and calling conventions. + // A shared target symbol must never cause a getter read to be treated as a pure method value. + if !isExternal(td) && (td.k != inherited.k || + td.params.nonEmpty != inherited.params.nonEmpty) then + fail(msg"Overriding member '${base.nme}' must preserve whether the member is a field, getter, or method", S(base)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index ad64215067..c1dcd1ac6d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -2704,6 +2704,28 @@ class NewResolver: selected(ref) , reject) + /** Super references expose every inherited candidate to inference, but never dispatch + * through the overriding member. The parent shape retains generic arguments and scope marks. + */ + def resolveSuper(sup: Super)(using NewResolverState): Unit = + def receive(info: MemberLookup, instances: TypeSubstitution)(using NewResolverState): Unit = info match + case MemberLookup.Contextual(source, substitution) => receive(source, instances.withOverrides(substitution)) + case MemberLookup.Found(member, marks) => publishMember(sup, member, sup.resSym, marks, instances) + case MemberLookup.Declared(member, bindings, marks, annotation, positive) => + publishDeclared(sup, member, sup.resSym, bindings, marks, annotation, positive, instances) + case _ => + rstate.markError(sup) + resolError(sup, msg"No inherited member '${sup.id.name}' is available through 'super'" -> sup.toLoc :: Nil) + def completed(defn: ClassLikeDef)(using NewResolverState): Unit = + listenInheritedMember(defn, sup.id.name): info => + def captures(term: Term): Ls[Marks] = term match + case Capture(base, thru) => EntryMark(ResolutionBoundary(thru), N, NoMarks) :: captures(base) + case _ => Nil + receive(info.withMarks(captures(sup.receiver)), TypeSubstitution.empty) + sup.owner.asDefnSym.defn match + case S(defn) => completed(defn) + case N => sup.owner.asDefnSym.defnListeners += completed + def newSel(sel: NewSel)(using NewResolverState): Unit = log(s"newSel? sel = ${sel.showDbg}") def member(info: MemberLookup, description: Message, diagnostic: Message => Ls[(Message, Opt[Loc])], @@ -2831,12 +2853,13 @@ class NewResolver: // constructor value. In particular, a captured constructor already carries // that exit; adding a second one here would duplicate its instance boundary. listenClass(nw.cls, recordTargets = true)(ref => - val cd = ref.definition + val cd = nw.rft.flatMap(_._1.defn).getOrElse(ref.definition) // Constructor arity is compiled from these shapes even when the runtime // operand is a variable with no resolved class symbol of its own. assert(rstate.canResolve(nw) || ClassValueShape.targetsOf(nw).contains(ref.target), "Inference changed a completed constructor target") - val marks = ref.marks + val marks = nw.rft.fold(ref.marks): (symbol, _) => + ExitMark(ResolutionBoundary(symbol), S(nw.resSym), NoMarks) :: Nil val callerInstances = rstate.instances listenExt(cd, extsh => val dsh = constructorShape(cd, extsh) @@ -2929,6 +2952,32 @@ class NewResolver: private def implicitParent(defn: ClassLikeDef): Opt[TermShape] = if defn.sym is prelude.builtins.Object then N else S(objectShape) + /** Register declaration edges even for unused classes. Parent lookup waits for forward + * definitions and follows inherited members using the same shapes as ordinary selection. + */ + def registerInheritance(defn: ClassLikeDef)(using NewResolverState): Unit = + defn.body.members.values.toList.distinct.foreach: member => + member.asTrm.filterNot(sym => sym.isInstanceOf[ClassCtorSymbol] || sym.isPrivate).foreach: own => + // Bind this host to the defining unit before an importer can observe its edges. + own.overrideLinks.inferenceHost + listenInheritedMember(defn, member.nme): lookup => + def record(info: MemberLookup): Unit = info match + case MemberLookup.Contextual(source, _) => record(source) + case MemberLookup.Found(base: BlockMemberSymbol, _) => link(base) + case MemberLookup.Declared(base, _, _, _, _) => link(base) + case _ => () + def link(base: BlockMemberSymbol): Unit = base.onComplete: () => + base.asTrm.filterNot(_.isPrivate).foreach: target => + if target isnt own then own.overrideLinks.inferenceHost.publish(target) + record(lookup) + + private def listenInheritedMember(defn: ClassLikeDef, name: Str) + (listener: ShapeListener[MemberLookup])(using NewResolverState): Unit = + // Legacy preludes may have no Object declaration or even an enclosing prelude context. + // Their root classes have no implicit nominal parent in the legacy resolver. + if !newResolution && defn.ext.isEmpty then listener(MemberLookup.Missing) + else listenExt(defn, parent => listener(parent.fold[MemberLookup](MemberLookup.Missing)(_.getMember(name)))) + def listenExt(defn: ClassLikeDef, listener: ShapeListener[Opt[TermShape]])(using NewResolverState): Unit = defn.ext match case S(trm) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 9dc3f42205..eac4155798 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -389,6 +389,16 @@ end BlockMemberSymbol sealed abstract class MemberSymbol(using State) extends Symbol: def nme: Str def subst(using SymbolSubst): MemberSymbol + + /** Resolution publishes direct override edges; only resolver subscriptions may observe the + * growing graph. Erasure/lowering read the completed declaration identities, never value flow. + * The host uses the same consumer-local copies as other inference publishers on imported code. + */ + private[semantics] val overrideLinks: OverrideLinks = new OverrideLinks + def overriddenMembers(using codegen.Erasure): Ls[TermSymbol] = overrideLinks.members + // IR duplication copies edges only after all fresh symbols have been allocated. + private[hkmc2] def copyOverrideLinksFrom(source: MemberSymbol)(using SymbolSubst): Unit = + overrideLinks.copyFrom(source.overrideLinks) class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.Ident)(using State) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index f215b946a2..50dc2ee301 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -385,6 +385,8 @@ enum Term extends Statement, AutoLocated, ShapePublisher: case SimpleRef(sym: codegen.SimpleSymbol)(val tree: Tree.Ident) extends Term, NewRefImpl case SelfRef(sym: InnerSymbol)(val tree: Tree.Ident) extends Term, NewRefImpl case MemberRef(sym: MemberSymbol)(val tree: Tree.Ident, val resSym: FlowSymbol) extends Term, NewResolvableImpl, NewRefImpl + /** A direct reference to an inherited implementation, retaining the lexical receiver. */ + case Super(receiver: Term, owner: InnerSymbol, id: Tree.Ident)(val resSym: FlowSymbol) extends Term, NewResolvableImpl, ShapeHost /** An optional class selection fixes the lookup scope for an explicit member projection. */ case NewSel(prefix: Term, id: Tree.Ident, cls: Opt[NewSel])(val resSym: FlowSymbol) extends Term, NewSelImpl, ShapeHost case UnresolvedRef(prefixes: Ls[Term], id: Tree.Ident)(val resSym: FlowSymbol) extends Term, UnresolvedRefImpl, ShapeHost @@ -657,6 +659,9 @@ enum Term extends Statement, AutoLocated, ShapePublisher: case term @ SimpleRef(sym) => SimpleRef(sym)(term.tree) case term @ SelfRef(sym) => SelfRef(sym)(term.tree) case term @ MemberRef(sym) => copyNewResolution(term, MemberRef(sym)(term.tree, term.resSym)) + case term @ Super(receiver, owner, id) => + return copyMetadata(term, copyShapes(term, copyNewResolution(term, + Super(receiver.mkClone, owner, id)(term.resSym)))) case term: NewSel => return cloneSel(term) case term @ UnresolvedRef(prefixes, id) => val clonedPrefixes = prefixes.map(_.mkClone) @@ -842,6 +847,7 @@ sealed trait Statement extends Located, ProductWithExtraInfo, Describable: case UnresolvedRef(_, _) => "wildcard-open reference" case App(lhs, rhs) => "application" case TyApp(lhs, targs) => "type application" + case _: Super => "super reference" case NewSel(pre, nme, _) => "selection" case Sel(pre, nme) => "selection" case SynthSel(pre, nme) => "selection" @@ -919,6 +925,7 @@ sealed trait Statement extends Located, ProductWithExtraInfo, Describable: case TyApp(pre, tarsg) => pre +: tarsg.toVector case Sel(pre, _) => Vector.single(pre) case SynthSel(pre, _) => Vector.single(pre) + case Super(receiver, _, _) => Vector.single(receiver) case NewSel(pre, _, cls) => Vector.single(pre) ++ cls.toVector case UnresolvedRef(prefixes, _) => prefixes.toVector case DynSel(o, f, _, _) => Vector.double(o, f) @@ -1022,6 +1029,7 @@ sealed trait Statement extends Located, ProductWithExtraInfo, Describable: r.sym match case _: BuiltinSymbol => r.sym.nme case _ => r.sym.showName + case sup: Super => doc"super.${sup.id.name}" case sel: NewSel => val str = sel.id.name val pre = sel.cls.fold(doc"${sel.prefix.show}.")(cls => doc"${sel.prefix.show}.${cls.show}#") @@ -1216,6 +1224,7 @@ sealed trait Statement extends Located, ProductWithExtraInfo, Describable: case WildcardTy(in, out) => s"in ${in.map(_.toString).getOrElse("⊥")} out ${out.map(_.toString).getOrElse("⊤")}" case Sel(pre, nme) => s"${pre.showDbg}.${nme.name}" case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" + case Super(_, owner, id) => s"super[${owner.showDbg}].${id.name}" case NewSel(pre, nme, cls) => s"${pre.showDbg}.${cls.fold("")(c => s"${c.showDbg}#")}${nme.name}" case UnresolvedRef(_, id) => s"${id.name}‹open›" case DynSel(pre, fld, _, _) => s"${pre.showDbg}[${fld.showDbg}]" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala index bf96a9add2..d75c9b8914 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala @@ -32,7 +32,7 @@ trait PossiblyErroneous: var isErroneous: Bool = false trait NewResolvableImpl extends PossiblyErroneous: - self: MemberRef | NewSel | UnresolvedRef => + self: MemberRef | NewSel | UnresolvedRef | Super => var resolvedTargets: Ls[DefinitionSymbol[?]] = Nil // * filled during flow analysis // val resSym: FlowSymbol = FlowSymbol.simpleRef(self.sym.name) // var disamb: Opt[Disambiguation] = None // * filled during flow analysis diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index fe90cad986..b22cbaa0a7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -225,5 +225,5 @@ object Keyword: type LetLike = `let`.type | `set`.type - type Modifier = `in`.type | `out`.type | `mut`.type | `abstract`.type | `declare`.type | `data`.type | `virtual`.type | `override`.type | + type Modifier = `in`.type | `out`.type | `mut`.type | `abstract`.type | `declare`.type | `data`.type | `virtual`.type | `override`.type | `open`.type | `public`.type | `private`.type | `staged`.type diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala index 2b6dc84fc3..049feabe8e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala @@ -419,6 +419,7 @@ class ParseRules(using State): singleKw(`undefined`)(UnitLit(false)), singleKw(`null`)(UnitLit(true)), singleKw(`this`)(Ident("this")), + singleKw(`super`)(Ident("super")), singleKw(Keyword.__)(Under()), Kw(`#`)( ParseRule(s"'#' directive keyword")( diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 5ebc577838..f95a173c39 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -314,6 +314,9 @@ enum Tree extends AutoLocated: case LetLike(kw, und @ Under(), r, b) => LetLike(kw, Ident("_").withLocOf(und), r, b) + // `open` keeps its existing expression grammar; a declaration operand makes it a modifier. + case Open(body: (TypeOrTermDef | Modified | Annotated)) => + Annotated(Keywrd(Keyword.`open`).withLocOf(this), body.desugared) case PossiblyAnnotated(anns, m: Modified) => PossiblyAnnotated(anns, m match @@ -327,6 +330,8 @@ enum Tree extends AutoLocated: Annotated(kw, s.desugared) case Modified(kw @ Keywrd(Keyword.`virtual`), s) => Annotated(kw, s.desugared) + case Modified(kw @ Keywrd(Keyword.`override`), s) => + Annotated(kw, s.desugared) case Modified(kw @ Keywrd(Keyword.`public`), s) => Annotated(kw, s.desugared) case Modified(kw @ Keywrd(Keyword.`private`), s) => diff --git a/hkmc2/shared/src/test/mlscript-compile/Block.mls b/hkmc2/shared/src/test/mlscript-compile/Block.mls index 8ed66e70ec..67c91d5e84 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Block.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Block.mls @@ -21,10 +21,10 @@ type ParamList = Array[Symbol] class Symbol(val name: Str) // this is so that we're able to retrieve information about the class from the symbol -class ClassSymbol(val name: Str) extends Symbol(name) -class VirtualClassSymbol(val name: Str) extends ClassSymbol(name) -class ConcreteClassSymbol(val name: Str, val value: Class, val paramsOpt: Opt[ParamList], val auxParams: Array[ParamList]) extends ClassSymbol(name) -class ModuleSymbol(val name: Str, val value: Class) extends Symbol(name) +class ClassSymbol(name: Str) extends Symbol(name) +class VirtualClassSymbol(name: Str) extends ClassSymbol(name) +class ConcreteClassSymbol(name: Str, val value: Class, val paramsOpt: Opt[ParamList], val auxParams: Array[ParamList]) extends ClassSymbol(name) +class ModuleSymbol(name: Str, val value: Class) extends Symbol(name) class NoSymbol() extends Symbol("$no_symbol$") class Arm(val cse: Case, val body: Block) @@ -109,7 +109,7 @@ fun showSymbol(s: Symbol) = s.name.replaceAll("$", "_") fun showPath(p: Path): Str = if p is // avoids needing to import the runtime module - Select(ValueSimpleRef(Symbol("runtime")), ModuleSymbol("Unit", Runtime.Unit)) then "()" + Select(ValueSimpleRef(Symbol("runtime")), name) and name is ModuleSymbol(_, Runtime.Unit) and name.name is "Unit" then "()" Select(qual, name) then showPath(qual) + "." + showSymbol(name) DynSelect(qual, fld, false) then showPath(qual) + ".(" + showPath(fld) + ")" DynSelect(qual, fld, true) then showPath(qual) + ".[" + showPath(fld) + "]" diff --git a/hkmc2/shared/src/test/mlscript-compile/Iter.mls b/hkmc2/shared/src/test/mlscript-compile/Iter.mls index b595b026ac..4dcf320a11 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Iter.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Iter.mls @@ -13,7 +13,7 @@ module Iter with ... class IterableBase with set this.(Symbol.iterator) = iterator - fun iterator() = throw new Error("Not implemented: iterator()") + open fun iterator() = throw new Error("Not implemented: iterator()") // require that subclasses are immutable fun toArray(): Array = [...this] @@ -27,8 +27,8 @@ class IterableBase with else array // * Functions that create a fresh new result - fun at(idx) = asArray.at(idx) - fun concat(...xs) = asArray.concat(...xs) + open fun at(idx) = asArray.at(idx) + open fun concat(...xs) = asArray.concat(...xs) fun copyWithin(...args) = asArray.copyWithin(...args) fun entries() = asArray.entries() fun every(...args) = asArray.every(...args) @@ -48,13 +48,13 @@ class IterableBase with fun map(...args) = asArray.map(...args) fun reduce(...args) = asArray.reduce(...args) fun reduceRight(...args) = asArray.reduceRight(...args) - fun slice(...args) = asArray.slice(...args) + open fun slice(...args) = asArray.slice(...args) fun some(...args) = asArray.some(...args) fun toLocaleString(...args) = asArray.toLocaleString(...args) fun toReversed() = asArray.toReversed() fun toSorted(...args) = asArray.toSorted(...args) fun toSpliced(...args) = asArray.toSpliced(...args) - fun toString() = asArray.toString() + open fun toString() = asArray.toString() // `with` is an mls keyword fun id"with"(...args) = asArray.with(...args) @@ -68,13 +68,13 @@ class IterableBase with fun splice(...args) = toArray().splice(...args) fun unshift(...args) = toArray().unshift(...args) - fun length = asArray.length + open fun length = asArray.length fun values() = iterator() // * Implements the `IterableImpl` protocol. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol class IterableImpl(mk: () -> Iterator) extends IterableBase with - fun iterator() = mk() + override fun iterator() = mk() // * Implements the `Iterator` protocol. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls index f8ee7e3b8a..6350913f63 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyArray.mls @@ -54,14 +54,14 @@ fun sliceHelper(beg: Int, fin: Int, arr: Indexable) = // Marker base class for View and Splice class LazyArr extends IterableBase with - fun toString() = "[" + this joined(", ") + "]" - fun concat(...args: Array[Indexable]) = concatHelper(mut [SpliceMarker, this], this.length, ...args) + open override fun toString() = "[" + this joined(", ") + "]" + open override fun concat(...args: Array[Indexable]) = concatHelper(mut [SpliceMarker, this], this.length, ...args) // Requires that the underlying iterable is constant-time indexable // Note: Ideally `underlying` should be typed as `IterableBase`, but `Array` currently does not extend it. class View(val underlying: Indexable, val start: Int, val end: Int) extends LazyArr with - fun iterator() = + override fun iterator() = let idx = start let until = underlying.length - end IteratorImpl of () => @@ -73,21 +73,21 @@ class View(val underlying: Indexable, val start: Int, val end: Int) extends Lazy fun materialize(): Indexable = underlying.slice(start, underlying.length - end) - fun at(i) = + override fun at(i) = if i < -length || i >= length do throw RangeError("View.at: Index out of bounds") underlying.at(normIdx(i, length) + start) - fun slice(beg, fin) = sliceHelper(beg, fin, this) + override fun slice(beg, fin) = sliceHelper(beg, fin, this) val len = underlying.length - start - end - fun length = len - fun toString() = "[" + this joined(", ") + "]" + override fun length = len + override fun toString() = "[" + this joined(", ") + "]" object SpliceMarker class Splice(val bits: Array, val len: Int) extends LazyArr with let materialized = null - fun iterator() = + override fun iterator() = let idx = 0 let uitr = null @@ -140,15 +140,15 @@ class Splice(val bits: Array, val len: Int) extends LazyArr with set materialized = reify materialized - fun at(i) = materialize().at(i) - fun slice(beg, fin) = sliceHelper(beg, fin, this) - fun concat(...args: Array[Indexable]) = + override fun at(i) = materialize().at(i) + override fun slice(beg, fin) = sliceHelper(beg, fin, this) + override fun concat(...args: Array[Indexable]) = if materialized === null then concatHelper(mut [SpliceMarker, this], len, ...args) else concatHelper(mut [SpliceMarker, materialized], len, ...args) - fun length = len - fun toString() = "[" + materialize() joined(", ") + "]" + override fun length = len + override fun toString() = "[" + materialize() joined(", ") + "]" module LazyArray with... diff --git a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls index 7f44ae8b41..837871d5dd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls +++ b/hkmc2/shared/src/test/mlscript-compile/LazyFingerTree.mls @@ -53,12 +53,12 @@ fun sliceHelper(beg: Int, fin: Int, arr: Indexable) = // Marker base class for View and Splice class LazyFT extends IterableBase with - fun toString() = "[" + this joined(", ") + "]" - fun concat(...args: Array[Indexable]) = concatHelper(mut [SpliceMarker, this], this.length, ...args) + open override fun toString() = "[" + this joined(", ") + "]" + open override fun concat(...args: Array[Indexable]) = concatHelper(mut [SpliceMarker, this], this.length, ...args) class View(val underlying: Indexable, val start: Int, val end: Int) extends LazyFT with - fun iterator() = + override fun iterator() = let idx = start let until = underlying.length - end IteratorImpl of () => @@ -70,21 +70,21 @@ class View(val underlying: Indexable, val start: Int, val end: Int) extends Lazy fun materialize(): Indexable = underlying.slice(start, underlying.length - end) - fun at(i) = + override fun at(i) = if i < -length || i >= length do throw RangeError("View.at: Index out of bounds") underlying.at(normIdx(i, length) + start) - fun slice(beg, fin) = sliceHelper(beg, fin, this) + override fun slice(beg, fin) = sliceHelper(beg, fin, this) val len = underlying.length - start - end - fun length = len - fun toString() = "[" + this joined(", ") + "]" + override fun length = len + override fun toString() = "[" + this joined(", ") + "]" object SpliceMarker class Splice(val bits: Array, val len: Int) extends LazyFT with let materialized = null - fun iterator() = materialize().iterator() + override fun iterator() = materialize().iterator() fun reify = let acc = FingerTreeList.mk() @@ -117,16 +117,16 @@ class Splice(val bits: Array, val len: Int) extends LazyFT with set materialized = reify materialized - fun at(i) = materialize().at(i) + override fun at(i) = materialize().at(i) // called on eager spreads in matches - fun slice(beg, fin) = sliceHelper(beg, fin, this) - fun concat(...args: Array[Indexable]) = + override fun slice(beg, fin) = sliceHelper(beg, fin, this) + override fun concat(...args: Array[Indexable]) = if materialized === null then concatHelper(mut [SpliceMarker, this], len, ...args) else concatHelper(mut [SpliceMarker, materialized], len, ...args) - fun length = len - fun toString() = "[" + materialize() joined(", ") + "]" + override fun length = len + override fun toString() = "[" + materialize() joined(", ") + "]" module LazyFingerTree with... diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 3bbafe44eb..5294741092 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -231,7 +231,7 @@ data class ContTrace( // PrintStackEffect stores the showLocals flag; FatalEffect has no handler function. data class EffectSig(contTrace: ContTrace, handler: Any, handlerFun: Function | Bool | null) -class NonLocalReturn with +abstract class NonLocalReturn with fun ret(value) data class FnLocalsInfo(fnName: Str, locals: Array[LocalVarInfo]) diff --git a/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls b/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls index 47ed1cb990..0c808e68f8 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls @@ -5,17 +5,25 @@ module M with //│ ╔══[COMPILATION ERROR] Keyword `using` must occur before all parameters. //│ ║ l.4: fun f(foo: Int, using bar: Int) //│ ╙── ^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Module 'M' has unimplemented members +//│ ║ l.3: module M with +//│ ║ ^^^^^^^^^^^^^ +//│ ║ l.4: fun f(foo: Int, using bar: Int) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'f' requires an implementation +//│ ║ l.4: fun f(foo: Int, using bar: Int) +//│ ╙── ^ module M with - fun f(using Int) + fun f(using Int) = () :e M.f //│ ╔══[COMPILATION ERROR] Cannot query instance of type Int for call: -//│ ║ l.13: M.f +//│ ║ l.21: M.f //│ ║ ^^^ //│ ╟── Required by contextual parameter declaration: -//│ ║ l.10: fun f(using Int) +//│ ║ l.18: fun f(using Int) = () //│ ║ ^^^^^^^^^ //│ ╙── Missing instance: Expected: Int; Available: ‹none available› @@ -29,20 +37,20 @@ val someInt = 42 :e using someInt = 42 //│ ╔══[COMPILATION ERROR] Expected a non-module type; found reference. -//│ ║ l.30: using someInt = 42 +//│ ║ l.38: using someInt = 42 //│ ║ ^^^^^^^ //│ ╙── Implicit instance must be marked as returning a 'module' in order to have a module return type. //│ ╔══[COMPILATION ERROR] Expected a type, got reference -//│ ║ l.30: using someInt = 42 +//│ ║ l.38: using someInt = 42 //│ ╙── ^^^^^^^ :e using someInt: Int = 42 //│ ╔══[COMPILATION ERROR] Expected a type, got block -//│ ║ l.40: using someInt: Int = 42 +//│ ║ l.48: using someInt: Int = 42 //│ ╙── ^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type block -//│ ║ l.40: using someInt: Int = 42 +//│ ║ l.48: using someInt: Int = 42 //│ ╙── ^^^^^^^^^^^^ module M with @@ -51,19 +59,19 @@ module M with :e M.bar[](1, 2) //│ ╔══[COMPILATION ERROR] Expected 2 type arguments, got 0 -//│ ║ l.52: M.bar[](1, 2) +//│ ║ l.60: M.bar[](1, 2) //│ ╙── ^^^^^^^ :e M.bar[Int](1, 2) //│ ╔══[COMPILATION ERROR] Expected 2 type arguments, got 1 -//│ ║ l.58: M.bar[Int](1, 2) +//│ ║ l.66: M.bar[Int](1, 2) //│ ╙── ^^^^^^^^^^ using Int = 42 module M with - fun foo()(using a: Int) + fun foo()(using a: Int) = () // This should fail because M.foo() returns a function that requires special handling :breakme @@ -74,18 +82,18 @@ M.foo() fun f[T](using t: T) = t f //│ ╔══[COMPILATION ERROR] Cannot query instance of type ‹unspecified› (type parameter T) for call: -//│ ║ l.75: f +//│ ║ l.83: f //│ ║ ^ //│ ╟── Required by contextual parameter declaration: -//│ ║ l.74: fun f[T](using t: T) = t +//│ ║ l.82: fun f[T](using t: T) = t //│ ║ ^^^^^^^^^^ //│ ╙── Illegal query for an unspecified type variable T. :e fun f(using t: Int = 42) = t //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found term definition -//│ ║ l.85: fun f(using t: Int = 42) = t +//│ ║ l.93: fun f(using t: Int = 42) = t //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: t -//│ ║ l.85: fun f(using t: Int = 42) = t +//│ ║ l.93: fun f(using t: Int = 42) = t //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/basics/Classes.mls b/hkmc2/shared/src/test/mlscript/basics/Classes.mls index b3a4caad0b..70eed390fa 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Classes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Classes.mls @@ -41,7 +41,7 @@ Foo(42) //│ = Foo(_) -class Bar { val x: Int } +abstract class Bar { val x: Int } class Foo() extends Bar { val x = 1 @@ -55,22 +55,36 @@ class Foo() extends Bar { x = 1 } //│ ╔══[COMPILATION ERROR] Name not found: x //│ ║ l.54: class Foo() extends Bar { x = 1 } //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Class 'Foo' has unimplemented members +//│ ║ l.54: class Foo() extends Bar { x = 1 } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'x' requires an implementation +//│ ║ l.44: abstract class Bar { val x: Int } +//│ ╙── ^ :e class Foo() extends Bar { val x = 1 } with val y = this.x //│ ╔══[COMPILATION ERROR] Multiple type definition bodies are not supported. -//│ ║ l.62: val y = this.x +//│ ║ l.68: val y = this.x //│ ╙── ^^^^^^^^^^^^^^ :e class Foo() extends Bar { x = 1 } with val y = this.x //│ ╔══[COMPILATION ERROR] Multiple type definition bodies are not supported. -//│ ║ l.69: val y = this.x +//│ ║ l.75: val y = this.x //│ ╙── ^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: x -//│ ║ l.68: class Foo() extends Bar { x = 1 } with +//│ ║ l.74: class Foo() extends Bar { x = 1 } with //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Class 'Foo' has unimplemented members +//│ ║ l.74: class Foo() extends Bar { x = 1 } with +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.75: val y = this.x +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Member 'x' requires an implementation +//│ ║ l.44: abstract class Bar { val x: Int } +//│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/basics/GenericClasses.mls b/hkmc2/shared/src/test/mlscript/basics/GenericClasses.mls index b4241eb0b3..6508632fba 100644 --- a/hkmc2/shared/src/test/mlscript/basics/GenericClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/GenericClasses.mls @@ -21,7 +21,7 @@ class Baz[C](x: C) class Bar[B](x: Foo[Int], y: Baz[B] -> Int) -class Foo[A] with +abstract class Foo[A] with fun foo: A -> A diff --git a/hkmc2/shared/src/test/mlscript/basics/Indentation.mls b/hkmc2/shared/src/test/mlscript/basics/Indentation.mls index 3b5f645014..70c7dcb558 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Indentation.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Indentation.mls @@ -163,7 +163,7 @@ id(0 //│ ╟── Integer literal cannot be called like a function. //│ ║ l.150: id(0 //│ ╙── ^ -//│ ═══[RUNTIME ERROR] TypeError: 0 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :soir diff --git a/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls b/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls index eeb7b5f9e8..2495b50b99 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls @@ -90,7 +90,7 @@ data class Some[out T](x: T) extends Option[T] data class Barr(x: Int)(y: Num) with - fun f = x + y + open fun f = x + y data class Bazz(z: Int) extends Barr(z % 10)(z / 10) with fun g = z @@ -101,7 +101,7 @@ Bazz(42).f new Barr(40)(2) with - fun f = 43 + override fun f = 43 //│ = $anon { x: 40, y: 2 } diff --git a/hkmc2/shared/src/test/mlscript/basics/Super.mls b/hkmc2/shared/src/test/mlscript/basics/Super.mls new file mode 100644 index 0000000000..3541054409 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/Super.mls @@ -0,0 +1,9 @@ +:. +:js + +class Base { open fun f(x) = x + 1 } +class Child extends Base { override fun f(x) = super.f(x + 1) * 2 } + +:expect 10 +(new Child).f(3) +//│ = 10 diff --git a/hkmc2/shared/src/test/mlscript/basics/Underscores.mls b/hkmc2/shared/src/test/mlscript/basics/Underscores.mls index 2b1c546f21..29b8ce1bc7 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Underscores.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Underscores.mls @@ -87,7 +87,7 @@ test(f: print)(1) //│ ╟── No member interface is known for this type. //│ ║ l.64: let test = (_.f(0, _, 2)) as (({f: (Int, Int, Int) -> ()}, Int) -> ()) //│ ╙── ^^ -//│ ═══[RUNTIME ERROR] Error: Function 'test' expected 2 arguments but got 1 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. test((f: print), 1) //│ > 0 1 2 diff --git a/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls b/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls index 3dc2e7aa41..bb3bb9a24a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BasicTerms.mls @@ -68,8 +68,8 @@ print("Hi") //│ ║ l.63: 2(2) //│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return runtime.safeCall(2(2)) +//│ throw "This code cannot be run as its compilation yielded an error." //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] TypeError: 2 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls b/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls index f6c27012bd..6677b169da 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CurriedClassInheritance.mls @@ -46,7 +46,7 @@ let c = b(100) //│ ╟── Instance of class 'Bar' cannot receive more argument lists. //│ ║ l.34: let b = Bar(123)(1234) //│ ╙── ^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: b is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ c = undefined :fixme @@ -61,7 +61,7 @@ Bar(123)(1234)(100).a //│ ╟── Instance of class 'Bar' cannot receive more argument lists. //│ ║ l.57: Bar(123)(1234)(100).a //│ ╙── ^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: baseCall is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :ge :re @@ -112,7 +112,7 @@ let c = b(100) //│ ╟── Instance of class 'Bar' cannot receive more argument lists. //│ ║ l.100: let b = b1(1234) //│ ╙── ^^^^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: b2 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ c = undefined :fixme @@ -151,7 +151,7 @@ let c = b(100) //│ ╟── Instance of class 'Baz' cannot receive more argument lists. //│ ║ l.142: let b = Baz(123)(1234) //│ ╙── ^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: b3 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ c = undefined :re diff --git a/hkmc2/shared/src/test/mlscript/codegen/ErasedTypes.mls b/hkmc2/shared/src/test/mlscript/codegen/ErasedTypes.mls index 90a50147fa..eb3f1813ec 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ErasedTypes.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ErasedTypes.mls @@ -1044,9 +1044,18 @@ fun viaTyped(x, y): Str = addInts(x, y) // A member is reached by *selection*, which - unlike a bare reference - is never auto-invoked, so its // signature is its own type. module Plain with - fun f: Int -> Int -//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define Plain⁰ as class Plain¹ module Plain²; end + fun f: Int -> Int = (x: Int) => x +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ define Plain⁰ as class Plain¹ +//│ module Plain² { +//│ method f⁶ = fun f⁷: Function⁰ { +//│ let lambda; +//│ @private +//│ define lambda as fun lambda⁴(x: Int⁰): ? { return x }; +//│ return (lambda⁴ as! Function⁰) +//│ } +//│ }; +//│ end declare module Ext with fun member: Int -> Int @@ -1061,7 +1070,7 @@ fun useExt() = useFn(Ext.member) fun usePlain() = useFn(Plain.f) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define usePlain⁰ as fun usePlain¹(): ? { return useFn¹(Plain².f⁶) }; end +//│ define usePlain⁰ as fun usePlain¹(): ? { return useFn¹(Plain².f⁷) }; end // *Applying* the selected member is a call on that function value, and since `Function` does not store its @@ -1079,7 +1088,7 @@ fun applyPlain() = useInt(Plain.f(1)) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define applyPlain⁰ as fun applyPlain¹(): ? { //│ let tmp: ?; -//│ set tmp = Plain².f⁶(1); +//│ set tmp = Plain².f⁷(1); //│ return useInt¹((tmp as! Int⁰)) //│ }; //│ end @@ -1122,7 +1131,7 @@ fun selUntyped(): Int = @untyped Plain.f //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ define selUntyped⁰ as fun selUntyped¹(): Int⁰ { //│ let tmp: Unknown; -//│ set tmp = Plain².f⁶; +//│ set tmp = Plain².f⁷; //│ return (tmp as! Int⁰) //│ }; //│ end @@ -1131,7 +1140,7 @@ fun selUntyped(): Int = @untyped Plain.f fun selTyped(): Int = Plain.f //│ ═══[COMPILATION ERROR] Cannot use a value of type 'Function' at an unrelated type 'Int' //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define selTyped⁰ as fun selTyped¹(): Int⁰ { return Plain².f⁶ }; end +//│ define selTyped⁰ as fun selTyped¹(): Int⁰ { return Plain².f⁷ }; end // A lambda is disclaimed too: it reaches `LambdaRewriter` as the binding's RHS and is floated into a @@ -1141,10 +1150,10 @@ fun lamUntyped(): Int = @untyped ((x: Int) => x) //│ define lamUntyped⁰ as fun lamUntyped¹(): Int⁰ { //│ let tmp: Unknown, lambda; //│ @private -//│ define lambda as fun lambda⁴(x: Int⁰): ? { +//│ define lambda as fun lambda⁵(x: Int⁰): ? { //│ return x //│ }; -//│ set tmp = lambda⁴; +//│ set tmp = lambda⁵; //│ return (tmp as! Int⁰) //│ }; //│ end @@ -1253,7 +1262,7 @@ fun tagged(x: Int) = fun describe() = Tag.label() describe() //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.1251: object Tag with +//│ ║ l.1260: object Tag with //│ ╙── ^^^ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let describe; @@ -1280,7 +1289,7 @@ fun mk() = fun get() = Tag.label() new Holder //│ ╔══[WARNING] Objects are not yet lifted. -//│ ║ l.1277: object Tag with +//│ ║ l.1286: object Tag with //│ ╙── ^^^ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let Holder; diff --git a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls index 53c5e4032a..220bdfd69f 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls @@ -327,6 +327,6 @@ foo(Foo(42))() //│ ╟── Value of type 'Int' cannot be called like a function. //│ ║ l.312: fun foo(f: {oops: Int}) = //│ ╙── ^^^ -//│ ═══[RUNTIME ERROR] TypeError: baseCall3 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/Lazy.mls b/hkmc2/shared/src/test/mlscript/codegen/Lazy.mls index e39fd1f963..a14014b5eb 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Lazy.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Lazy.mls @@ -2,7 +2,7 @@ :js -data class Lazy[out A](init: () -> A) with +data abstract class Lazy[out A](init: () -> A) with mut val cached: A | () = () fun get: A // fun eq(that: Lazy[A]) = this.cached == that.cached // TODO `this` diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index bd416521be..4a04413ff6 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -60,9 +60,9 @@ Foo() //│ ║ l.8: class Foo //│ ╙── ^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return runtime.safeCall(Foo1()) +//│ throw "This code cannot be run as its compilation yielded an error." //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo cannot be invoked without 'new' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. data class Foo with { print("hi") } diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index 08c8a51e19..1ec80eb1b2 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -209,14 +209,14 @@ fun f(ls) = //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f6; //│ f6 = function f(ls) { -//│ lbl9: while (true) { +//│ lbl8: while (true) { //│ let arg$Cons$0$, arg$Cons$1$; //│ if (ls instanceof Cons1.class) { //│ arg$Cons$0$ = ls.hd; //│ arg$Cons$1$ = ls.tl; //│ ls = arg$Cons$1$; //│ Predef.print(arg$Cons$0$); -//│ continue lbl9 +//│ continue lbl8 //│ } //│ break; //│ } @@ -259,8 +259,8 @@ let x = 1 :sjs while x is {} do() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ lbl11: while (true) { -//│ if (x3 instanceof globalThis.Object) { continue lbl11 } +//│ lbl10: while (true) { +//│ if (x3 instanceof globalThis.Object) { continue lbl10 } //│ break; //│ } //│ return runtime.Unit diff --git a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls index 6deaebf5da..eee514784c 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls @@ -129,7 +129,7 @@ res.resumeWith(42) -class Debugger() with +abstract class Debugger() with fun break(payload) module Test with diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls index bed1bc53d9..24fabe0b83 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectInHandler.mls @@ -2,7 +2,7 @@ :effectHandlers :lift -class PrintEffect with +abstract class PrintEffect with fun p(s: String): () fun aux(s: String): () diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index 822448ac6a..fe8ace8571 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -311,7 +311,7 @@ if true do //│ > 3 module A with - data class Effect(x) with + data abstract class Effect(x) with fun test() = x fun perform(arg) diff --git a/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls index cd01677764..1a34181221 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/GeneratorStack.mls @@ -5,7 +5,7 @@ import "../../mlscript-compile/Stack.mls" open Stack -class Generator with +abstract class Generator with fun produce(result: Stack[Int]): () fun (++) concat(l1, l2) = diff --git a/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls index 43573d189b..6b5da74e54 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/MultiResumption.mls @@ -2,7 +2,7 @@ :effectHandlers :lift -class Logger with +abstract class Logger with fun info(s: Str): () :re diff --git a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls index 392d5fb2a6..0f57ae871d 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/NestedHandlers.mls @@ -6,7 +6,7 @@ let id = 0 //│ id = 0 // FIXME: if the following 2 blocks are together, lifter causes class ordering issue -class MaybeStop with +abstract class MaybeStop with fun f(x: Bool): () fun handleEffects(g) = @@ -89,7 +89,7 @@ handleEffects(f) //│ > h1 end 10 -class Eff with +abstract class Eff with fun perform(): () data class Box(n) let box = new mut Box(0) diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls index 3c99eb4ab1..4a9b99ec1e 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsSafe.mls @@ -3,7 +3,7 @@ :lift -class ThreadEffect with +abstract class ThreadEffect with // task queue val tasks = mut [] fun drain() = diff --git a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls index 47a5e799e7..d412a735fc 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/UserThreadsUnsafe.mls @@ -3,7 +3,7 @@ :lift -class ThreadEffect with +abstract class ThreadEffect with fun fork(thread: () -> ()): () fun yld(): () diff --git a/hkmc2/shared/src/test/mlscript/interop/Virtualization.mls b/hkmc2/shared/src/test/mlscript/interop/Virtualization.mls index ff8c13f0cc..6432e7e63d 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Virtualization.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Virtualization.mls @@ -15,7 +15,7 @@ js.try_catch(() => 123(), id) //│ ╟── Integer literal cannot be called like a function. //│ ║ l.11: js.try_catch(() => 123(), id) //│ ╙── ^^^ -//│ = TypeError: 123 is not a function +//│ = "This code cannot be run as its compilation yielded an error." :e js.try_catch of @@ -27,7 +27,7 @@ js.try_catch of //│ ╟── Integer literal cannot be called like a function. //│ ║ l.22: () => 123() //│ ╙── ^^^ -//│ = 666 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :re js.try_catch of diff --git a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls index 0c8af302ff..d1aab431fb 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls @@ -87,6 +87,7 @@ foo()() //│ ║ l.85: foo()() //│ ║ ^^^^^^^ //│ ╙── Unit value cannot be called like a function. -//│ = 2 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/BadApps.mls b/hkmc2/shared/src/test/mlscript/newres/BadApps.mls index f1ed2cac7f..301aa3ff07 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BadApps.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BadApps.mls @@ -11,7 +11,7 @@ //│ ╟── Integer literal cannot be called like a function. //│ ║ l.7: 42() //│ ╙── ^^ -//│ ═══[RUNTIME ERROR] TypeError: 42 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. let x = 123 @@ -25,7 +25,7 @@ x(456) //│ ╟── Integer literal cannot be called like a function. //│ ║ l.17: let x = 123 //│ ╙── ^^^ -//│ ═══[RUNTIME ERROR] TypeError: x is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Returning a non-callable value adds capture marks, not an argument list. @@ -38,7 +38,7 @@ result()() //│ ╟── Integer literal cannot be called like a function. //│ ║ l.33: fun result() = 1 //│ ╙── ^ -//│ ═══[RUNTIME ERROR] TypeError: 1 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // A captured module is still a non-callable value. :e @@ -51,7 +51,7 @@ call(M) //│ ╟── Module 'M' cannot be called like a function. //│ ║ l.45: module M //│ ╙── ^ -//│ ═══[RUNTIME ERROR] TypeError: Class constructor M cannot be invoked without 'new' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Plain class values require new, including when passed through a parameter. :e @@ -64,7 +64,7 @@ instantiate(Plain) //│ ╟── Class 'Plain' must be instantiated with 'new'. //│ ║ l.58: class Plain //│ ╙── ^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: Class constructor Plain cannot be invoked without 'new' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // A fully applied constructor really has received all its argument lists. :e @@ -77,7 +77,7 @@ overapply(Curried(1)(2)) //│ ╟── Instance of class 'Curried' cannot receive more argument lists. //│ ║ l.73: overapply(Curried(1)(2)) //│ ╙── ^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: tmp is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Explicitly constructed instances must not be told to add another new. :e @@ -90,5 +90,5 @@ overapply(new Plain) //│ ╟── Instance of class 'Plain' cannot receive more argument lists. //│ ║ l.86: overapply(new Plain) //│ ╙── ^^^^^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: tmp1 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls index 5173a0f0a1..77d41a0050 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BareClasses.mls @@ -128,7 +128,7 @@ let f = Foo(1) //│ ╟── Class 'Foo' must be instantiated with 'new'. //│ ║ l.114: class Foo { //│ ╙── ^^^ -//│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo6 cannot be invoked without 'new' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ f = undefined diff --git a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls index 10f47c02ab..2524a80aea 100644 --- a/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls +++ b/hkmc2/shared/src/test/mlscript/newres/BuiltinFunction.mls @@ -50,7 +50,7 @@ curried((x) => (y) => x + y) //│ ║ l.44: fun curried(f: Function) = f(1)(2) //│ ║ ^^^^^^^^ //│ ╙── The 'Function' type does not specify a return type. -//│ = 3 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // The diagnostic points to the written alias annotation, not its definition or call. :ge diff --git a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls index 50226713e4..aae1b3e6b4 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ConstructorInstances.mls @@ -144,7 +144,7 @@ box[Str]("bad") //│ ╟── Instance of class 'Box' cannot receive more argument lists. //│ ║ l.133: let box = new Box[Int](1) //│ ╙── ^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] TypeError: box1 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Box(1) // A partial constructor has already bound its type parameters at its first list. diff --git a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls index 849327edd7..494c00574d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls +++ b/hkmc2/shared/src/test/mlscript/newres/DeclaredTypes.mls @@ -29,9 +29,9 @@ hidden(new Child) // The declared interface selects the method, while dispatch uses the actual receiver. :expect 42 class Base with - fun value(): Int = 0 + open fun value(): Int = 0 class Child extends Base with - fun value(): Int = 42 + override fun value(): Int = 42 fun read(base: Base) = base.value() read(new Child) //│ = 42 @@ -254,7 +254,7 @@ invoke[() -> Int](() => 42) //│ ╟── Type parameter 'T' does not specify a member interface. //│ ║ l.246: private fun invoke[T](value: T) = value() //│ ╙── ^ -//│ = 42 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // TODO: class qualification preserves inference for a selected constructor field. :fixme @@ -573,20 +573,21 @@ invoke(Box(() => 42)) //│ ╟── This type annotation does not provide a type for field 'callback'. //│ ║ l.562: fun invoke(box: Box) = box.callback() //│ ╙── ^^^ -//│ = 42 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' // Abstract tuple elements retain the use of the type parameter in the receiver annotation. :ge class Box[X](val value: [X, X]) fun foo[A](x: Box[A]) = x.value.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.581: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.582: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ║ l.581: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.582: fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.581: fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.582: fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ // Concrete callers cannot hide the annotation responsible for an abstract tuple element. @@ -597,13 +598,13 @@ class Box[X](val value: [X, X]) private fun foo[A](x: Box[A]) = x.value.0.oops foo[Item](Box[Item]([new Item, new Item])) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.597: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.598: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ║ l.597: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.598: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.597: private fun foo[A](x: Box[A]) = x.value.0.oops +//│ ║ l.598: private fun foo[A](x: Box[A]) = x.value.0.oops //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -613,26 +614,26 @@ class Box[X](val value: [X, X]) type Wrapped[T] = Box[T] fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.614: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.615: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ║ l.614: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.615: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.614: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops +//│ ║ l.615: fun foo[A](factory: () -> Wrapped[A]) = factory().value.1.oops //│ ╙── ^ // Tuple annotations do not need a nominal receiver to produce an abstract element. :ge fun foo[A](x: [A, A]) = x.0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.627: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.628: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ║ l.627: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.628: fun foo[A](x: [A, A]) = x.0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.627: fun foo[A](x: [A, A]) = x.0.oops +//│ ║ l.628: fun foo[A](x: [A, A]) = x.0.oops //│ ╙── ^ // An unannotated field can be reached through a function result annotation and an alias. @@ -641,14 +642,14 @@ class Box(val value) type Wrapped = Box fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.642: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.643: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.640: class Box(val value) +//│ ║ l.641: class Box(val value) //│ ║ ^^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.642: fun foo(factory: () -> Wrapped) = factory().value.oops +//│ ║ l.643: fun foo(factory: () -> Wrapped) = factory().value.oops //│ ╙── ^^^^^^^ // TODO: ascription preserves receiver flow for the selected field's missing type. @@ -659,14 +660,14 @@ class Item with class Box(val value) (Box(new Item) as Box).value.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.660: (Box(new Item) as Box).value.oops +//│ ║ l.661: (Box(new Item) as Box).value.oops //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Constructor parameter 'value' has no type annotation. -//│ ║ l.659: class Box(val value) +//│ ║ l.660: class Box(val value) //│ ║ ^^^^^^^^^ //│ ╟── This type annotation does not provide a type for field 'value'. -//│ ║ l.660: (Box(new Item) as Box).value.oops +//│ ║ l.661: (Box(new Item) as Box).value.oops //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' @@ -675,13 +676,13 @@ class Box(val value) :ge fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.676: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.677: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. -//│ ║ l.676: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.677: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ║ ^ //│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.676: fun foo[A](factory: () -> [A, A]) = factory().0.oops +//│ ║ l.677: fun foo[A](factory: () -> [A, A]) = factory().0.oops //│ ╙── ^ // A concrete annotation supplies the nested member interface without losing tuple precision. @@ -698,14 +699,14 @@ foo(Box[Item]([new Item, new Item])) class Pair[A, B](val value: [A, B]) fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.699: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.700: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.699: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.700: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ║ ^^^^^^^^^^^^ //│ ╟── 'omittedSecond' is exposed by this compilation unit. -//│ ║ l.699: fun omittedSecond(x: Pair[Int]) = x.value.1.oops +//│ ║ l.700: fun omittedSecond(x: Pair[Int]) = x.value.1.oops //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // An unfilled omitted alias argument likewise cannot justify an arbitrary member. @@ -713,12 +714,12 @@ fun omittedSecond(x: Pair[Int]) = x.value.1.oops type HalfPair[T] = Pair[Int, T] fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.714: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.715: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'oops' of a value with unknown shape. //│ ╟── Parameter 'x' admits values of unknown shape. -//│ ║ l.714: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.715: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ║ ^^^^^^^^^^^ //│ ╟── 'omittedAlias' is exposed by this compilation unit. -//│ ║ l.714: fun omittedAlias(x: HalfPair) = x.value.1.oops +//│ ║ l.715: fun omittedAlias(x: HalfPair) = x.value.1.oops //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls index 33d33118c3..f5344a602e 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls @@ -93,7 +93,7 @@ explicit."nested".value //│ ╟── Integer literal cannot be called like a function. //│ ║ l.89: 1() //│ ╙── ^ -//│ ═══[RUNTIME ERROR] TypeError: 1 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e dyn diff --git a/hkmc2/shared/src/test/mlscript/newres/GetterReadEffects.mls b/hkmc2/shared/src/test/mlscript/newres/GetterReadEffects.mls index 7b35ec7fed..ce9d3df4e7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GetterReadEffects.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GetterReadEffects.mls @@ -70,10 +70,9 @@ fun test() = test() //│ = 0 -// The abstract val interface also promises a pure read. This getter violates -// that promise; its side effects cannot be relied on through the base interface. -// The same discarded-read behavior occurs under legacy resolution. -:expect 0 +// An abstract val promises a pure read, so implementing it with an effectful +// getter is rejected by override validation. +:e fun test() = let calls = 0 abstract class Base with @@ -86,6 +85,16 @@ fun test() = box.value calls test() +//│ ╔══[COMPILATION ERROR] Overriding member 'value' must preserve whether the member is a field, getter, or method +//│ ║ l.81: fun value: Int = +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ║ l.82: set calls += 1 +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.83: 42 +//│ ║ ^^^^^^^^ +//│ ╟── Inherited member 'value' is defined here +//│ ║ l.79: val value: Int +//│ ╙── ^^^^^ //│ = 0 // An abstract getter interface preserves effects when dispatching to its child. diff --git a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls index 1ed615498b..ae10c5c7fb 100644 --- a/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls +++ b/hkmc2/shared/src/test/mlscript/newres/InterfaceExposure.mls @@ -96,7 +96,7 @@ apply(x => x) //│ ╟── 'apply' is exposed by this compilation unit. //│ ║ l.85: fun apply(f) = f(1) //│ ╙── ^^^^^^^^^^^^^^^^^^^ -//│ = 1 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // A declared callable interface admits calls. :expect 1 diff --git a/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls b/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls index fdfd5e908b..f90fcfcf3d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ObjectMethods.mls @@ -69,9 +69,9 @@ renderObject(Box(3)) :expect "child" class HasMethod with - fun label(): Str = "parent" + open fun label(): Str = "parent" class Child extends HasMethod with - fun label(): Str = "child" + override fun label(): Str = "child" new Child().label() //│ = "child" diff --git a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls new file mode 100644 index 0000000000..a69456a481 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls @@ -0,0 +1,421 @@ +:. + +class Base { open fun foo() = 1 } +class Left extends Base { override fun foo() = 2 } +class Right extends Base { override fun foo() = 3 } +class Inherited extends Base + +:sir +:expect [2, 3, 1] +private fun read(x) = x.foo() +[read(new Left), read(new Right), read(new Inherited)] +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; +//│ @private +//│ define read⁰ as fun read¹(x) { +//│ return x.foo⁰() +//│ }; +//│ set tmp = new Left⁰(); +//│ set tmp1 = read¹(tmp); +//│ set tmp2 = new Right⁰(); +//│ set tmp3 = read¹(tmp2); +//│ set tmp4 = new Inherited⁰(); +//│ set tmp5 = read¹(tmp4); +//│ return [tmp1, tmp3, tmp5] +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = [2, 3, 1] + +:e +class Closed { fun foo() = 1 } +class Invalid extends Closed { override fun foo() = 2 } +//│ ╔══[COMPILATION ERROR] Cannot override non-open member 'foo' +//│ ║ l.30: class Invalid extends Closed { override fun foo() = 2 } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Inherited member 'foo' is defined here +//│ ║ l.29: class Closed { fun foo() = 1 } +//│ ╙── ^^^ + +:e +class MissingOverride extends Base { fun foo() = 2 } +//│ ╔══[COMPILATION ERROR] Overriding member 'foo' requires the 'override' modifier +//│ ║ l.39: class MissingOverride extends Base { fun foo() = 2 } +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Inherited member 'foo' is defined here +//│ ║ l.3: class Base { open fun foo() = 1 } +//│ ╙── ^^^ + +:e +class SpuriousOverride { override fun missing() = 2 } +//│ ╔══[COMPILATION ERROR] Member 'missing' is marked 'override' but overrides no inherited member +//│ ║ l.48: class SpuriousOverride { override fun missing() = 2 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +abstract class Abstract { fun foo() } +class Implemented extends Abstract { fun foo() = 4 } + +:expect 4 +(new Implemented).foo() +//│ = 4 + +:e +class Incomplete extends Abstract +//│ ╔══[COMPILATION ERROR] Class 'Incomplete' has unimplemented members +//│ ║ l.61: class Incomplete extends Abstract +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'foo' requires an implementation +//│ ║ l.53: abstract class Abstract { fun foo() } +//│ ╙── ^^^ + +:e +new Abstract +//│ ╔══[COMPILATION ERROR] Cannot instantiate abstract class 'Abstract' +//│ ║ l.70: new Abstract +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Abstract class defined here +//│ ║ l.53: abstract class Abstract { fun foo() } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:sir +:expect 10 +class SuperBase { open fun foo(x) = x + 1 } +class SuperChild extends SuperBase { override fun foo(x) = super.foo(x + 1) * 2 } +(new SuperChild).foo(3) +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let superBase, tmp; +//│ define SuperBase⁰ as class SuperBase¹ { +//│ method foo¹ = fun foo²(x) { +//│ return +⁰(x, 1) +//│ } +//│ }; +//│ set superBase = SuperBase¹; +//│ define SuperChild⁰ as class SuperChild¹ extends SuperBase¹ { +//│ method foo³ = fun foo⁴(x) { +//│ let tmp1, tmp2; +//│ set tmp1 = +⁰(x, 1); +//│ set tmp2 = super[superBase](SuperChild¹.this).foo²(tmp1); +//│ return *⁰(tmp2, 2) +//│ } +//│ }; +//│ set tmp = new SuperChild¹(); +//│ return tmp.foo⁴(3) +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = 10 + +:expect 11 +class SuperInherited extends SuperChild { fun bar(x) = super.foo(x) + 1 } +(new SuperInherited).bar(3) +//│ = 11 + +:expect 10 +class SuperClosure extends SuperBase { + override fun foo(x) = (() => super.foo(x + 1) * 2)() +} +(new SuperClosure).foo(3) +//│ = 10 + +:expect 13 +class GetterBase(val n) { open fun value = n + 1 } +class GetterChild(n) extends GetterBase(n) { + override fun value = super.value + super.n +} +(new GetterChild(6)).value +//│ = 13 + +:expect 12 +class ReceiverBase { + open fun value() = 1 + open fun run() = this.value() + 2 +} +class ReceiverChild extends ReceiverBase { + override fun value() = 10 + override fun run() = super.run() +} +(new ReceiverChild).run() +//│ = 12 + +:expect 7 +class GenericBase[A](val value: A) { open fun get(): A = value } +class GenericChild[A](x: A) extends GenericBase[A](x) { + override fun get(): A = super.get() +} +(new GenericChild[Int](7)).get() +//│ = 7 + +// Forward parent references resolve, but JS still initializes classes in source order. +:fixme +:expect 8 +class ForwardChild extends ForwardBase { override fun f(x) = super.f(x) + 1 } +class ForwardBase { open fun f(x) = x + 2 } +(new ForwardChild).f(5) +//│ ═══[RUNTIME ERROR] TypeError: Class extends value undefined is not a constructor or null +//│ ═══[RUNTIME ERROR] Expected: '8', got: 'undefined' + +:e +super.foo() +//│ ╔══[COMPILATION ERROR] Cannot use 'super' outside of an object scope +//│ ║ l.155: super.foo() +//│ ╙── ^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +class NoParent { fun f() = super.missing() } +//│ ╔══[COMPILATION ERROR] Resolution error in super reference +//│ ║ l.162: class NoParent { fun f() = super.missing() } +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── No inherited member 'missing' is available through 'super' +//│ ║ l.162: class NoParent { fun f() = super.missing() } +//│ ╙── ^^^^^^^^^^^^^ + +:e +class AbstractSuper extends Abstract { fun foo() = super.foo() } +//│ ╔══[COMPILATION ERROR] Super reference 'foo' requires one inherited implementation +//│ ║ l.171: class AbstractSuper extends Abstract { fun foo() = super.foo() } +//│ ║ ^^^^^^^^^ +//│ ╟── Candidate 'foo' is declared here +//│ ║ l.53: abstract class Abstract { fun foo() } +//│ ╙── ^^^ + +:e +class PrivateBase { private fun secret() = 1 } +class PrivateSuper extends PrivateBase { fun f() = super.secret() } +//│ ╔══[COMPILATION ERROR] Super reference 'secret' requires one inherited implementation +//│ ║ l.181: class PrivateSuper extends PrivateBase { fun f() = super.secret() } +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Candidate 'secret' is declared here +//│ ║ l.180: class PrivateBase { private fun secret() = 1 } +//│ ╙── ^^^^^^ + +:e +class ConcreteMissing { fun foo() } +//│ ╔══[COMPILATION ERROR] Class 'ConcreteMissing' has unimplemented members +//│ ║ l.190: class ConcreteMissing { fun foo() } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'foo' requires an implementation +//│ ║ l.190: class ConcreteMissing { fun foo() } +//│ ╙── ^^^ + +:e +object MissingObject extends Abstract +//│ ╔══[COMPILATION ERROR] Object 'MissingObject' has unimplemented members +//│ ║ l.199: object MissingObject extends Abstract +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'foo' requires an implementation +//│ ║ l.53: abstract class Abstract { fun foo() } +//│ ╙── ^^^ + +:e +class BadKind extends Base { override fun foo = 1 } +//│ ╔══[COMPILATION ERROR] Overriding member 'foo' must preserve whether the member is a field, getter, or method +//│ ║ l.208: class BadKind extends Base { override fun foo = 1 } +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Inherited member 'foo' is defined here +//│ ║ l.3: class Base { open fun foo() = 1 } +//│ ╙── ^^^ + +:e +class AbstractCall() { fun f() } +//│ ╔══[COMPILATION ERROR] Class 'AbstractCall' has unimplemented members +//│ ║ l.217: class AbstractCall() { fun f() } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'f' requires an implementation +//│ ║ l.217: class AbstractCall() { fun f() } +//│ ╙── ^ + +:e +abstract class AbstractCallable() { fun f() } +AbstractCallable() +//│ ╔══[COMPILATION ERROR] Cannot instantiate abstract class 'AbstractCallable' +//│ ║ l.227: AbstractCallable() +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Abstract class defined here +//│ ║ l.226: abstract class AbstractCallable() { fun f() } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:expect 6 +(new SuperBase with { + override fun foo(x) = super.foo(x) * 2 +}).foo(2) +//│ = 6 + +:expect 9 +(new Abstract with { fun foo() = 9 }).foo() +//│ = 9 + +:e +new Abstract with { fun other() = 9 } +//│ ╔══[COMPILATION ERROR] Class '$anon' has unimplemented members +//│ ║ l.247: new Abstract with { fun other() = 9 } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'foo' requires an implementation +//│ ║ l.53: abstract class Abstract { fun foo() } +//│ ╙── ^^^ +//│ = $anon + +:e +class PrivateAbstract { private fun hidden() } +//│ ╔══[COMPILATION ERROR] Class 'PrivateAbstract' has unimplemented members +//│ ║ l.257: class PrivateAbstract { private fun hidden() } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'hidden' requires an implementation +//│ ║ l.257: class PrivateAbstract { private fun hidden() } +//│ ╙── ^^^^^^ + +// The nearest common ancestor is used even when a deeper ancestor is also common. +:expect [5, 6] +class MiddleBase extends Base { open override fun foo() = 4 } +class DeepLeft extends MiddleBase { override fun foo() = 5 } +class DeepRight extends MiddleBase { override fun foo() = 6 } +private fun readDeep(x) = x.foo() +[readDeep(new DeepLeft), readDeep(new DeepRight)] +//│ = [5, 6] + +// Captured parent values and receivers remain correct when lifting is requested. +// The lifter currently leaves first-class local parents (and their subclasses) in place. +:w +:lift +:inlineThreshold 10000 +:expect [6, 14] +fun local(n) = { + class LocalBase { open fun foo(x) = n + x } + class LocalChild extends LocalBase { override fun foo(x) = super.foo(x) * 2 } + (new LocalChild).foo(1) +} +[local(2), local(6)] +//│ ═══[WARNING] Cannot yet lift class `LocalBase` as it is used as a first-class class. +//│ ═══[WARNING] Cannot yet lift definition `LocalChild` as it extends an unliftable class. +//│ = [6, 14] + +:e +class Unrelated { fun foo() = 0 } +private fun ambiguous(x) = x.foo() +[ambiguous(new Base), ambiguous(new Unrelated)] +//│ ╔══[COMPILATION ERROR] This selection of member 'foo' is ambiguous, as it has multiple resolved targets +//│ ║ l.292: private fun ambiguous(x) = x.foo() +//│ ║ ^^^^^ +//│ ╟── target: function 'foo' in class 'Unrelated' +//│ ║ l.291: class Unrelated { fun foo() = 0 } +//│ ║ ^^^ +//│ ╟── target: function 'foo' in class 'Base' +//│ ║ l.3: class Base { open fun foo() = 1 } +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +:e +class ClosedAgain extends Left { override fun foo() = 4 } +//│ ╔══[COMPILATION ERROR] Cannot override non-open member 'foo' +//│ ║ l.306: class ClosedAgain extends Left { override fun foo() = 4 } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Inherited member 'foo' is defined here +//│ ║ l.4: class Left extends Base { override fun foo() = 2 } +//│ ╙── ^^^ + +:e +abstract class MissingInherited extends Abstract +class StillMissing extends MissingInherited +//│ ╔══[COMPILATION ERROR] Class 'StillMissing' has unimplemented members +//│ ║ l.316: class StillMissing extends MissingInherited +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'foo' requires an implementation +//│ ║ l.53: abstract class Abstract { fun foo() } +//│ ╙── ^^^ + +:expect 4 +class InheritsImplementation extends Implemented +(new InheritsImplementation).foo() +//│ = 4 + +:expect 4 +object ImplementedObject extends Abstract { fun foo() = 4 } +ImplementedObject.foo() +//│ = 4 + +// Evaluating a class value must not re-evaluate the parent at every super call. +:expect 8 +fun makeParent(n) = { + class Parent { open fun f() = n } + Parent +} +let parent = makeParent(4) +class StoredParent extends parent { override fun f() = super.f() * 2 } +(new StoredParent).f() +//│ = 8 +//│ parent = class Parent + +:e +let abstractValue = Abstract +new abstractValue +//│ ╔══[COMPILATION ERROR] Cannot instantiate abstract class 'Abstract' +//│ ║ l.348: new abstractValue +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── Abstract class defined here +//│ ║ l.53: abstract class Abstract { fun foo() } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ abstractValue = class Abstract + +:e +let abstractCtor = AbstractCallable +abstractCtor() +//│ ╔══[COMPILATION ERROR] Cannot instantiate abstract class 'AbstractCallable' +//│ ║ l.360: abstractCtor() +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Abstract class defined here +//│ ║ l.226: abstract class AbstractCallable() { fun f() } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ abstractCtor = fun AbstractCallable { class: class AbstractCallable } + +:e +let abstractParent = Abstract +class StoredIncomplete extends abstractParent +//│ ╔══[COMPILATION ERROR] Class 'StoredIncomplete' has unimplemented members +//│ ║ l.372: class StoredIncomplete extends abstractParent +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'foo' requires an implementation +//│ ║ l.53: abstract class Abstract { fun foo() } +//│ ╙── ^^^ +//│ abstractParent = class Abstract + +:expect 5 +let abstractParent = Abstract +class StoredComplete extends abstractParent { fun foo() = 5 } +(new StoredComplete).foo() +//│ = 5 +//│ abstractParent = class Abstract + +// A lifted subclass can call a top-level parent and retain its captured argument. +:lift +:inlineThreshold 10000 +:expect [8, 10] +fun lifted(n) = { + class Child extends SuperBase { override fun foo(x) = super.foo(x) + n } + (new Child).foo(2) +} +[lifted(5), lifted(7)] +//│ = [8, 10] + +:expect [9, 9] +class FieldBase { open val x: Int = 1 } +class FieldChild extends FieldBase { override val x: Int = 9 } +private fun field(x) = x.x +[field(new FieldChild), (new FieldChild).x] +//│ = [9, 9] + +:expect 7 +abstract class AbstractField { val x: Int } +class ConcreteField extends AbstractField { val x: Int = 7 } +(new ConcreteField).x +//│ = 7 + +abstract class PrivateRequirement { private fun hidden() } + +:e +class InheritedPrivateRequirement extends PrivateRequirement { fun hidden() = 1 } +//│ ╔══[COMPILATION ERROR] Class 'InheritedPrivateRequirement' has unimplemented members +//│ ║ l.415: class InheritedPrivateRequirement extends PrivateRequirement { fun hidden() = 1 } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Member 'hidden' requires an implementation +//│ ║ l.412: abstract class PrivateRequirement { private fun hidden() } +//│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls index 08543f0fe1..05729ecd67 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ParamClasses.mls @@ -241,7 +241,7 @@ g(2) //│ ╟── Instance of class 'Foo' cannot receive more argument lists. //│ ║ l.233: let g = f(1) //│ ╙── ^^^^ -//│ ═══[RUNTIME ERROR] TypeError: g is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e let f = new Foo() diff --git a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls index 59d2691829..e25849b859 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PartialSignatures.mls @@ -119,9 +119,9 @@ let right = delayed(Second(9), First(10)) class Item(val value: Int) class Other(val other: Int) class Base with - fun item() = Item(1) + open fun item() = Item(1) class Child extends Base with - fun item() = Other(2) + override fun item() = Other(2) private fun read(x: Base) = x.item().value read(new Child) //│ ╔══[COMPILATION ERROR] Resolution error in selection diff --git a/hkmc2/shared/src/test/mlscript/newres/Records.mls b/hkmc2/shared/src/test/mlscript/newres/Records.mls index 23be18a5a6..faebc9035f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Records.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Records.mls @@ -85,7 +85,7 @@ answer2 //│ ╟── Integer literal cannot be called like a function. //│ ║ l.81: {a: 1}.a() //│ ╙── ^ -//│ ═══[RUNTIME ERROR] TypeError: tmp10.a is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :todo // TODO: Resolve computed keys. Until then, do not look through an unknown overwrite. let key = "a" diff --git a/hkmc2/shared/src/test/mlscript/newres/ShapeFixture.mls b/hkmc2/shared/src/test/mlscript/newres/ShapeFixture.mls index 2248ac1031..d1165b8780 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ShapeFixture.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ShapeFixture.mls @@ -9,7 +9,7 @@ class Pair(val left: Int, val right: Str) val pair = Block.ConcreteClassSymbol("Pair", Pair, Option.Some([ Block.Symbol("left"), Block.Symbol("right")]), []) //│ pair = ConcreteClassSymbol( -//│ "Pair", +//│ _, //│ fun Pair { class: class Pair }, //│ Some([Symbol("left"), Symbol("right")]), //│ [] @@ -29,7 +29,7 @@ Shape.sel(pairShape, Shape.Lit("left")).map((s, ...) => Shape.show(s)) //│ = ["Lit(42)"] //│ pairShape = Class( //│ ConcreteClassSymbol( -//│ "Pair", +//│ _, //│ fun Pair { class: class Pair }, //│ Some([Symbol("left"), Symbol("right")]), //│ [] diff --git a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls index 6524eb0324..7faa485313 100644 --- a/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls +++ b/hkmc2/shared/src/test/mlscript/newres/SpreadCalls.mls @@ -241,7 +241,7 @@ invokeUnknown(...opaque([() => 95])) //│ ║ in ../decls/Prelude.mls //│ ║ l.2: declare type Any //│ ╙── ^^^ -//│ = 95 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Unknown elements must not silently resolve against another known candidate. :e @@ -293,7 +293,7 @@ invokeRest(...opaque([() => 97]), Box(0)) //│ ║ in ../decls/Prelude.mls //│ ║ l.2: declare type Any //│ ╙── ^^^ -//│ = 97 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // When repeatedTuples receives [Box(98)], both a and b must acquire its element // shape so that both `.value` selections resolve. Although the tuple expressions @@ -337,7 +337,7 @@ invokePublicRest(...callbacks, Box(0)) //│ ╟── 'invokePublicRest' is exposed by this compilation unit. //│ ║ l.326: fun invokePublicRest(f, x, ...tail) = f() //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ = 97 +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // A function parameter's Array annotation carries its element interface through a spread. :expect 97 diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Overrides.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Overrides.mls new file mode 100644 index 0000000000..876fddeb2a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Overrides.mls @@ -0,0 +1,49 @@ +:. + +class Base { open fun foo() = 1 } +class Left extends Base { override fun foo() = 2 } +class Right extends Base { override fun foo() = 3 } + +// Loose mode also retains the common member symbol in the lowered selection. +:sir +:expect [2, 3] +private fun read(x) = x.foo() +[read(new Left), read(new Right)] +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let tmp, tmp1, tmp2, tmp3; +//│ @private +//│ define read⁰ as fun read¹(x) { +//│ return x.foo⁰() +//│ }; +//│ set tmp = new Left⁰(); +//│ set tmp1 = read¹(tmp); +//│ set tmp2 = new Right⁰(); +//│ set tmp3 = read¹(tmp2); +//│ return [tmp1, tmp3] +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = [2, 3] + +// Unrelated targets still use loose mode's dynamic selection. +:sir +:expect [1, 4] +class Other { fun foo() = 4 } +private fun readOther(x) = x.foo() +[readOther(new Base), readOther(new Other)] +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let tmp, tmp1, tmp2, tmp3; +//│ @private +//│ define readOther⁰ as fun readOther¹(x) { +//│ return x.foo﹖() +//│ }; +//│ define Other⁰ as class Other¹ { +//│ method foo¹ = fun foo²() { +//│ return 4 +//│ } +//│ }; +//│ set tmp = new Base⁰(); +//│ set tmp1 = readOther¹(tmp); +//│ set tmp2 = new Other¹(); +//│ set tmp3 = readOther¹(tmp2); +//│ return [tmp1, tmp3] +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = [1, 4] diff --git a/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls index 6b5fa7eb72..1fd51a8b33 100644 --- a/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls +++ b/hkmc2/shared/src/test/mlscript/newres/loose/Targets.mls @@ -31,7 +31,7 @@ receiver.missing //│ ╟── Integer literal cannot be called like a function. //│ ║ l.27: 1() //│ ╙── ^ -//│ ═══[RUNTIME ERROR] TypeError: 1 is not a function +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // Missing names are still errors when used as callees. :e diff --git a/hkmc2/shared/src/test/mlscript/newres/wasm/Super.mls b/hkmc2/shared/src/test/mlscript/newres/wasm/Super.mls new file mode 100644 index 0000000000..5005cb6608 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/wasm/Super.mls @@ -0,0 +1,99 @@ +:. + +:expect 10 +class Base() { open fun f(x) = x + 1 } +class Child() extends Base() { override fun f(x) = super.f(x + 1) * 2 } +Child().f(3) +//│ Wasm result: +//│ = 10 + +:expect 21 +class Root() { open fun f() = 1 } +class Middle() extends Root() +class Leaf() extends Middle() { override fun f() = 21 } +fun read(x: Root) = x.f() +read(Leaf()) +//│ Wasm result: +//│ = 21 + +:expect 7 +abstract class Abstract() { fun f() } +class Concrete() extends Abstract() { fun f() = 7 } +fun readAbstract(x: Abstract) = x.f() +readAbstract(Concrete()) +//│ Wasm result: +//│ = 7 + +:expect 10 +class GetterBase() { open fun value = 5 } +class GetterChild() extends GetterBase() { override fun value = super.value * 2 } +GetterChild().value +//│ Wasm result: +//│ = 10 + +:expect 9 +class FieldBase() { open val x: Int = 1 } +class FieldChild() extends FieldBase() { override val x: Int = 9 } +fun field(x: FieldBase) = x.x +field(FieldChild()) +//│ Wasm result: +//│ = 9 + +:expect 7 +abstract class AbstractField() { val x: Int } +class ConcreteField() extends AbstractField() { val x: Int = 7 } +fun abstractField(x: AbstractField) = x.x +abstractField(ConcreteField()) +//│ Wasm result: +//│ = 7 + +:lift +:inlineThreshold 10000 +:expect 10 +fun local() = { + class Base() { open fun f(x) = x + 1 } + class Child() extends Base() { override fun f(x) = super.f(x) * 2 } + Child().f(4) +} +local() +//│ Wasm result: +//│ = 10 + +:expect 12 +abstract class WideField() { val value: Any } +class NarrowField() extends WideField() { val value: Int = 12 } +fun readNarrow(x: NarrowField): Int = x.value +readNarrow(NarrowField()) +//│ Wasm result: +//│ = 12 + +:wat +:expect 9 +FieldChild().x +//│ Wat: +//│ (module +//│ (type $TypeInfoBase (sub (struct (field $$tag i32) (field $$parent (ref null $TypeInfoBase))))) +//│ (type $Object (sub (struct (field $$typeinfo (mut (ref $TypeInfoBase)))))) +//│ (type $FieldBase (sub $Object (struct (field $$typeinfo (mut (ref $TypeInfoBase))) (field $x (mut (ref null i31)))))) +//│ (type $FieldChild (sub $FieldBase (struct (field $$typeinfo (mut (ref $TypeInfoBase))) (field $x (mut (ref null i31)))))) +//│ (type $FieldBase_typeinfo (sub $TypeInfoBase (struct (field $$tag i32) (field $$parent (ref null $TypeInfoBase))))) +//│ (type $FieldBase_FieldBase (func (result (ref $FieldBase)))) +//│ (type $FieldChild_typeinfo (sub $FieldBase_typeinfo (struct (field $$tag i32) (field $$parent (ref null $TypeInfoBase))))) +//│ (type $FieldChild_FieldChild (func (result (ref $FieldChild)))) +//│ (type $entry (func (result (ref null i31)))) +//│ (import "repl" "FieldBase" (func $FieldBase_FieldBase (type $FieldBase_FieldBase))) +//│ (import "repl" "FieldChild" (func $FieldChild_FieldChild (type $FieldChild_FieldChild))) +//│ (import "repl" "FieldBase_typeinfo" (global $FieldBase_typeinfo (ref $FieldBase_typeinfo))) +//│ (import "repl" "FieldChild_typeinfo" (global $FieldChild_typeinfo (ref $FieldChild_typeinfo))) +//│ (func $entry (export "entry") (type $entry) (result (ref null i31)) +//│ (local $tmp (ref null any)) +//│ (block (result (ref null i31)) +//│ (local.set $tmp +//│ (call $FieldChild_FieldChild)) +//│ (return +//│ (struct.get $FieldChild $x +//│ (ref.cast (ref $FieldChild) +//│ (local.get $tmp)))))) +//│ (elem $entry declare func $entry)) +//│ Wasm result: +//│ = 9 diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Call.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Call.mls index 4c6433d420..f890766485 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Call.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Call.mls @@ -41,7 +41,7 @@ fun curried(x)(y) = x //│ ║ l.36: @special identity(1)(2) //│ ╙── ^ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let baseCall; set baseCall = identity¹(1); return @special baseCall(2) +//│ throw "This code cannot be run as its compilation yielded an error." fun nullary = 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls index 9dd43c2f4d..7339d2a832 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls @@ -7,7 +7,7 @@ open Stack abstract class Q -abstract class A extends Q +class A extends Q abstract class B extends Q // Note: A and B are sibling classes (both extend Q but neither extends the diff --git a/hkmc2/shared/src/test/mlscript/wasm/Casts.mls b/hkmc2/shared/src/test/mlscript/wasm/Casts.mls index ef609a3f0b..2003d6debd 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/Casts.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/Casts.mls @@ -52,9 +52,8 @@ useBox(id(new Box(42))) //│ (local.get $a))) //│ (func $useBox (export "useBox") (type $useBox) (param $b (ref $Box)) (result (ref null i31)) //│ (return -//│ (ref.cast (ref i31) -//│ (struct.get $Box $v -//│ (local.get $b))))) +//│ (struct.get $Box $v +//│ (local.get $b)))) //│ (func $entry (export "entry") (type $entry) (result (ref null i31)) //│ (local $tmp (ref $Box)) //│ (local $tmp1 (ref null any)) @@ -150,10 +149,10 @@ wasm.ref.i31(wasm.i32.const(7)) :noInline wasm.ref.i31(id(wasm.i32.const(7))) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int32' at an unrelated type 'Unknown' -//│ ║ l.151: wasm.ref.i31(id(wasm.i32.const(7))) +//│ ║ l.150: wasm.ref.i31(id(wasm.i32.const(7))) //│ ╙── ^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Unknown' at an unrelated type 'Int32' -//│ ║ l.151: wasm.ref.i31(id(wasm.i32.const(7))) +//│ ║ l.150: wasm.ref.i31(id(wasm.i32.const(7))) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^ //│ ═══[COMPILATION ERROR] Cannot cast a Wasm value of type `i32` to `(ref null any)` in a different type hierarchy //│ ═══[COMPILATION ERROR] Operand #1 of wasm intrinsic 'ref.i31' must be of type 'Int32' @@ -178,7 +177,7 @@ unpack(toObj()) :e fun test: Function = id //│ ╔══[COMPILATION ERROR] Returning function instances is not supported -//│ ║ l.179: fun test: Function = id +//│ ║ l.178: fun test: Function = id //│ ╙── ^^ :fixme @@ -220,7 +219,7 @@ fun test: Function = castToA(123) :e castToA(wasm.i32.const(123)) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int32' at an unrelated type 'Unknown' -//│ ║ l.221: castToA(wasm.i32.const(123)) +//│ ║ l.220: castToA(wasm.i32.const(123)) //│ ╙── ^^^^^^^^^^^^^^^^^^^ //│ Wasm result: //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls b/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls index 01c75c069a..1a8b877b10 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ClassInheritance.mls @@ -265,6 +265,9 @@ class B :ge class A extends B class B extends A +//│ ╔══[COMPILATION ERROR] Inheritance cycle involving 'A' +//│ ║ l.266: class A extends B +//│ ╙── ^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Inheritance cycles are not supported. //│ ║ l.266: class A extends B //│ ╙── ^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls b/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls index 449ef9a522..d2409532df 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ClassMethods.mls @@ -182,11 +182,11 @@ d.Counter#inc().Counter#double().Counter#get() //│ ╔══[COMPILATION ERROR] Cannot find variable `d` (VarSymbol) in local or global scope. //│ ║ l.177: let d = Counter(c.Counter#get()) //│ ╙── ^ -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Missing type definition for symbol `member:Counter` +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Missing field `term:Counter/inc` in struct `member:Counter` with type `(type $Counter (sub $Object (struct (field $$typeinfo (mut (ref $TypeInfoBase))) (field $x (mut (ref null any))))))` :todo c.Counter#get() -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Missing type definition for symbol `member:Counter` +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Missing field `term:Counter/get` in struct `member:Counter` with type `(type $Counter (sub $Object (struct (field $$typeinfo (mut (ref $TypeInfoBase))) (field $x (mut (ref null any))))))` class GetterOnly() with diff --git a/hkmc2/shared/src/test/mlscript/wasm/VirtualMethods.mls b/hkmc2/shared/src/test/mlscript/wasm/VirtualMethods.mls index c883f42639..057b84255a 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/VirtualMethods.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/VirtualMethods.mls @@ -3,10 +3,10 @@ class A() with - virtual fun f(x) = x + 1 + open fun f(x) = x + 1 class B() extends A() with - virtual fun g() = 0 - fun f(x) = x * 2 + open fun g() = 0 + override fun f(x) = x * 2 B().f(5) //│ Wasm result: //│ = 10 @@ -14,9 +14,9 @@ B().f(5) :wat class A() with - virtual fun f(x) = x + 1 + open fun f(x) = x + 1 class B() extends A() with - fun f(x) = x * 2 + override fun f(x) = x * 2 fun callF(a: A) = a.f(5) callF(B()) //│ Wat: @@ -145,37 +145,37 @@ callF(B()) class A(val a) with - virtual fun f(x) = x + a + open fun f(x) = x + a class B(val b) extends A(b) with - virtual fun f(x) = x * b + open override fun f(x) = x * b class C(val c) extends B(c) with - fun f(x) = x - c + override fun f(x) = x - c C(3).f(5) //│ Wasm result: //│ = 2 class A() with - virtual fun f() = 1 + open fun f() = 1 class B() extends A() with - virtual fun g() = 2 + open fun g() = 2 class C() extends B() with - fun f() = 10 - fun g() = 20 + override fun f() = 10 + override fun g() = 20 fun callF(a: A) = a.f() fun callG(b: B) = b.g() callF(C()) + callG(C()) //│ Wasm result: -//│ = 21 +//│ = 30 // A virtual slot's Wasm type carries the result type of the method that introduced it, so overrides share it :wat val n32: Int32 = wasm.i32.const(7) class V() with - virtual fun f(m: Int32): Int32 = m + open fun f(m: Int32): Int32 = m class W() extends V() with - fun f(m: Int32): Int32 = m + override fun f(m: Int32): Int32 = m fun callPrim(a: V): Int32 = a.f(n32) callPrim(W()) //│ Wat: @@ -300,36 +300,36 @@ callPrim(W()) // Overrides share the slot's Wasm function type, so primitives must match exactly - here in the result. :ge class M1() with - virtual fun f(m: Int32): Int32 = m + open fun f(m: Int32): Int32 = m class M2() extends M1() with - fun f(m: Int32): Int = 0 + override fun f(m: Int32): Int = 0 M2().f(1) //│ ╔══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Int32' //│ ║ l.306: M2().f(1) //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Overriding method `f` declares result as `(ref null i31)`, but the method it overrides declares `i32` -//│ ║ l.305: fun f(m: Int32): Int = 0 -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.305: override fun f(m: Int32): Int = 0 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ // The same rule on a parameter: `g`'s `m` is unannotated, so it erases to `anyref` rather than `i32`. :ge class N1() with - virtual fun g(m: Int32): Int32 = m + open fun g(m: Int32): Int32 = m class N2() extends N1() with - fun g(m): Int32 = 0 + override fun g(m): Int32 = 0 N2().g(1) //│ ═══[COMPILATION ERROR] Cannot use a value of type 'Int' at an unrelated type 'Int32' //│ ╔══[COMPILATION ERROR] Overriding method `g` declares parameter 1 as `(ref null any)`, but the method it overrides declares `i32` -//│ ║ l.320: fun g(m): Int32 = 0 -//│ ╙── ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.320: override fun g(m): Int32 = 0 +//│ ╙── ^^^^^^^^^^^^^^^^^^^ // Boxed types are bridged by casts, so they need not match class R1() with - virtual fun h(x: Int): Anything = x + open fun h(x: Int): Anything = x class R2() extends R1() with - fun h(x) = x + override fun h(x) = x R2().h(3) //│ Wasm result: //│ = 3 @@ -341,9 +341,9 @@ R2().h(3) :noInline :wat class Parent() with - virtual fun get(): Object = new Parent() + open fun get(): Object = new Parent() class Child() extends Parent() with - fun get(): Child = new Child() + override fun get(): Child = new Child() fun readChild(c: Child): Child = c.get() readChild(new Child()) //│ Wat: From 680a43f8157de186b6297a9a772c159c76958d93 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 18:03:06 +0800 Subject: [PATCH 213/228] Cover selections across subclasses inheriting the same member --- hkmc2/shared/src/test/mlscript/newres/Overrides.mls | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls index a69456a481..d8296aac05 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls @@ -419,3 +419,13 @@ class InheritedPrivateRequirement extends PrivateRequirement { fun hidden() = 1 //│ ╟── Member 'hidden' requires an implementation //│ ║ l.412: abstract class PrivateRequirement { private fun hidden() } //│ ╙── ^^^^^^ + +// Different receiver classes can inherit the same declaration without overriding it. +:expect [1, 1] +class OtherInherited extends Base +private fun readInherited(flag) = { + let receiver = if flag then new Inherited else new OtherInherited + receiver.foo() +} +[readInherited(true), readInherited(false)] +//│ = [1, 1] From 0478299a7790c0bf4d1159f714f747f9fde8d7e2 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 18:15:03 +0800 Subject: [PATCH 214/228] Seal classes to their compilation unit unless open or abstract --- .../main/scala/hkmc2/codegen/Erasure.scala | 26 ++-- .../scala/hkmc2/semantics/Inheritance.scala | 12 ++ .../shared/src/test/mlscript-compile/Iter.mls | 2 +- .../test/mlscript-compile/SealedClasses.mls | 14 ++ .../test/mlscript/basics/SealedClasses.mls | 19 +++ .../src/test/mlscript/decls/Prelude.mls | 6 +- .../test/mlscript/invalml/InvalMLPrelude.mls | 2 +- .../test/mlscript/newres/SealedClasses.mls | 142 ++++++++++++++++++ 8 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript-compile/SealedClasses.mls create mode 100644 hkmc2/shared/src/test/mlscript/basics/SealedClasses.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/SealedClasses.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala index fd9a3bc9dd..21803d3797 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala @@ -161,11 +161,12 @@ final class Erasure private (using Config, Ctx, State)(using Raise): case _ => () visit(root) - def parentClass(parent: New): Opt[ClassLikeDef] = - parent.cls.resolvedSym.flatMap(_.asClsOrMod).flatMap(_.defn).orElse: - ClassValueShape.targetsOf(parent) match - case target :: Nil => S(ClassValueShape.definitionOf(target)) - case _ => N + def parentClasses(parent: New): Ls[ClassLikeDef] = + (parent.cls.resolvedSym.flatMap(_.asClsOrMod).flatMap(_.defn).toList ::: + ClassValueShape.targetsOf(parent).map(ClassValueShape.definitionOf)).distinct + def parentClass(parent: New): Opt[ClassLikeDef] = parentClasses(parent) match + case cls :: Nil => S(cls) + case _ => N // No erased representation is read until every interpretation has been validated and every // nominal parent header has been published, including forward and external declarations. @@ -173,12 +174,15 @@ final class Erasure private (using Config, Ctx, State)(using Raise): case term: Term => term.typeInterpretation.foreach(_.validate(Set.empty)) case _ => () statements.foreach: - case cls: ClassLikeDef => cls.sym match - case sym: ClassLikeSymbol if sym.irClassHeader.isEmpty => - sym.irClassHeader = cls.ext match - case N => S(ClassHeader(N)) - case S(parent) => parentClass(parent).flatMap(_.sym.asClsOrMod).map(p => ClassHeader(S(p))) - case _ => () + case cls: ClassLikeDef => + cls.ext.foreach: extension => + parentClasses(extension).foreach(Inheritance.validateParent(cls, _, extension)) + cls.sym match + case sym: ClassLikeSymbol if sym.irClassHeader.isEmpty => + sym.irClassHeader = cls.ext match + case N => S(ClassHeader(N)) + case S(parent) => parentClass(parent).flatMap(_.sym.asClsOrMod).map(p => ClassHeader(S(p))) + case _ => () case _ => () // Public members replace inherited names; private members retain their declaration identity. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala index 4a1a32a740..814b3d1611 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala @@ -33,6 +33,18 @@ object Inheritance: def isOpen(d: TermDefinition): Bool = d.body.isEmpty || hasModifier(d, Keyword.`open`) + /** Symbol states identify compilation units, including all blocks of a worksheet. Source + * locations cannot establish this boundary: generated classes may have no source location, + * and imported aliases must retain the defining class's unit rather than the alias's unit. + */ + def validateParent(child: ClassLikeDef, parent: ClassLikeDef, site: Term.New)(using Erasure, Raise): Unit = + if parent.isInstanceOf[ClassDef] && (parent.sym.getState isnt child.sym.getState) && + !hasModifier(parent, Keyword.`open`) && !hasModifier(parent, Keyword.`abstract`) then + raise(ErrorReport( + msg"Cannot extend sealed class '${parent.sym.nme}' outside its compilation unit" -> site.toLoc :: + (msg"Class '${parent.sym.nme}' is defined here; declare it 'open' or 'abstract' to allow external subclasses" -> parent.toLoc) :: Nil, + source = Diagnostic.Source.Compilation)) + /** An ancestor may itself be a target (one receiver overrides a member, another inherits it). * Discard less-specific common ancestors, so a chain of overrides is not an ambiguity. */ diff --git a/hkmc2/shared/src/test/mlscript-compile/Iter.mls b/hkmc2/shared/src/test/mlscript-compile/Iter.mls index 4dcf320a11..5daa483a5a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Iter.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Iter.mls @@ -11,7 +11,7 @@ open Stack { Nil, Cons } module Iter with ... -class IterableBase with +open class IterableBase with set this.(Symbol.iterator) = iterator open fun iterator() = throw new Error("Not implemented: iterator()") diff --git a/hkmc2/shared/src/test/mlscript-compile/SealedClasses.mls b/hkmc2/shared/src/test/mlscript-compile/SealedClasses.mls new file mode 100644 index 0000000000..4e4d72e388 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/SealedClasses.mls @@ -0,0 +1,14 @@ +#lang(0.3.x, strictResolution: true) + +module SealedClasses with + class Closed { fun value() = 1 } + open class Open { fun value() = 2 } + abstract class Abstract { fun value() } + // Sealing restricts direct subclasses; a local subclass may itself be open. + open class OpenChild extends Closed + class ClosedChild extends Open + val alias = Closed + module Nested with + class Closed + class Parameterized[A](val item: A) + open class OpenParameterized[A](val item: A) diff --git a/hkmc2/shared/src/test/mlscript/basics/SealedClasses.mls b/hkmc2/shared/src/test/mlscript/basics/SealedClasses.mls new file mode 100644 index 0000000000..0824b0ce8e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/SealedClasses.mls @@ -0,0 +1,19 @@ +:. +:js + +import "../../mlscript-compile/SealedClasses.mls" + +:expect 2 +class OpenChild extends SealedClasses.Open +(new OpenChild).value() +//│ = 2 + +:e +class Invalid extends SealedClasses.Closed +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Closed' outside its compilation unit +//│ ║ l.12: class Invalid extends SealedClasses.Closed +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Closed' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.3: class Closed { fun value() = 1 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index deef5ab3fc..54f1cb00a9 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -4,7 +4,7 @@ declare type Any declare type Anything declare type Nothing -declare class Object { +declare open class Object { fun toString(): Str } declare fun Object(...args): Object // TODO: use an optional parameter when supported @@ -49,7 +49,7 @@ declare class BigInt { declare fun BigInt(value: Str | Int | Bool | BigInt): BigInt declare module BigInt -declare class Function { +declare open class Function { val name: Str fun bind(thisArg, ...args): Function fun apply(thisArg, args: Array): Any @@ -579,7 +579,7 @@ declare module runtime { declare val document: dyn declare val customElements: dyn // Preserve the nominal superclass while its host members remain dynamic. -declare class HTMLElement { +declare open class HTMLElement { val textContent: dyn val innerHTML: dyn val shadowRoot: dyn diff --git a/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls b/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls index eb54d2013c..73aa842ea0 100644 --- a/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls +++ b/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls @@ -4,7 +4,7 @@ open annotations declare class Any declare class Anything declare class Nothing -declare class Object +declare open class Object declare class Unit declare class Bool diff --git a/hkmc2/shared/src/test/mlscript/newres/SealedClasses.mls b/hkmc2/shared/src/test/mlscript/newres/SealedClasses.mls new file mode 100644 index 0000000000..497b843fe7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/SealedClasses.mls @@ -0,0 +1,142 @@ +:. + +import "../../mlscript-compile/SealedClasses.mls" + +// Worksheet blocks and nested scopes still belong to the same compilation unit. +class Local { fun value() = 3 } + +:expect [3, 3, 3] +class LocalChild extends Local +module Nested with + class LocalChild extends Local +[(new LocalChild).value(), (new Nested.LocalChild).value(), + (new Local with { fun extra() = 4 }).value()] +//│ = [3, 3, 3] + +// A sealed class can still be constructed outside its defining unit. +:expect 1 +(new SealedClasses.Closed).value() +//│ = 1 + +:expect [2, 4, 1] +class OpenChild extends SealedClasses.Open +class AbstractChild extends SealedClasses.Abstract { fun value() = 4 } +class IndirectChild extends SealedClasses.OpenChild +[(new OpenChild).value(), (new AbstractChild).value(), (new IndirectChild).value()] +//│ = [2, 4, 1] + +:e +class Invalid extends SealedClasses.Closed +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Closed' outside its compilation unit +//│ ║ l.29: class Invalid extends SealedClasses.Closed +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Closed' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.3: class Closed { fun value() = 1 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// Neither modifier on the subclass grants access to a sealed parent. +:e +open class InvalidOpen extends SealedClasses.Closed +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Closed' outside its compilation unit +//│ ║ l.40: open class InvalidOpen extends SealedClasses.Closed +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Closed' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.3: class Closed { fun value() = 1 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:e +abstract class InvalidAbstract extends SealedClasses.Closed +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Closed' outside its compilation unit +//│ ║ l.50: abstract class InvalidAbstract extends SealedClasses.Closed +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Closed' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.3: class Closed { fun value() = 1 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// Openness is not inherited by subclasses. +:e +class InvalidGrandchild extends SealedClasses.ClosedChild +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'ClosedChild' outside its compilation unit +//│ ║ l.61: class InvalidGrandchild extends SealedClasses.ClosedChild +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'ClosedChild' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.8: class ClosedChild extends Open +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:e +object InvalidObject extends SealedClasses.Closed +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Closed' outside its compilation unit +//│ ║ l.71: object InvalidObject extends SealedClasses.Closed +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Closed' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.3: class Closed { fun value() = 1 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:e +new SealedClasses.Closed with { fun extra() = 4 } +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Closed' outside its compilation unit +//│ ║ l.81: new SealedClasses.Closed with { fun extra() = 4 } +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Closed' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.3: class Closed { fun value() = 1 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ = $anon + +:e +let storedParent = SealedClasses.Closed +class InvalidStored extends storedParent +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Closed' outside its compilation unit +//│ ║ l.93: class InvalidStored extends storedParent +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Class 'Closed' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.3: class Closed { fun value() = 1 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ storedParent = class Closed + +:e +class InvalidAlias extends SealedClasses.alias +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Closed' outside its compilation unit +//│ ║ l.104: class InvalidAlias extends SealedClasses.alias +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Closed' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.3: class Closed { fun value() = 1 } +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:e +class InvalidNested extends SealedClasses.Nested.Closed +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Closed' outside its compilation unit +//│ ║ l.114: class InvalidNested extends SealedClasses.Nested.Closed +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Closed' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.11: class Closed +//│ ╙── ^^^^^^^^^^^^ + +// The prelude's universal superclass is explicitly open. +:expect 5 +class ExplicitObject extends Object { fun value() = 5 } +(new ExplicitObject).value() +//│ = 5 + +:e +class InvalidParameterized extends SealedClasses.Parameterized[Int](1) +//│ ╔══[COMPILATION ERROR] Cannot extend sealed class 'Parameterized' outside its compilation unit +//│ ║ l.130: class InvalidParameterized extends SealedClasses.Parameterized[Int](1) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Class 'Parameterized' is defined here; declare it 'open' or 'abstract' to allow external subclasses +//│ ║ in ../../mlscript-compile/SealedClasses.mls +//│ ║ l.12: class Parameterized[A](val item: A) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:expect 7 +class OpenParameterized extends SealedClasses.OpenParameterized[Int](7) +(new OpenParameterized).item +//│ = 7 From 877fd4e2dcb74834d2624ecd0726fa85e462091e Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 18:35:28 +0800 Subject: [PATCH 215/228] Simplify superclass lowering and override metadata lifetime --- .../src/main/scala/hkmc2/codegen/Block.scala | 6 +- .../src/main/scala/hkmc2/codegen/Lifter.scala | 7 +- .../main/scala/hkmc2/codegen/Lowering.scala | 168 +++++++++--------- .../scala/hkmc2/codegen/SymbolRefresher.scala | 5 - .../scala/hkmc2/semantics/Inheritance.scala | 3 - .../main/scala/hkmc2/semantics/Symbol.scala | 5 +- .../src/test/mlscript/backlog/Lifter.mls | 2 +- .../src/test/mlscript/backlog/ToTriage.mls | 57 +++--- .../src/test/mlscript/newres/Overrides.mls | 95 ++++++---- 9 files changed, 179 insertions(+), 169 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index f41687666e..09305a9d04 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -1348,9 +1348,9 @@ case class Select(qual: Path, name: Tree.Ident)(val symbol: Opt[DefinitionSymbol def extraInfo(using DebugPrinter): Str = symbol.map(s => s"sym=${s.showAsPlain}").mkString /** Direct lookup on the lexical parent's prototype, with the original receiver as `this`. - * `base` is captured when the class is defined, so an extends expression is never re-evaluated - * at a super call. Explicit operands preserve this meaning through lifting and inlining. - * For Wasm's static dispatch, `base` is a class reference and `symbol` names the called function. + * `base` is the same stable parent operand used by the class definition: a direct class + * reference or a previously evaluated value. No extends expression is re-evaluated at a + * super call. Both operands are explicit so lifting and inlining preserve their bindings. */ case class SuperSelect(qual: Path, base: Path, name: Tree.Ident)(val symbol: TermSymbol, val toLoc: Opt[Loc]) extends Path: def withLoc(loc: Opt[Loc]): SuperSelect = if loc == toLoc then this else copy()(symbol, loc) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index d94606529a..6e3981a290 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -213,10 +213,9 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): new BlockTraverser: this.applyScopedObject(s.obj) override def applyPath(p: Path): Unit = p match - // Wasm's super operand identifies a static class, just like the operand of - // Instantiate. It does not escape as a first-class runtime class value. - case sel @ SuperSelect(qual, RefOfDefn(S(cls: ClassSymbol), _), _) - if config.target == CompilationTarget.Wasm => + // A direct superclass reference, like an extends or Instantiate operand, does not + // let the class escape as a value. Its receiver still needs ordinary capture analysis. + case sel @ SuperSelect(qual, RefOfDefn(S(cls: ClassSymbol), _), _) => applyPath(qual) cls.traverse sel.symbol.traverse diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 1b69589547..384a35b0e9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -41,6 +41,7 @@ class LoweringCtx( val mayRet: Bool, // For rewriting while loop into tail recursive function, represent whether an explicit return is legal in the current block private val definedSymsDuringLowering: collection.mutable.Set[ScopedSymbol], // used to create Scoped blocks val returnType: Opt[ErasedType], // the declared return type of the enclosing function, used to coerce `return`s + val superBases: Map[InnerSymbol, Path], // evaluated parents of the enclosing class definitions ): val map = initMap def collectScopedSym(s: ScopedSymbol) = definedSymsDuringLowering.add(s) @@ -64,11 +65,13 @@ class LoweringCtx( object LoweringCtx: def loweringCtx(using sub: LoweringCtx): LoweringCtx = sub def empty = - LoweringCtx(Map.empty, mayRet = false, collection.mutable.Set.empty, returnType = N) + LoweringCtx(Map.empty, mayRet = false, collection.mutable.Set.empty, returnType = N, superBases = Map.empty) def nestFunc(returnType: Opt[ErasedType])(using sub: LoweringCtx): LoweringCtx = - LoweringCtx(sub.map, mayRet = true, sub.definedSymsDuringLowering, returnType) + LoweringCtx(sub.map, mayRet = true, sub.definedSymsDuringLowering, returnType, sub.superBases) def nestScoped(using sub: LoweringCtx): LoweringCtx = - LoweringCtx(sub.map, sub.mayRet, collection.mutable.Set.empty, sub.returnType) + LoweringCtx(sub.map, sub.mayRet, collection.mutable.Set.empty, sub.returnType, sub.superBases) + def withSuperBase(owner: InnerSymbol, parent: Path)(using sub: LoweringCtx): LoweringCtx = + LoweringCtx(sub.map, sub.mayRet, sub.definedSymsDuringLowering, sub.returnType, sub.superBases + (owner -> parent)) end LoweringCtx import LoweringCtx.loweringCtx @@ -90,20 +93,25 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu val newResolution: Bool = config.language.useNewResolution val strictResolution: Bool = config.language.strictResolution - // Methods are lowered before their class's extends expression. Reserve the captured parent - // here and initialize it at the class definition, in the surrounding lexical scope. - private val superBases = scala.collection.mutable.Map.empty[InnerSymbol, TempSymbol] - private def superBase(owner: InnerSymbol): TempSymbol = - superBases.getOrElseUpdate(owner, TempSymbol(N, erasedType = N, "superBase")) - private def captureSuperBase(owner: InnerSymbol, parent: Path)(body: => Block)(using LoweringCtx): Block = - superBases.get(owner) match - case S(sym) => - // classOf evaluates computed class values into temporaries. The remaining path is - // stable across this assignment and the immediately following class definition. - softAssert(parent.isPure, "Superclass operand must be evaluated before it is captured") - loweringCtx.collectScopedSym(sym) - Assign(sym, parent, body) - case N => body + /** Lower the parent before the class body and share its value with every super reference. + * Definition references are stable; classOf already saves computed class values in temporaries. + * Other paths (notably a class selected through a mutable receiver) must be read here, once, + * so changing that receiver after the class definition cannot redirect its super calls. + */ + private def withClassParent(parent: Path)(k: Path => Block)(using LoweringCtx): Block = + def save(path: Path)(k: Path => Block): Block = + val saved = loweringCtx.registerTempSymbol(N, erasedType = N, "superBase") + Assign(saved, path, k(saved.asSimpleRef)) + parent match + case Value.MemberRef(_, _: ClassSymbol) | Value.SimpleRef(_: TempSymbol) => k(parent) + case sel: Select if sel.symbol.exists(_.isInstanceOf[ClassSymbol]) => + // The selected class declaration is immutable, but its receiver might be a variable. + // Save that receiver while retaining the class symbol for nominal IR analyses. + sel.qual match + case _: Value.This | Value.MemberRef(_, _: ClassSymbol | _: ModuleOrObjectSymbol) | + Value.SimpleRef(_: TempSymbol) => k(sel) + case qual => save(qual)(p => k(sel.copy(qual = p)(sel.symbol, sel.toLoc)(sel.sanitize))) + case _ => save(parent)(k) extension (t: Term) def instantiated = t match @@ -364,68 +372,56 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu msg"Buffered classes must not have a main parameter list; use `constructor(...)` syntax instead." -> defn.toLoc :: Nil, source = Diagnostic.Source.Compilation )) - val (mtds, publicFlds, privateFlds, ctor) = defn match - case pd: PatternDef => - // Compile the pattern definition into `unapply` and `unapplyStringPrefix` - // methods using the `SplitCompiler`, which transliterate the pattern into - // UCS splits that backtrack without any optimizations. - val compiler = new ups.SplitCompiler - val methods = compiler.compilePattern(pd) - // We only need `owner`, `sym`, `params` and `body` - val mtds = methods.map: - case (sym, params, split) => - val paramLists = params :: Nil - val bodyBlock = inScopedBlock(ucs.Normalization(this)(split)(Ret)) - FunDefn.withFreshSymbol(N, sym, paramLists, bodyBlock)(configOverride = N, annotations = Nil) - // The return type is intended to be consistent with `gatherMembers` - (mtds, Nil, Nil, End()) - case _ => gatherMembers(defn.body) - val mod = defn.companion match - case S(sym) => - sym.defn match - case S(mod: ModuleOrObjectDef) => - reportAnnotations(mod, mod.extraAnnotations) - mod.ext match - case S(ext) => fail: - ErrorReport( - msg"Modules cannot have an extension clause." -> ext.toLoc :: Nil, - source = Diagnostic.Source.Compilation - ) - case N => - val (mtds, publicFlds, privateFlds, ctor) = - gatherMembers(mod.body) - S(ClsLikeBody(mod.sym, mtds, privateFlds, publicFlds, ctor, mod.annotations)) - case _ => N - case _ => N + def defineClass(parent: Opt[Path], parentParams: Ls[ParamList]): Block = + val base = parent.getOrElse( + Select(State.globalThisSymbol.asThis, Tree.Ident("Object"))(S(ctx.builtins.Object), defn.toLoc)(false)) + val clsDef = LoweringCtx.withSuperBase(defn.sym, base).givenIn: + val (mtds, publicFlds, privateFlds, ctor) = defn match + case pd: PatternDef => + // Compile the pattern definition into `unapply` and `unapplyStringPrefix` + // methods using the `SplitCompiler`, which transliterate the pattern into + // UCS splits that backtrack without any optimizations. + val compiler = new ups.SplitCompiler + val methods = compiler.compilePattern(pd) + // We only need `owner`, `sym`, `params` and `body` + val mtds = methods.map: + case (sym, params, split) => + val paramLists = params :: Nil + val bodyBlock = inScopedBlock(ucs.Normalization(this)(split)(Ret)) + FunDefn.withFreshSymbol(N, sym, paramLists, bodyBlock)(configOverride = N, annotations = Nil) + // The return type is intended to be consistent with `gatherMembers` + (mtds, Nil, Nil, End()) + case _ => gatherMembers(defn.body) + val mod = defn.companion match + case S(sym) => + sym.defn match + case S(mod: ModuleOrObjectDef) => + reportAnnotations(mod, mod.extraAnnotations) + mod.ext match + case S(ext) => fail: + ErrorReport( + msg"Modules cannot have an extension clause." -> ext.toLoc :: Nil, + source = Diagnostic.Source.Compilation + ) + case N => + val (mtds, publicFlds, privateFlds, ctor) = + gatherMembers(mod.body) + S(ClsLikeBody(mod.sym, mtds, privateFlds, publicFlds, ctor, mod.annotations)) + case _ => N + case _ => N + val pctor = defn.ext.fold[Block](End()): ext => + inScopedBlock(parentConstructor(parentParams, ext.args, ext.toLoc)) + val cfgOverride = defn.extraAnnotations.collectFirst: + case Annot.Config(modify) => modify(config) + ClsLikeDefn(defn.owner, defn.sym, defn.bsym, defn.ctorSym, defn.kind, defn.paramsOpt, defn.auxParams, parent, + mtds, privateFlds, publicFlds, pctor, ctor, mod, bufferable)(cfgOverride, defn.annotations) + Define(clsDef, blockImpl(stats, res)) defn.ext match - case N => - val cfgOverride = defn.extraAnnotations.collectFirst: - case Annot.Config(modify) => modify(config) - captureSuperBase(defn.sym, - Select(State.globalThisSymbol.asThis, Tree.Ident("Object"))(S(ctx.builtins.Object), defn.toLoc)(false))(Define( - ClsLikeDefn(defn.owner, defn.sym, defn.bsym, defn.ctorSym, defn.kind, defn.paramsOpt, defn.auxParams, N, - mtds, - privateFlds, - publicFlds, - End(), - ctor, - mod, - bufferable, - )(cfgOverride, defn.annotations), - blockImpl(stats, res))) + case N => defineClass(N, Nil) case S(ext) => assert(k isnt syntax.Mod) // modules can't extend things and can't have super calls - val cfgOverride = defn.extraAnnotations.collectFirst: - case Annot.Config(modify) => modify(config) classOf(ext.cls, ext): (clsp, params) => - val pctor = inScopedBlock(parentConstructor(params, ext.args, ext.toLoc)) - captureSuperBase(defn.sym, clsp)(Define( - ClsLikeDefn( - defn.owner, defn.sym, defn.bsym, defn.ctorSym, defn.kind, defn.paramsOpt, defn.auxParams, S(clsp), - mtds, privateFlds, publicFlds, pctor, ctor, mod, bufferable, - )(cfgOverride, defn.annotations), - blockImpl(stats, res) - )) + withClassParent(clsp)(parent => defineClass(S(parent), params)) case td: TypeDef => // * Type definitions are erased blockImpl(stats, res) @@ -746,12 +742,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu // Fields live on the receiver; only methods/getters have a base implementation // on the prototype. In particular Reflect.get(base.prototype, ...) cannot read fields. if target.k is syntax.Fun then - // Wasm dispatches to a statically named function and only supports static parents; - // its class operand, like an Instantiate operand, need not be a runtime class value. - val base = if config.target == CompilationTarget.Wasm then - val owner = target.owner.get - owner.asBlkMember.get.asMemberRef(owner.asDefnSym) - else superBase(sup.owner).asSimpleRef + val base = loweringCtx.superBases.getOrElse(sup.owner, + lastWords("Superclass must be lowered before its members")) k(SuperSelect(receiver, base, target.id)(target, sup.toLoc)) else k(Select(receiver, target.id)(S(target), sup.toLoc)(false)) case targets => fail(ErrorReport( @@ -1416,12 +1408,14 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu case S((isym, rft)) => val sym = isym.defn.get.bsym loweringCtx.collectScopedSym(sym) - val (mtds, publicFlds, privateFlds, ctor) = gatherMembers(rft) - val pctor = parentConstructor(params, as, nw.toLoc) - val clsDef = ClsLikeDefn(N, isym, sym, N, syntax.Cls, N, Nil, S(sr), - mtds, privateFlds, publicFlds, pctor, ctor, N, N)(N, Nil) - val inner = new New(sym.ref().resolved(isym), Nil, N)(FlowSymbol.neww(), N) - captureSuperBase(isym, sr)(Define(clsDef, term_nonTail(if mut then Mut(inner) else inner)(k))) + withClassParent(sr): parent => + val clsDef = LoweringCtx.withSuperBase(isym, parent).givenIn: + val (mtds, publicFlds, privateFlds, ctor) = gatherMembers(rft) + val pctor = parentConstructor(params, as, nw.toLoc) + ClsLikeDefn(N, isym, sym, N, syntax.Cls, N, Nil, S(parent), + mtds, privateFlds, publicFlds, pctor, ctor, N, N)(N, Nil) + val inner = new New(sym.ref().resolved(isym), Nil, N)(FlowSymbol.neww(), N) + Define(clsDef, term_nonTail(if mut then Mut(inner) else inner)(k)) case Try(sub, finallyDo) => val l = loweringCtx.registerTempSymbol(S(sub), erasedType = N) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 929ddc6407..b923b0e69e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -157,12 +157,7 @@ private class SymbolRefresherInternal(m: MutMap[Symbol, Symbol])(using State) ex // We have a pretty weird setup here, where we store a mutable state inside the SymbolRefresher // We must initialize the SymbolRefresher by walking before applyBlock def apply(b: Block) = - val existing = m.keySet.toSet SymbolRefresherWalker(m).applyBlock(b) - m.foreach: - case (source: MemberSymbol, target: MemberSymbol) if !existing(source) => - target.copyOverrideLinksFrom(source)(using SymbolRefresher.initSymbolSubst(m)) - case _ => () applyBlock(b) // Although the types created during walking can always be symbol substituted, the user may pass in extra symbol that map across different types diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala index 814b3d1611..daa8bfa601 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Inheritance.scala @@ -13,9 +13,6 @@ import codegen.Erasure private[semantics] final class OverrideLinks extends Host[TermSymbol]: def showDbg(using DebugPrinter): Str = "overridden members" def members(using Erasure): Ls[TermSymbol] = shapes.toList - def copyFrom(source: OverrideLinks)(using SymbolSubst): Unit = - assert(shapes.isEmpty, "Override links must be copied onto a fresh symbol") - shapes ++= source.shapes.iterator.map(_.subst) object Inheritance: def hasModifier(d: Definition, keyword: Keyword.Modifier): Bool = d.annotations.exists: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index eac4155798..1b14bc5846 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -393,12 +393,11 @@ sealed abstract class MemberSymbol(using State) extends Symbol: /** Resolution publishes direct override edges; only resolver subscriptions may observe the * growing graph. Erasure/lowering read the completed declaration identities, never value flow. * The host uses the same consumer-local copies as other inference publishers on imported code. + * These edges belong to source declarations and are consumed before/while lowering. MIR + * symbol refreshing does not copy them: lowered selections already contain their chosen target. */ private[semantics] val overrideLinks: OverrideLinks = new OverrideLinks def overriddenMembers(using codegen.Erasure): Ls[TermSymbol] = overrideLinks.members - // IR duplication copies edges only after all fresh symbols have been allocated. - private[hkmc2] def copyOverrideLinksFrom(source: MemberSymbol)(using SymbolSubst): Unit = - overrideLinks.copyFrom(source.overrideLinks) class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.Ident)(using State) diff --git a/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls b/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls index c21ab4f447..1da30f957a 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls @@ -139,7 +139,7 @@ foo(2) //│ ╔══[WARNING] Objects are not yet lifted. //│ ║ l.129: object Foo with //│ ╙── ^^^ -//│ ═══[WARNING] Cannot yet lift definition `Bar` as it extends an unliftable class. +//│ ═══[WARNING] Cannot yet lift definition `Bar` as it extends an expression. //│ ═══[RUNTIME ERROR] TypeError: Class extends value [object Object] is not a constructor or null // `h` is lifted out, but then cannot access the BlockMemberSymbol M. diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index 352d3ee422..cd2c42c7b2 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -155,8 +155,9 @@ class D extends id(C) //│ ║ l.149: class D extends id(C) //│ ╙── ^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let D1; -//│ (class D extends Predef.id { +//│ let D1, superBase; +//│ superBase = Predef.id; +//│ (class D extends superBase { //│ static { //│ D1 = this //│ } @@ -180,14 +181,14 @@ let x = 0 :fixme set x += 1; () //│ ╔══[COMPILATION ERROR] Expected a right-hand side for this assignment -//│ ║ l.181: set x += 1; () +//│ ║ l.182: set x += 1; () //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e set (x += 1; ()) //│ ╔══[COMPILATION ERROR] Expected a right-hand side for this assignment -//│ ║ l.188: set (x += 1; ()) +//│ ║ l.189: set (x += 1; ()) //│ ╙── ^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -196,7 +197,7 @@ set (x += 1; ()) :fixme [x, set x += 1; x] //│ ╔══[COMPILATION ERROR] Expected a right-hand side for this assignment -//│ ║ l.197: [x, set x += 1; x] +//│ ║ l.198: [x, set x += 1; x] //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -207,13 +208,13 @@ import "../../mlscript-compile/Stack.mls" // The parser rejects this, but it should be allowed. open Stack { ::, Nil } //│ ╔══[COMPILATION ERROR] Illegal 'open' statement shape. -//│ ║ l.208: open Stack { ::, Nil } +//│ ║ l.209: open Stack { ::, Nil } //│ ╙── ^^^^^^^^^^^^^^^ // Instead, if we parenthesize the operator, it is rejected by the elaborator. open Stack { (::), Nil } //│ ╔══[COMPILATION ERROR] Illegal 'open' statement element. -//│ ║ l.214: open Stack { (::), Nil } +//│ ║ l.215: open Stack { (::), Nil } //│ ╙── ^^^^ open Stack { Nil, :: } @@ -239,46 +240,46 @@ id(Foo)(1) :e data class FooSpd(...args) //│ ╔══[COMPILATION ERROR] Spread parameters are not supported in class parameters. -//│ ║ l.240: data class FooSpd(...args) +//│ ║ l.241: data class FooSpd(...args) //│ ╙── ^^^^^^^ FooSpd(1, 2, 3).args //│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 3 -//│ ║ l.245: FooSpd(1, 2, 3).args +//│ ║ l.246: FooSpd(1, 2, 3).args //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: Access to required field 'args' yielded 'undefined' :todo if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╔══[COMPILATION ERROR] Unrecognized pattern (spread). -//│ ║ l.252: if FooSpd(1, 2, 3) is FooSpd(...args) then args +//│ ║ l.253: if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: args -//│ ║ l.252: if FooSpd(1, 2, 3) is FooSpd(...args) then args +//│ ║ l.253: if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 3 -//│ ║ l.252: if FooSpd(1, 2, 3) is FooSpd(...args) then args +//│ ║ l.253: if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] The constructor does not take any arguments but found one argument. -//│ ║ l.252: if FooSpd(1, 2, 3) is FooSpd(...args) then args +//│ ║ l.253: if FooSpd(1, 2, 3) is FooSpd(...args) then args //│ ╙── ^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] //│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 3 -//│ ║ l.267: if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] +//│ ║ l.268: if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] The constructor does not take any arguments but found three arguments. -//│ ║ l.267: if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] +//│ ║ l.268: if FooSpd(1, 2, 3) is FooSpd(a, b, c) then [a, b, c] //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error if FooSpd(1, 2, 3) is FooSpd(arg) then arg //│ ╔══[COMPILATION ERROR] Expected 0 arguments, got 3 -//│ ║ l.276: if FooSpd(1, 2, 3) is FooSpd(arg) then arg +//│ ║ l.277: if FooSpd(1, 2, 3) is FooSpd(arg) then arg //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] The constructor does not take any arguments but found one argument. -//│ ║ l.276: if FooSpd(1, 2, 3) is FooSpd(arg) then arg +//│ ║ l.277: if FooSpd(1, 2, 3) is FooSpd(arg) then arg //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -289,10 +290,10 @@ fun foo() = let bar(x: Str): Str = x + x bar("test") //│ ╔══[COMPILATION ERROR] Unsupported let binding shape -//│ ║ l.289: let bar(x: Str): Str = x + x +//│ ║ l.290: let bar(x: Str): Str = x + x //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: bar -//│ ║ l.290: bar("test") +//│ ║ l.291: bar("test") //│ ╙── ^^^ // ——— ——— ——— @@ -306,7 +307,7 @@ module Foo(x) with print(x) //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.305: module Foo(x) with +//│ ║ l.306: module Foo(x) with //│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo5; @@ -324,7 +325,7 @@ module Foo(x) with //│ }); //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.305: module Foo(x) with +//│ ║ l.306: module Foo(x) with //│ ╙── ^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > 1 @@ -334,7 +335,7 @@ module Foo(x) with module Foo(val x) //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.334: module Foo(val x) +//│ ║ l.335: module Foo(val x) //│ ╙── ^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let Foo7; @@ -350,7 +351,7 @@ module Foo(val x) //│ }); //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.334: module Foo(val x) +//│ ║ l.335: module Foo(val x) //│ ╙── ^ //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -359,7 +360,7 @@ module Foo(val x) // TODO support syntax? data class Foo(mut x) //│ ╔══[COMPILATION ERROR] Expected a valid parameter, found 'mut'-modified identifier -//│ ║ l.360: data class Foo(mut x) +//│ ║ l.361: data class Foo(mut x) //│ ╙── ^ // ——— ——— ——— @@ -368,16 +369,16 @@ data class Foo(mut x) :e class Id(name: UnboundName) //│ ╔══[COMPILATION ERROR] Name not found: UnboundName -//│ ║ l.369: class Id(name: UnboundName) +//│ ║ l.370: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.369: class Id(name: UnboundName) +//│ ║ l.370: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got ‹error› -//│ ║ l.369: class Id(name: UnboundName) +//│ ║ l.370: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Expected a type, got a non-type ‹error› -//│ ║ l.369: class Id(name: UnboundName) +//│ ║ l.370: class Id(name: UnboundName) //│ ╙── ^^^^^^^^^^^ // ——— ——— ——— diff --git a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls index d8296aac05..c7809025e1 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls @@ -82,18 +82,17 @@ class SuperBase { open fun foo(x) = x + 1 } class SuperChild extends SuperBase { override fun foo(x) = super.foo(x + 1) * 2 } (new SuperChild).foo(3) //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let superBase, tmp; +//│ let tmp; //│ define SuperBase⁰ as class SuperBase¹ { //│ method foo¹ = fun foo²(x) { //│ return +⁰(x, 1) //│ } //│ }; -//│ set superBase = SuperBase¹; //│ define SuperChild⁰ as class SuperChild¹ extends SuperBase¹ { //│ method foo³ = fun foo⁴(x) { //│ let tmp1, tmp2; //│ set tmp1 = +⁰(x, 1); -//│ set tmp2 = super[superBase](SuperChild¹.this).foo²(tmp1); +//│ set tmp2 = super[SuperBase¹](SuperChild¹.this).foo²(tmp1); //│ return *⁰(tmp2, 2) //│ } //│ }; @@ -154,23 +153,23 @@ class ForwardBase { open fun f(x) = x + 2 } :e super.foo() //│ ╔══[COMPILATION ERROR] Cannot use 'super' outside of an object scope -//│ ║ l.155: super.foo() +//│ ║ l.154: super.foo() //│ ╙── ^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. :e class NoParent { fun f() = super.missing() } //│ ╔══[COMPILATION ERROR] Resolution error in super reference -//│ ║ l.162: class NoParent { fun f() = super.missing() } +//│ ║ l.161: class NoParent { fun f() = super.missing() } //│ ║ ^^^^^^^^^^^^^ //│ ╟── No inherited member 'missing' is available through 'super' -//│ ║ l.162: class NoParent { fun f() = super.missing() } +//│ ║ l.161: class NoParent { fun f() = super.missing() } //│ ╙── ^^^^^^^^^^^^^ :e class AbstractSuper extends Abstract { fun foo() = super.foo() } //│ ╔══[COMPILATION ERROR] Super reference 'foo' requires one inherited implementation -//│ ║ l.171: class AbstractSuper extends Abstract { fun foo() = super.foo() } +//│ ║ l.170: class AbstractSuper extends Abstract { fun foo() = super.foo() } //│ ║ ^^^^^^^^^ //│ ╟── Candidate 'foo' is declared here //│ ║ l.53: abstract class Abstract { fun foo() } @@ -180,25 +179,25 @@ class AbstractSuper extends Abstract { fun foo() = super.foo() } class PrivateBase { private fun secret() = 1 } class PrivateSuper extends PrivateBase { fun f() = super.secret() } //│ ╔══[COMPILATION ERROR] Super reference 'secret' requires one inherited implementation -//│ ║ l.181: class PrivateSuper extends PrivateBase { fun f() = super.secret() } +//│ ║ l.180: class PrivateSuper extends PrivateBase { fun f() = super.secret() } //│ ║ ^^^^^^^^^^^^ //│ ╟── Candidate 'secret' is declared here -//│ ║ l.180: class PrivateBase { private fun secret() = 1 } +//│ ║ l.179: class PrivateBase { private fun secret() = 1 } //│ ╙── ^^^^^^ :e class ConcreteMissing { fun foo() } //│ ╔══[COMPILATION ERROR] Class 'ConcreteMissing' has unimplemented members -//│ ║ l.190: class ConcreteMissing { fun foo() } +//│ ║ l.189: class ConcreteMissing { fun foo() } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'foo' requires an implementation -//│ ║ l.190: class ConcreteMissing { fun foo() } +//│ ║ l.189: class ConcreteMissing { fun foo() } //│ ╙── ^^^ :e object MissingObject extends Abstract //│ ╔══[COMPILATION ERROR] Object 'MissingObject' has unimplemented members -//│ ║ l.199: object MissingObject extends Abstract +//│ ║ l.198: object MissingObject extends Abstract //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'foo' requires an implementation //│ ║ l.53: abstract class Abstract { fun foo() } @@ -207,7 +206,7 @@ object MissingObject extends Abstract :e class BadKind extends Base { override fun foo = 1 } //│ ╔══[COMPILATION ERROR] Overriding member 'foo' must preserve whether the member is a field, getter, or method -//│ ║ l.208: class BadKind extends Base { override fun foo = 1 } +//│ ║ l.207: class BadKind extends Base { override fun foo = 1 } //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Inherited member 'foo' is defined here //│ ║ l.3: class Base { open fun foo() = 1 } @@ -216,20 +215,20 @@ class BadKind extends Base { override fun foo = 1 } :e class AbstractCall() { fun f() } //│ ╔══[COMPILATION ERROR] Class 'AbstractCall' has unimplemented members -//│ ║ l.217: class AbstractCall() { fun f() } +//│ ║ l.216: class AbstractCall() { fun f() } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'f' requires an implementation -//│ ║ l.217: class AbstractCall() { fun f() } +//│ ║ l.216: class AbstractCall() { fun f() } //│ ╙── ^ :e abstract class AbstractCallable() { fun f() } AbstractCallable() //│ ╔══[COMPILATION ERROR] Cannot instantiate abstract class 'AbstractCallable' -//│ ║ l.227: AbstractCallable() +//│ ║ l.226: AbstractCallable() //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── Abstract class defined here -//│ ║ l.226: abstract class AbstractCallable() { fun f() } +//│ ║ l.225: abstract class AbstractCallable() { fun f() } //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -246,7 +245,7 @@ AbstractCallable() :e new Abstract with { fun other() = 9 } //│ ╔══[COMPILATION ERROR] Class '$anon' has unimplemented members -//│ ║ l.247: new Abstract with { fun other() = 9 } +//│ ║ l.246: new Abstract with { fun other() = 9 } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'foo' requires an implementation //│ ║ l.53: abstract class Abstract { fun foo() } @@ -256,10 +255,10 @@ new Abstract with { fun other() = 9 } :e class PrivateAbstract { private fun hidden() } //│ ╔══[COMPILATION ERROR] Class 'PrivateAbstract' has unimplemented members -//│ ║ l.257: class PrivateAbstract { private fun hidden() } +//│ ║ l.256: class PrivateAbstract { private fun hidden() } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'hidden' requires an implementation -//│ ║ l.257: class PrivateAbstract { private fun hidden() } +//│ ║ l.256: class PrivateAbstract { private fun hidden() } //│ ╙── ^^^^^^ // The nearest common ancestor is used even when a deeper ancestor is also common. @@ -271,9 +270,7 @@ private fun readDeep(x) = x.foo() [readDeep(new DeepLeft), readDeep(new DeepRight)] //│ = [5, 6] -// Captured parent values and receivers remain correct when lifting is requested. -// The lifter currently leaves first-class local parents (and their subclasses) in place. -:w +// Local classes and their super calls can be lifted while retaining captured arguments. :lift :inlineThreshold 10000 :expect [6, 14] @@ -283,8 +280,6 @@ fun local(n) = { (new LocalChild).foo(1) } [local(2), local(6)] -//│ ═══[WARNING] Cannot yet lift class `LocalBase` as it is used as a first-class class. -//│ ═══[WARNING] Cannot yet lift definition `LocalChild` as it extends an unliftable class. //│ = [6, 14] :e @@ -292,10 +287,10 @@ class Unrelated { fun foo() = 0 } private fun ambiguous(x) = x.foo() [ambiguous(new Base), ambiguous(new Unrelated)] //│ ╔══[COMPILATION ERROR] This selection of member 'foo' is ambiguous, as it has multiple resolved targets -//│ ║ l.292: private fun ambiguous(x) = x.foo() +//│ ║ l.287: private fun ambiguous(x) = x.foo() //│ ║ ^^^^^ //│ ╟── target: function 'foo' in class 'Unrelated' -//│ ║ l.291: class Unrelated { fun foo() = 0 } +//│ ║ l.286: class Unrelated { fun foo() = 0 } //│ ║ ^^^ //│ ╟── target: function 'foo' in class 'Base' //│ ║ l.3: class Base { open fun foo() = 1 } @@ -305,7 +300,7 @@ private fun ambiguous(x) = x.foo() :e class ClosedAgain extends Left { override fun foo() = 4 } //│ ╔══[COMPILATION ERROR] Cannot override non-open member 'foo' -//│ ║ l.306: class ClosedAgain extends Left { override fun foo() = 4 } +//│ ║ l.301: class ClosedAgain extends Left { override fun foo() = 4 } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Inherited member 'foo' is defined here //│ ║ l.4: class Left extends Base { override fun foo() = 2 } @@ -315,7 +310,7 @@ class ClosedAgain extends Left { override fun foo() = 4 } abstract class MissingInherited extends Abstract class StillMissing extends MissingInherited //│ ╔══[COMPILATION ERROR] Class 'StillMissing' has unimplemented members -//│ ║ l.316: class StillMissing extends MissingInherited +//│ ║ l.311: class StillMissing extends MissingInherited //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'foo' requires an implementation //│ ║ l.53: abstract class Abstract { fun foo() } @@ -347,7 +342,7 @@ class StoredParent extends parent { override fun f() = super.f() * 2 } let abstractValue = Abstract new abstractValue //│ ╔══[COMPILATION ERROR] Cannot instantiate abstract class 'Abstract' -//│ ║ l.348: new abstractValue +//│ ║ l.343: new abstractValue //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Abstract class defined here //│ ║ l.53: abstract class Abstract { fun foo() } @@ -359,10 +354,10 @@ new abstractValue let abstractCtor = AbstractCallable abstractCtor() //│ ╔══[COMPILATION ERROR] Cannot instantiate abstract class 'AbstractCallable' -//│ ║ l.360: abstractCtor() +//│ ║ l.355: abstractCtor() //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Abstract class defined here -//│ ║ l.226: abstract class AbstractCallable() { fun f() } +//│ ║ l.225: abstract class AbstractCallable() { fun f() } //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ abstractCtor = fun AbstractCallable { class: class AbstractCallable } @@ -371,7 +366,7 @@ abstractCtor() let abstractParent = Abstract class StoredIncomplete extends abstractParent //│ ╔══[COMPILATION ERROR] Class 'StoredIncomplete' has unimplemented members -//│ ║ l.372: class StoredIncomplete extends abstractParent +//│ ║ l.367: class StoredIncomplete extends abstractParent //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'foo' requires an implementation //│ ║ l.53: abstract class Abstract { fun foo() } @@ -414,10 +409,10 @@ abstract class PrivateRequirement { private fun hidden() } :e class InheritedPrivateRequirement extends PrivateRequirement { fun hidden() = 1 } //│ ╔══[COMPILATION ERROR] Class 'InheritedPrivateRequirement' has unimplemented members -//│ ║ l.415: class InheritedPrivateRequirement extends PrivateRequirement { fun hidden() = 1 } +//│ ║ l.410: class InheritedPrivateRequirement extends PrivateRequirement { fun hidden() = 1 } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Member 'hidden' requires an implementation -//│ ║ l.412: abstract class PrivateRequirement { private fun hidden() } +//│ ║ l.407: abstract class PrivateRequirement { private fun hidden() } //│ ╙── ^^^^^^ // Different receiver classes can inherit the same declaration without overriding it. @@ -429,3 +424,33 @@ private fun readInherited(flag) = { } [readInherited(true), readInherited(false)] //│ = [1, 1] + +// Reassigning a stored class cannot change a previously defined superclass. +// The resolver still assumes one contextual parent shape per class; reassignment violates +// its constructor-shape cache assertion before lowering. The runtime result remains correct. +:fixme +:expect 4 +let changingParent = makeParent(4) +class SavedParent extends changingParent { override fun f() = super.f() } +set changingParent = makeParent(9) +(new SavedParent).f() +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2838' +//│ ╟── The compilation result may be incorrect. +//│ ╙── This is a compiler bug; please report it to the maintainers. +//│ = 4 +//│ changingParent = class Parent + +// For a nested class, its receiver must likewise be saved at the extends clause. +// This also encounters the constructor-shape cache limitation above. +:fixme +:expect 5 +class Holder(n) { class Parent { open fun f() = n } } +let changingHolder = Holder(5) +class SavedNestedParent extends changingHolder.Parent { override fun f() = super.f() } +set changingHolder = Holder(11) +(new SavedNestedParent).f() +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2838' +//│ ╟── The compilation result may be incorrect. +//│ ╙── This is a compiler bug; please report it to the maintainers. +//│ = 5 +//│ changingHolder = Holder(_) From 8b96b65fc40031c1c20c1f90e4652758664089df Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 3 Oct 2026 17:48:44 +0800 Subject: [PATCH 216/228] Reorganize term impl. definitions and make small cleanups --- .../src/main/scala/hkmc2/semantics/Term.scala | 537 +---------------- .../main/scala/hkmc2/semantics/TermImpl.scala | 546 +++++++++++++++++- 2 files changed, 545 insertions(+), 538 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 50dc2ee301..3e5cddf695 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -128,6 +128,7 @@ end AppImpl type Resolvable = Term & ResolvableImpl +// TODO: remove when the new resolution migration is completed. sealed trait ResolvableImpl extends ShapeHost, PossiblyErroneous: this: Term => @@ -289,11 +290,6 @@ object Resolvable: defn, )) -trait LeadingDotSelImpl(using State): - self: Term.LeadingDotSel => - val resSym: FlowSymbol = FlowSymbol.lds(self.nme.name) - var resolvedTargets: Ls[flow.SelectionTarget.CompanionMember] = Nil // * filled during flow analysis - case class SrcScope(outer: Elaborator.OuterCtx, parent: Opt[SrcScope]): /** Computes the outermost scope from which the current scope can still be accessed. @@ -330,46 +326,6 @@ object SrcScope: given s: Ctx => SrcScope = summon[Ctx].scope -type AnyRef_ = AnyRefImpl & Term - -sealed trait AnyRefImpl: - self: Term.Ref | Term.SimpleRef | Term.MemberRef | Term.SelfRef => - def tree: Tree.Ident - def sym: Symbol - -sealed trait NewRefImpl extends AnyRefImpl: - self: Term.SimpleRef | Term.MemberRef | Term.SelfRef => - // def tree: Tree.Ident = Tree.Dummy - def refNum: Int = 0 // TODO - -sealed trait NewSelImpl extends NewResolvableImpl: - self: Term.NewSel => - // At least one receiver permits runtime lookup without a static member symbol. - var hasDynamicTarget: Bool = false - // Resolution records a numeric tuple projection; lowering emits an indexed - // access rather than looking for a nominal field symbol. - var tupleIndex: Opt[Int] = N - var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution - // Unlike a field whose value happens to be a class, C.class has a class - // target without an ordinary member. Its receiver remains the class reference. - def isClassValue(using Erasure): Bool = - !isErroneous && self.cls.isEmpty && self.id.name == "class" && resolvedMembers.isEmpty && - !hasDynamicTarget && resolvedTargets.exists(_.isInstanceOf[ClassSymbol]) - def hasAmbiguousClass(using Erasure): Bool = hasAmbiguousClassImpl - // Also used by the guarded symbol lookup for completed imports. - private[semantics] def hasAmbiguousClassImpl: Bool = projectionClassesImpl.sizeCompare(1) > 0 - def projectionClasses(using Erasure): Ls[ClassSymbol] = projectionClassesImpl - private def projectionClassesImpl: Ls[ClassSymbol] = self.cls.toList.flatMap: qualifier => - qualifier.resolvedTargets.collect { case cls: ClassSymbol => cls }.distinct - - -sealed trait UnresolvedRefImpl extends NewResolvableImpl: - self: Term.UnresolvedRef => - // Retain the receiver as well as the definition: two instances can expose - // the same member symbol without denoting the same storage location. - var resolvedMembers: Ls[(Term, BlockMemberSymbol)] = Nil - var dynamicPrefixes: Ls[Term] = Nil - enum Term extends Statement, AutoLocated, ShapePublisher: /** Filled by the type interpreter during elaboration; erasure validates the completed result. */ @@ -816,479 +772,9 @@ object ShowCfg: end ShowCfg -// TODO: mv to other file -trait Describable: - def describe: Str +sealed trait Statement extends StatementImpl -sealed trait Statement extends Located, ProductWithExtraInfo, Describable: - - def mkClone(using State, Erasure): Statement = this match - case t: Term => lastWords(s"overridden implementation") - case d: Definition => ??? - case imp: Import => Import(imp.sym, imp.str, imp.file)(imp.toLoc) - case LetDecl(sym, annotations) => LetDecl(sym, annotations.map(_.mkClone))(toLoc) - case RcdField(field, rhs, sym) => RcdField(field.mkClone, rhs.mkClone, sym) - case RcdSpread(rcd) => RcdSpread(rcd.mkClone) - case DefineVar(sym, rhs) => DefineVar(sym, rhs.mkClone)(toLoc) - case sc: SetConfig => sc - - def describe: Str = - val desc = this match - case Error() => "‹error›" - case UnitVal() => "unit value" - case DynTy() => "dynamic type" - case _: Rcd => "record literal" - case Lit(lit) => lit.describeLit - case Ref(sym) => "reference" - case Capture(base, thru) => base.describe - case SimpleRef(sym) => "reference" - case MemberRef(sym) => "member reference" - case UnresolvedRef(_, _) => "wildcard-open reference" - case App(lhs, rhs) => "application" - case TyApp(lhs, targs) => "type application" - case _: Super => "super reference" - case NewSel(pre, nme, _) => "selection" - case Sel(pre, nme) => "selection" - case SynthSel(pre, nme) => "selection" - case DynSel(o, f, _, checked) => if checked then "array index" else "dynamic selection" - case Tup(fields) => "tuple literal" - case CtxTup(fields) => "contextual tuple literal" - case IfLike(_, IfLikeForm.ReturningIf, body) => "`if` expression" - case IfLike(_, IfLikeForm.ImperativeIf, body) => "`if` statement" - case IfLike(_, IfLikeForm.While, body) => "`while` statement" - case SynthIf(split) => "synthetic `if` expression" - case SynthWhile(split) => "synthetic `while` expression" - case Lam(params, body) => "function literal" - case FunTy(lhs, rhs, eff) => "function type" - case Forall(tvs, outer, body) => "universal quantification" - case Constrained(constraints, body) => "constrained type" - case WildcardTy(in, out) => "wildcard type" - case Blk(stats, res) => "block" - case Quoted(term) => "quoted term" - case Unquoted(term) => "unquoted term" - case New(cls, args, rft) => "object instantiation" - case DynNew(cls, args) => "dynamic object instantiation" - case SelProj(pre, cls, proj) => "field selection" - case Asc(term, ty) => "type ascription" - case CompType(lhs, rhs, pol) => if pol then "alternation" else "composition" - case Neg(rhs) => "negation type" - case Region(name, body) => "region expression" - case RegRef(reg, value) => "reference creation" - case Assgn(lhs, rhs) => "assignment" - case SetRef(ref, value) => "mutable reference assignment" - case Drop(ref) => "drop" - case Deref(ref) => "dereference" - case Throw(e) => "throw" - case Label(label, _, _, _) => s"label '${label.nme}'" - case Break(label, _, _) => s"break to label '${label.nme}'" - case Continue(label) => s"continue to label '${label.nme}'" - case Annotated(annotation, target) => "annotation" - case Ret(res) => "return" - case Try(body, finallyDo) => "try expression" - case _: Handle => "handler expression" - case Missing => "missing" - case LeadingDotSel(name) => "leading dot selection" - case Resolved(t, sym) => t.describe - case td: TermDefinition => s"term definition '${td.sym.nme}'" - case cls: ClassDef => s"class definition '${cls.sym.nme}'" - case mod: ModuleOrObjectDef => s"module/object definition '${mod.sym.nme}'" - case td: TypeDef => s"type definition '${td.bsym.nme}'" - case s => TODO(s) - this match - case self: Resolvable => self.resolvedTyp match - case S(typ) => s"${desc} of type ${typ.show}" - case N => desc - case _ => desc - - def extraInfo(using DebugPrinter): Str = this match - case s: AnySel if s.resolvedTargets.nonEmpty => - s"targets=${s.resolvedTargets.map(_.showAsPlain).mkString("[", ",", "]")}" - case r: Resolvable if r.legacyResolvedSym.isDefined || r.resolvedTyp.isDefined => ( - r.legacyResolvedSym.map(s => s"sym=${s.showAsPlain}") :: - r.resolvedTyp.map(s => s"typ=${s.showDbg}") :: Nil - ).flatten.mkString(",") - case r: SelProj => r.symbol.mkString - case _ => "" - - def subStatements: Vector[Statement] = this match - case Blk(stats, res) => stats.toVector :+ res - case _ => subTerms - def subTerms: Vector[Term] = this match - case Error() | Missing | _: Lit | _: AnyRef_ | _: DynTy | _: UnitVal => Vector.empty - case Capture(base, thru) => Vector.single(base) - case Resolved(t, sym) => Vector.single(t) - case App(lhs, rhs) => Vector.double(lhs, rhs) - case RcdField(lhs, rhs, _) => Vector.double(lhs, rhs) - case RcdSpread(bod) => Vector.single(bod) - case FunTy(lhs, rhs, eff) => Vector.double(lhs, rhs) ++ eff.toVector - case TyApp(pre, tarsg) => pre +: tarsg.toVector - case Sel(pre, _) => Vector.single(pre) - case SynthSel(pre, _) => Vector.single(pre) - case Super(receiver, _, _) => Vector.single(receiver) - case NewSel(pre, _, cls) => Vector.single(pre) ++ cls.toVector - case UnresolvedRef(prefixes, _) => prefixes.toVector - case DynSel(o, f, _, _) => Vector.double(o, f) - case Tup(fields) => fields.flatMap(_.subTerms).toVector - case Mut(und) => Vector.single(und) - case CtxTup(fields) => fields.flatMap(_.subTerms).toVector - case IfLike(_, _, split) => split.subTerms - case SynthIf(split) => split.subTerms - case SynthWhile(split) => split.subTerms - case Lam(params, body) => params.allParams.iterator.flatMap(_.sign).toVector :+ body - case Blk(stats, res) => stats.flatMap(_.subTerms).toVector :+ res - case Rcd(mut, stats) => stats.flatMap(_.subTerms).toVector - case Quoted(term) => Vector.single(term) - case Unquoted(term) => Vector.single(term) - case New(cls, args, rft) => (cls +: args.toVector) ++ rft.toVector.flatMap(_._2.blk.subTerms) - case DynNew(cls, args) => cls +: args.toVector - case SelProj(pre, cls, _) => Vector.double(pre, cls) - case Asc(term, ty) => Vector.double(term, ty) - case Ret(res) => Vector.single(res) - case Throw(res) => Vector.single(res) - case Label(_, _, body, _) => Vector.single(body) - case Break(_, _, value) => value.toVector - case Continue(_) => Vector.empty - case Forall(_, _, body) => Vector.single(body) - case Constrained(constraints, body) => constraints.flatMap(_.subTerms).toVector :+ body - case WildcardTy(in, out) => in.toVector ++ out.toVector - case CompType(lhs, rhs, _) => Vector.double(lhs, rhs) - case LetDecl(sym, annotations) => annotations.flatMap(_.subTerms).toVector - case DefineVar(sym, rhs) => Vector.single(rhs) - case Region(_, body) => Vector.single(body) - case RegRef(reg, value) => Vector.double(reg, value) - case Assgn(lhs, rhs) => Vector.double(lhs, rhs) - case SetRef(lhs, rhs) => Vector.double(lhs, rhs) - case Drop(term) => Vector.single(term) - case Deref(term) => Vector.single(term) - case TermDefinition(_, _, _, pss, tps, sign, body, _, _, annotations, _) => - pss.toVector.flatMap(_.subTerms) ++ tps.getOrElse(Nil).flatMap(_.subTerms).toVector ++ sign.toVector ++ body.toVector ++ annotations.flatMap(_.subTerms).toVector - case cls: ClassDef => - (cls.paramsOpt.toVector.flatMap(_.subTerms) :+ cls.body.blk) ++ cls.annotations.flatMap(_.subTerms).toVector - case mod: ModuleOrObjectDef => - ( mod.paramsOpt.toVector.flatMap(_.subTerms) :+ mod.body.blk) ++ mod.annotations.flatMap(_.subTerms).toVector - case td: TypeDef => - td.rhs.toVector ++ td.annotations.flatMap(_.subTerms).toVector - case pat: PatternDef => - (pat.paramsOpt.toVector.flatMap(_.subTerms) :+ pat.body.blk) ++ pat.annotations.flatMap(_.subTerms).toVector - case Import(sym, str, pth) => Vector.empty - case Try(body, finallyDo) => Vector.single(body) ++ Vector.single(finallyDo) - case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => (rhs +: args.toVector) ++ defs.flatMap(_.td.subTerms).toVector :+ bod - case Neg(e) => Vector.single(e) - case Annotated(ann, target) => ann.subTerms ++ Vector.single(target) - case LeadingDotSel(nme) => Vector.empty - case SetConfig(_) => Vector.empty - - // private def treeOrSubterms(t: Tree, t: Term): Ls[Located] = t match - private def treeOrSubterms(t: Tree): Vector[Located] = t match - case Tree.DummyApp | Tree.DummyTup => subTerms - case _ => Vector.single(t) - - protected def children: Vector[Located] = this match - case t: Lit => Vector.single(t.lit.asTree) - case t: AnyRef_ => treeOrSubterms(t.tree) - case t: Tup => treeOrSubterms(t.tree) - case l: Lam => Vector.double(l.params, l.body) - case t: App => treeOrSubterms(t.tree) - case IfLike(_, _, split) => Vector.single(split) - case SynthIf(split) => Vector.single(split) - case SynthWhile(split) => Vector.single(split) - case SynthSel(pre, nme) => Vector.double(pre, nme) - case Sel(pre, nme) => Vector.double(pre, nme) - case SelProj(prefix, cls, proj) => Vector.triple(prefix, cls, proj) - case _ => - subTerms // TODO more precise (include located things that aren't terms) - - def show(using Scope, ShowCfg, Raise): Document = - - def showSelTargets(sel: AnySel): Document = - val str = sel.nme.name - val ts = sel.resolvedTargets - ts match - case Nil => - // doc"$str‹?›" - doc"${str}ˀˀˀ" - case t :: Nil => t.show - case ts => doc"$str‹" :: ts.map(_.show).mkDocument(", ") :: doc"›" - - def res: Document = this match - case lit: Lit => lit.lit.idStr - case UnitVal() => doc"()" - case DynTy() => doc"dyn" - case r: SimpleRef => - r.sym match - case _: BuiltinSymbol => r.sym.nme - case _ => r.sym.showName - case r: MemberRef => r.sym.showName - case r: UnresolvedRef => doc"${r.id.name}" - case sr: SelfRef => s"${sr.sym.showName}.this" - case Capture(base, thru) => - // doc"${base.show}⟨${thru.showName}⟩" - doc"${base.show}^${thru.showName}" - case r: Ref => - r.sym match - case _: BuiltinSymbol => r.sym.nme - case _ => r.sym.showName - case sup: Super => doc"super.${sup.id.name}" - case sel: NewSel => - val str = sel.id.name - val pre = sel.cls.fold(doc"${sel.prefix.show}.")(cls => doc"${sel.prefix.show}.${cls.show}#") - if summon[ShowCfg].showFlowSymbols - then doc"$pre${ - sel.resolvedMembers match - case Nil if sel.resolvedTargets.nonEmpty => - doc"$str‹" :: sel.resolvedTargets.distinct.map(_.showName).mkDocument(", ") :: doc"›" - case Nil => doc"${str}ˀˀˀ" - case t :: Nil => t.showName - case ts => doc"$str‹" :: ts.map(_.showName).mkDocument(", ") :: doc"›" - }" - else doc"$pre$str" - case sel: Sel => - if summon[ShowCfg].showFlowSymbols - then doc"${sel.prefix.show}.${sel.sym.fold(doc"${ - showSelTargets(sel)}")(_.showName)}" - else doc"${sel.prefix.show}.${sel.nme.name}" - case sel: SynthSel => - if summon[ShowCfg].showFlowSymbols - then doc"⟨${sel.prefix.show}.⟩${sel.sym.fold(doc"${ - showSelTargets(sel)}")(_.showName)}" - else doc"${sel.prefix.show}.${sel.nme.name}" - case Resolved(trm, sym) => - trm.show - case app: App => - doc"${app.lhs.show}${app.rhs.showAsParams}${ - if summon[ShowCfg].showFlowSymbols - then - summon[ShowCfg].shownSymbols.add(app.resSym) - "‹" :: app.resSym.showPlainName :: "›" - else "" - }" - case lam: Lam => doc"${lam.params.show} => ${lam.body.show}" - case nw: New => doc"new ${nw.cls.show}${nw.args.map(_.showAsParams).mkDocument()}${ - nw.rft.fold(doc"")(doc" with " :: _._2.blk.show)}" - case tup: Tup => bracketed("[", "]", insertBreak = true): - tup.fields.map(_.show).mkDocument(doc", # ") - case blk: Blk => braced: - doc" # " :: (blk.stats ::: - blk.res.match - case Lit(Tree.UnitLit(false)) => Nil - case res => res :: Nil - ).map(_.show).mkDocument(doc", # ") - case ld: LetDecl => - (ld.annotations.map(_.show :: " ") ::: doc"let ${ld.sym.showName}" :: Nil).mkDocument() - case df: DefineVar => - doc"${df.sym.showName} = ${df.rhs.show}" - case td: TermDefinition => - td.annotations.map(_.show :: " ").mkDocument() - :: doc"${td.k.str} ${td.bsym.showName}::${td.tsym.showName}" - :: (if td.tparams.isEmpty then doc"" - else doc"[${td.tparams.get.map(_.sym.showName).mkDocument(", ")}]") - :: td.params.map(_.show).mkDocument() - :: td.sign.fold(doc"")(s => doc": ${s.show}") - :: (if summon[ShowCfg].showFlowSymbols then doc" ‹${td.bsym.flow.showName}›" else doc"") - :: td.body.fold(doc"")(b => doc" = ${b.show}") - case cld: ClassLikeDef => - cld.annotations.map(_.show :: " ").mkDocument() - :: doc"${cld.ctorSym.fold(doc"")("fun "::_.showName::doc" # ")}${cld.kind.str} ${cld.bsym.showName}::${cld.sym.showName}" - :: (if cld.tparams.isEmpty then doc"" - else doc"[${cld.tparams.map(_.sym.showName).mkDocument(", ")}]") - :: cld.paramsOpt.map(_.show).toList.mkDocument() - :: cld.auxParams.map(_.show).mkDocument() - :: cld.ext.fold(doc"")(e => doc" extends ${e.show}") - :: doc" ${cld.body.blk.show}" - case imp: Import => - doc"import ${"\""}.../${imp.file.last}${"\""} as ${imp.sym.showName}" - case LeadingDotSel(name) => doc"_?_.${name.name}" - case Error() => doc"‹error›" - case IfLike(kw, form, split) => - // doc"${kw.name} ${form.headStr} ${split.show}" - given IfLikeForm = form - doc"${form.headStr} { #{ # ${split.show} #} # }" - // val fs = fomr match - case Missing => doc"‹missing›" - case TyApp(lhs, targs) => doc"${lhs.show}[${targs.map(_.show).mkDocument(", ")}]" - case DynSel(prefix, field, arrayIdx, _) => - if arrayIdx then doc"${prefix.show}.[${field.show}]" - else doc"${prefix.show}.(${field.show})" - case SelProj(prefix, cls, field) => doc"${prefix.show}.${cls.show}#${field.name}" - case Mut(underlying) => doc"mut ${underlying.show}" - case CtxTup(fields) => doc"using (${fields.map(_.show).mkDocument(", ")})" - case SynthIf(split) => doc"if { #{ # ${split.show} #} # }" - case SynthWhile(split) => doc"while { #{ # ${split.show} #} # }" - case FunTy(lhs, rhs, eff) => - doc"(${lhs.showAsParams} ->${eff.fold(doc"")(e => doc"{${e.show}}")} ${rhs.show})" - case Forall(tvs, outer, body) => - val variables = tvs.map: tv => - tv.sym.showName - :: tv.lb.fold(doc"")(b => doc" :> ${b.show}") - :: tv.ub.fold(doc"")(b => doc" <: ${b.show}") - doc"forall ${variables.mkDocument(", ")}${outer.fold(doc"")(v => doc", outer ${v.showName}")}: ${body.show}" - case Constrained(constraints, body) => - val bounds = constraints.map(c => doc"${c.lhs.show} ${c.dir.showDbg} ${c.rhs.show}") - doc"[${bounds.mkDocument(", ")}] => ${body.show}" - case WildcardTy(in, out) => - doc"in ${in.fold(doc"⊥")(_.show)} out ${out.fold(doc"⊤")(_.show)}" - case Rcd(mut, stats) => - (if mut then doc"mut " else doc"") :: braced: - doc" # " :: stats.map(_.show).mkDocument(doc", # ") - case RcdField(field, rhs, _) => doc"${field.show}: ${rhs.show}" - case RcdSpread(record) => doc"...${record.show}" - case Quoted(body) => doc"""code"${body.show}"""" - case Unquoted(body) => doc"$${${body.show}}" - case DynNew(cls, args) => doc"new! ${cls.show}${args.map(_.showAsParams).mkDocument()}" - case Asc(term, ty) => doc"(${term.show}: ${ty.show})" - case CompType(lhs, rhs, pol) => doc"(${lhs.show} ${if pol then "|" else "&"} ${rhs.show})" - case Neg(rhs) => doc"~(${rhs.show})" - case Region(name, body) => doc"region ${name.showName} in ${body.show}" - case RegRef(reg, value) => doc"(${reg.show}).ref ${value.show}" - case Assgn(lhs, rhs) => doc"${lhs.show} := ${rhs.show}" - case SetRef(ref, value) => doc"${ref.show} := ${value.show}" - case Drop(term) => doc"drop ${term.show}" - case Deref(ref) => doc"!(${ref.show})" - case Ret(result) => doc"return ${result.show}" - case Throw(result) => doc"throw ${result.show}" - case Label(label, _, body, _) => doc"do ${label.showName}: ${body.show}" - case Break(label, _, value) => doc"${label.showName}.break${value.fold(doc"")(v => doc" ${v.show}")}" - case Continue(label) => doc"${label.showName}.continue" - case Try(body, finallyDo) => doc"try ${body.show} finally ${finallyDo.show}" - case Annotated(annot, target) => doc"${annot.show} ${target.show}" - case Handle(lhs, rhs, args, _, defs, body) => - doc"handle ${lhs.showName} = ${rhs.show}${args.map(_.showAsParams).mkDocument()} with { #{ # ${ - defs.map(_.td.show).mkDocument(doc" # ")} #} # } in ${body.show}" - case td: TypeDef => - td.annotations.map(_.show :: " ").mkDocument() - :: doc"type ${td.bsym.showName}::${td.sym.showName}" - :: (if td.tparams.isEmpty then doc"" - else doc"[${td.tparams.map(_.sym.showName).mkDocument(", ")}]") - :: td.rhs.fold(doc"")(rhs => doc" = ${rhs.show}") - case SetConfig(_) => doc"#config(...)" - - this match - case t: Resolvable => t.expansion match - case S(S(exp)) => - val rhs = exp.show(using summon, summon[ShowCfg].copy(showExpansionMappings = false)) - if summon[ShowCfg].showExpansionMappings then - if exp === t then rhs - // ^ Some expansions only modify meta-data, such as types and symbols; - // we don't print them for conciseness - else res :: doc"{ ~> " :: rhs :: doc" }" - else exp.show - case _ => res - case _ => res - - end show - - def size: Int = this match - case Lit(Tree.StrLit(str)) => str.size / 4 + 1 - case _ => subTerms.iterator.map(_.size).sum + 1 - - def showDbg(using DebugPrinter): Str = this match - case r: Ref => r.sym.showAsPlain - case r: Resolved => - s"${r.showPlain}‹${r.sym}›" - case trm: Term => - // s"$showPlain‹${trm.symbol.getOrElse("")}›" - s"$showPlain${trm.symbol.fold("")("‹"+_+"›")}" - case _ => - showPlain - - def showAsParams(using Scope, ShowCfg, Raise): Document = this match - case tup: Tup => doc"(${tup.fields.map(_.show).mkDocument(", ")})" - case _ => doc"(...$show)" - - def showDbgAsParams(using DebugPrinter): Str = this match - case tup: Tup => s"(${tup.fields.map(_.showDbg).mkString(", ")})" - case _ => s"(...$showDbg)" - - def showPlain(using DebugPrinter): Str = this match - case Term.UnitVal() => "()" - case Term.DynTy() => "dyn" - case Lit(lit) => lit.idStr - case Resolved(t, sym) => t.showPlain - case r @ Ref(symbol) => symbol.showAsPlain - case r @ SimpleRef(symbol) => symbol.showAsPlain - case r @ MemberRef(symbol) => symbol.showAsPlain - case r @ SelfRef(sym) => sym.showAsPlain - case Capture(base, thru) => s"${base.showDbg}^${thru.showDbg}" - case App(lhs, rhs) => s"${lhs.showDbg}${rhs.showDbgAsParams}" - case RcdField(lhs, rhs, _) => s"${lhs.showDbg}: ${rhs.showDbg}" - case RcdSpread(bod) => s"...${bod.showDbg}" - case FunTy(lhs: Tup, rhs, eff) => - s"${lhs.fields.map(_.showDbg).mkString(", ")} ->${ - eff.map(e => s"{${e.showDbg}}").getOrElse("")} ${rhs.showDbg}" - case FunTy(lhs, rhs, eff) => - s"(...${lhs.showDbg}) ->${eff.map(e => s"{${e.showDbg}}").getOrElse("")} ${rhs.showDbg}" - case TyApp(lhs, targs) => s"${lhs.showDbg}[${targs.mkString(", ")}]" - case Forall(tvs, outer, body) => s"forall ${tvs.mkString(", ")}${outer.map(v => s", outer $v").mkString}: ${body.toString}" - case Constrained(constraints, body) => s"[${constraints.map(_.showDbg).mkString(", ")}] => ${body.showDbg}" - case WildcardTy(in, out) => s"in ${in.map(_.toString).getOrElse("⊥")} out ${out.map(_.toString).getOrElse("⊤")}" - case Sel(pre, nme) => s"${pre.showDbg}.${nme.name}" - case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" - case Super(_, owner, id) => s"super[${owner.showDbg}].${id.name}" - case NewSel(pre, nme, cls) => s"${pre.showDbg}.${cls.fold("")(c => s"${c.showDbg}#")}${nme.name}" - case UnresolvedRef(_, id) => s"${id.name}‹open›" - case DynSel(pre, fld, _, _) => s"${pre.showDbg}[${fld.showDbg}]" - case IfLike(kw, _, split) => s"${kw.name} { ${split.showDbg} }" - case SynthIf(split) => s"if { ${split.showDbg} }" - case SynthWhile(split) => s"while { ${split.showDbg} }" - case Lam(params, body) => s"λ${params.showDbg}. ${body.showDbg}" - case Blk(stats, res) => - (stats.map(_.showDbg + "; ") :+ (res match { case Lit(Tree.UnitLit(false)) => "" case x => x.showDbg + " " })) - .mkString("{ ", "", "}") - case Rcd(mut, stats) => - (if mut then "mut " else "") + stats.map(_.showDbg + "; ").mkString("{ ", "", "}") - case Quoted(term) => s"""code"${term.showDbg}"""" - case Unquoted(term) => s"$${${term.showDbg}}" - case New(cls, args, rft) => - s"new ${cls.showDbg}${args.map(_.showDbgAsParams).mkString}${rft.fold("")(r => s"{ ${r._2.blk.showDbg} }")}" - case DynNew(cls, args) => - s"new! ${cls.showDbg}${args.map(_.showDbgAsParams).mkString}" - case SelProj(pre, cls, proj) => s"${pre.showDbg}.${cls.showDbg}#${proj.name}" - case Asc(term, ty) => s"${term.toString}: ${ty.toString}" - case LetDecl(sym, _) => s"let ${sym}" - case DefineVar(sym, rhs) => s"${sym} = ${rhs.showDbg}" - case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => - s"handle ${lhs} = ${rhs}(${args.mkString(", ")}) ${defs} in ${bod}" - case Region(name, body) => s"region ${name.nme} in ${body.showDbg}" - case RegRef(reg, value) => s"(${reg.showDbg}).ref ${value.showDbg}" - case Assgn(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}" - case SetRef(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}" - case Drop(term) => s"drop $term" - case Deref(term) => s"!$term" - case Neg(ty) => s"~${ty.showDbg}" - case CompType(lhs, rhs, pol) => s"${lhs.showDbg} ${if pol then "|" else "&"} ${rhs.showDbg}" - case Error() => "" - case Tup(fields) => fields.map(_.showDbg).mkString("[", ", ", "]") - case Mut(und) => s"mut ${und.showDbg}" - case CtxTup(fields) => fields.map(_.showDbg).mkString("‹using›[", ", ", "]") - case TermDefinition(k, sym, tsym, pss, tps, sign, body, flags, _, _, _) => - s"${flags.showDbg}${k.str} ${sym}${ - tps.map(_.map(_.showDbg)).mkStringOr(", ", "[", "]") - }${ - pss.map(_.showDbg).mkString("") - }${sign.fold("")(": "+_.showDbg)}${ - body match - case S(x) => " = " + x.showDbg - case N => "" - }" - case cls: ClassLikeDef => - s"${cls.kind} ${cls.sym.nme}${ - cls.tparams.map(_.showDbg).mkStringOr(", ", "[", "]")}${ - cls.paramsOpt.fold("")(_.toString)} ${cls.body}" - case Import(sym, str, file) => s"import $str from ${file}" - case Annotated(ann, target) => s"@${ann} ${target.showDbg}" - case Throw(res) => s"throw ${res.showDbg}" - case Label(label, _, body, _) => s"do ${label.nme}: ${body.showDbg}" - case Break(label, _, value) => s"${label.nme}.break${value.fold("")(v => s" ${v.showDbg}")}" - case Continue(label) => s"${label.nme}.continue" - case Try(body, finallyDo) => s"try ${body.showDbg} finally ${finallyDo.showDbg}" - case Ret(res) => s"return ${res.showDbg}" - case TypeDef(sym, _, tparams, rhs, _, _) => - s"type ${sym}${tparams.mkStringOr(", ", "[", "]")} = ${rhs.fold("")(x => x.showDbg)}" - case Missing => "missing" - case LeadingDotSel(nme) => s"_?_.${nme.name}" - case SetConfig(_) => "#config(...)" - final case class LetDecl(sym: LocalVarSymbol | TermSymbol, annotations: Ls[Annot])(val toLoc: Opt[Loc]) extends Statement /** The symbol identifies the property, independently of any local binding used @@ -1838,17 +1324,6 @@ object ParamListFlags: val empty = ParamListFlags(false) -// trait FldImpl extends AutoLocated: -trait FldImpl: - self: Fld => - def children: Vector[Located] = self.term +: self.asc.toVector - def show(using Scope, ShowCfg, Raise): Document = flags.show(true) :: self.term.show - def showDbg(using DebugPrinter): Str = flags.show(true) + self.term.showDbg - def describe: Str = - (if self.flags.spec then "specialized " else "") + - (if self.flags.mut then "mutable " else "") + - self.term.describe - /** * Unwrapper that unwraps a term until it is no longer an App. */ @@ -1857,11 +1332,3 @@ object Apps: case Term.App(Apps(base, args), arg) => S(base, args :+ arg) case t => S(t, Nil) - -trait BlkImpl: - this: Blk => - def mkBlkClone(using State, Erasure): Blk = Blk(stats.map(_.mkClone), res.mkClone) - def showTopLevel(using Scope, ShowCfg, Raise): Document = - (stats ::: (res match - case Lit(Tree.UnitLit(false)) => Nil - case res => res :: Nil)).map(_.show).mkDocument(doc", # ") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala index d75c9b8914..e78ff8d981 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TermImpl.scala @@ -14,13 +14,82 @@ import Elaborator.State import hkmc2.typing.Type import hkmc2.semantics.Elaborator.{Ctx, ctx} import hkmc2.Message.MessageContext -import hkmc2.semantics.flow.{SelectionTarget, AppTarget} +import hkmc2.codegen.Erasure import Term.* -// enum ReslTaregt: -// case + +trait Describable: + def describe: Str + + +type AnyRef_ = AnyRefImpl & Term + +trait AnyRefImpl: + self: Term.Ref | Term.SimpleRef | Term.MemberRef | Term.SelfRef => + def tree: Tree.Ident + def sym: Symbol + +trait NewRefImpl extends AnyRefImpl: + self: Term.SimpleRef | Term.MemberRef | Term.SelfRef => + def refNum: Int = 0 // TODO: track reference counts like in the old refs? + +trait NewSelImpl extends NewResolvableImpl: + self: Term.NewSel => + // At least one receiver permits runtime lookup without a static member symbol. + var hasDynamicTarget: Bool = false + // Resolution records a numeric tuple projection; lowering emits an indexed + // access rather than looking for a nominal field symbol. + var tupleIndex: Opt[Int] = N + var resolvedMembers: Ls[BlockMemberSymbol] = Nil // * filled during resolution + // Unlike a field whose value happens to be a class, C.class has a class + // target without an ordinary member. Its receiver remains the class reference. + def isClassValue(using Erasure): Bool = + !isErroneous && self.cls.isEmpty && self.id.name == "class" && resolvedMembers.isEmpty && + !hasDynamicTarget && resolvedTargets.exists(_.isInstanceOf[ClassSymbol]) + def hasAmbiguousClass(using Erasure): Bool = hasAmbiguousClassImpl + // Also used by the guarded symbol lookup for completed imports. + private[semantics] def hasAmbiguousClassImpl: Bool = projectionClassesImpl.sizeCompare(1) > 0 + def projectionClasses(using Erasure): Ls[ClassSymbol] = projectionClassesImpl + private def projectionClassesImpl: Ls[ClassSymbol] = + self.cls.toList.flatMap: qualifier => + qualifier.resolvedTargets.collect { case cls: ClassSymbol => cls }.distinct + +trait UnresolvedRefImpl extends NewResolvableImpl: + self: Term.UnresolvedRef => + // Retain the receiver as well as the definition: two instances can expose + // the same member symbol without denoting the same storage location. + var resolvedMembers: Ls[(Term, BlockMemberSymbol)] = Nil + var dynamicPrefixes: Ls[Term] = Nil + + +// trait FldImpl extends AutoLocated: +trait FldImpl: + self: Fld => + def children: Vector[Located] = self.term +: self.asc.toVector + def show(using Scope, ShowCfg, Raise): Document = flags.show(true) :: self.term.show + def showDbg(using DebugPrinter): Str = flags.show(true) + self.term.showDbg + def describe: Str = + (if self.flags.spec then "specialized " else "") + + (if self.flags.mut then "mutable " else "") + + self.term.describe + + +trait BlkImpl: + this: Blk => + def mkBlkClone(using State, Erasure): Blk = Blk(stats.map(_.mkClone), res.mkClone) + def showTopLevel(using Scope, ShowCfg, Raise): Document = + (stats ::: (res match + case Lit(Tree.UnitLit(false)) => Nil + case res => res :: Nil)).map(_.show).mkDocument(doc", # ") + + +trait LeadingDotSelImpl(using State): + self: Term.LeadingDotSel => + val resSym: FlowSymbol = FlowSymbol.lds(self.nme.name) + var resolvedTargets: Ls[flow.SelectionTarget.CompanionMember] = Nil // * filled during flow analysis + type AnyResolvable = Resolvable | NewResolvableImpl // type AnyRef = Ref | SimpleRef | MemberRef @@ -38,4 +107,475 @@ trait NewResolvableImpl extends PossiblyErroneous: // var disamb: Opt[Disambiguation] = None // * filled during flow analysis +trait StatementImpl extends Located, ProductWithExtraInfo, Describable: + this: Statement => + + def mkClone(using State, Erasure): Statement = this match + case t: Term => lastWords(s"overridden implementation") + case d: Definition => ??? + case imp: Import => Import(imp.sym, imp.str, imp.file)(imp.toLoc) + case LetDecl(sym, annotations) => LetDecl(sym, annotations.map(_.mkClone))(toLoc) + case RcdField(field, rhs, sym) => RcdField(field.mkClone, rhs.mkClone, sym) + case RcdSpread(rcd) => RcdSpread(rcd.mkClone) + case DefineVar(sym, rhs) => DefineVar(sym, rhs.mkClone)(toLoc) + case sc: SetConfig => sc + + def describe: Str = + val desc = this match + case Error() => "‹error›" + case UnitVal() => "unit value" + case DynTy() => "dynamic type" + case _: Rcd => "record literal" + case Lit(lit) => lit.describeLit + case Ref(sym) => "reference" + case Capture(base, thru) => base.describe + case SimpleRef(sym) => "reference" + case MemberRef(sym) => "member reference" + case UnresolvedRef(_, _) => "wildcard-open reference" + case App(lhs, rhs) => "application" + case TyApp(lhs, targs) => "type application" + case _: Super => "super reference" + case NewSel(pre, nme, _) => "selection" + case Sel(pre, nme) => "selection" + case SynthSel(pre, nme) => "selection" + case DynSel(o, f, _, checked) => if checked then "array index" else "dynamic selection" + case Tup(fields) => "tuple literal" + case CtxTup(fields) => "contextual tuple literal" + case IfLike(_, IfLikeForm.ReturningIf, body) => "`if` expression" + case IfLike(_, IfLikeForm.ImperativeIf, body) => "`if` statement" + case IfLike(_, IfLikeForm.While, body) => "`while` statement" + case SynthIf(split) => "synthetic `if` expression" + case SynthWhile(split) => "synthetic `while` expression" + case Lam(params, body) => "function literal" + case FunTy(lhs, rhs, eff) => "function type" + case Forall(tvs, outer, body) => "universal quantification" + case Constrained(constraints, body) => "constrained type" + case WildcardTy(in, out) => "wildcard type" + case Blk(stats, res) => "block" + case Quoted(term) => "quoted term" + case Unquoted(term) => "unquoted term" + case New(cls, args, rft) => "object instantiation" + case DynNew(cls, args) => "dynamic object instantiation" + case SelProj(pre, cls, proj) => "field selection" + case Asc(term, ty) => "type ascription" + case CompType(lhs, rhs, pol) => if pol then "alternation" else "composition" + case Neg(rhs) => "negation type" + case Region(name, body) => "region expression" + case RegRef(reg, value) => "reference creation" + case Assgn(lhs, rhs) => "assignment" + case SetRef(ref, value) => "mutable reference assignment" + case Drop(ref) => "drop" + case Deref(ref) => "dereference" + case Throw(e) => "throw" + case Label(label, _, _, _) => s"label '${label.nme}'" + case Break(label, _, _) => s"break to label '${label.nme}'" + case Continue(label) => s"continue to label '${label.nme}'" + case Annotated(annotation, target) => "annotation" + case Ret(res) => "return" + case Try(body, finallyDo) => "try expression" + case _: Handle => "handler expression" + case Missing => "missing" + case LeadingDotSel(name) => "leading dot selection" + case Resolved(t, sym) => t.describe + case td: TermDefinition => s"term definition '${td.sym.nme}'" + case cls: ClassDef => s"class definition '${cls.sym.nme}'" + case mod: ModuleOrObjectDef => s"module/object definition '${mod.sym.nme}'" + case td: TypeDef => s"type definition '${td.bsym.nme}'" + case s => TODO(s) + this match + case self: Resolvable => self.resolvedTyp match + case S(typ) => s"${desc} of type ${typ.show}" + case N => desc + case _ => desc + + def extraInfo(using DebugPrinter): Str = this match + case s: AnySel if s.resolvedTargets.nonEmpty => + s"targets=${s.resolvedTargets.map(_.showAsPlain).mkString("[", ",", "]")}" + case r: Resolvable if r.legacyResolvedSym.isDefined || r.resolvedTyp.isDefined => ( + r.legacyResolvedSym.map(s => s"sym=${s.showAsPlain}") :: + r.resolvedTyp.map(s => s"typ=${s.showDbg}") :: Nil + ).flatten.mkString(",") + case r: SelProj => r.symbol.mkString + case _ => "" + + def subStatements: Vector[Statement] = this match + case Blk(stats, res) => stats.toVector :+ res + case _ => subTerms + def subTerms: Vector[Term] = this match + case Error() | Missing | _: Lit | _: AnyRef_ | _: DynTy | _: UnitVal => Vector.empty + case Capture(base, thru) => Vector.single(base) + case Resolved(t, sym) => Vector.single(t) + case App(lhs, rhs) => Vector.double(lhs, rhs) + case RcdField(lhs, rhs, _) => Vector.double(lhs, rhs) + case RcdSpread(bod) => Vector.single(bod) + case FunTy(lhs, rhs, eff) => Vector.double(lhs, rhs) ++ eff.toVector + case TyApp(pre, tarsg) => pre +: tarsg.toVector + case Sel(pre, _) => Vector.single(pre) + case SynthSel(pre, _) => Vector.single(pre) + case Super(receiver, _, _) => Vector.single(receiver) + case NewSel(pre, _, cls) => Vector.single(pre) ++ cls.toVector + case UnresolvedRef(prefixes, _) => prefixes.toVector + case DynSel(o, f, _, _) => Vector.double(o, f) + case Tup(fields) => fields.flatMap(_.subTerms).toVector + case Mut(und) => Vector.single(und) + case CtxTup(fields) => fields.flatMap(_.subTerms).toVector + case IfLike(_, _, split) => split.subTerms + case SynthIf(split) => split.subTerms + case SynthWhile(split) => split.subTerms + case Lam(params, body) => params.allParams.iterator.flatMap(_.sign).toVector :+ body + case Blk(stats, res) => stats.flatMap(_.subTerms).toVector :+ res + case Rcd(mut, stats) => stats.flatMap(_.subTerms).toVector + case Quoted(term) => Vector.single(term) + case Unquoted(term) => Vector.single(term) + case New(cls, args, rft) => (cls +: args.toVector) ++ rft.toVector.flatMap(_._2.blk.subTerms) + case DynNew(cls, args) => cls +: args.toVector + case SelProj(pre, cls, _) => Vector.double(pre, cls) + case Asc(term, ty) => Vector.double(term, ty) + case Ret(res) => Vector.single(res) + case Throw(res) => Vector.single(res) + case Label(_, _, body, _) => Vector.single(body) + case Break(_, _, value) => value.toVector + case Continue(_) => Vector.empty + case Forall(_, _, body) => Vector.single(body) + case Constrained(constraints, body) => constraints.flatMap(_.subTerms).toVector :+ body + case WildcardTy(in, out) => in.toVector ++ out.toVector + case CompType(lhs, rhs, _) => Vector.double(lhs, rhs) + case LetDecl(sym, annotations) => annotations.flatMap(_.subTerms).toVector + case DefineVar(sym, rhs) => Vector.single(rhs) + case Region(_, body) => Vector.single(body) + case RegRef(reg, value) => Vector.double(reg, value) + case Assgn(lhs, rhs) => Vector.double(lhs, rhs) + case SetRef(lhs, rhs) => Vector.double(lhs, rhs) + case Drop(term) => Vector.single(term) + case Deref(term) => Vector.single(term) + case TermDefinition(_, _, _, pss, tps, sign, body, _, _, annotations, _) => + pss.toVector.flatMap(_.subTerms) ++ tps.getOrElse(Nil).flatMap(_.subTerms).toVector ++ sign.toVector ++ body.toVector ++ annotations.flatMap(_.subTerms).toVector + case cls: ClassDef => + (cls.paramsOpt.toVector.flatMap(_.subTerms) :+ cls.body.blk) ++ cls.annotations.flatMap(_.subTerms).toVector + case mod: ModuleOrObjectDef => + ( mod.paramsOpt.toVector.flatMap(_.subTerms) :+ mod.body.blk) ++ mod.annotations.flatMap(_.subTerms).toVector + case td: TypeDef => + td.rhs.toVector ++ td.annotations.flatMap(_.subTerms).toVector + case pat: PatternDef => + (pat.paramsOpt.toVector.flatMap(_.subTerms) :+ pat.body.blk) ++ pat.annotations.flatMap(_.subTerms).toVector + case Import(sym, str, pth) => Vector.empty + case Try(body, finallyDo) => Vector.single(body) ++ Vector.single(finallyDo) + case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => (rhs +: args.toVector) ++ defs.flatMap(_.td.subTerms).toVector :+ bod + case Neg(e) => Vector.single(e) + case Annotated(ann, target) => ann.subTerms ++ Vector.single(target) + case LeadingDotSel(nme) => Vector.empty + case SetConfig(_) => Vector.empty + + // private def treeOrSubterms(t: Tree, t: Term): Ls[Located] = t match + private def treeOrSubterms(t: Tree): Vector[Located] = t match + case Tree.DummyApp | Tree.DummyTup => subTerms + case _ => Vector.single(t) + + protected def children: Vector[Located] = this match + case t: Lit => Vector.single(t.lit.asTree) + case t: AnyRef_ => treeOrSubterms(t.tree) + case t: Tup => treeOrSubterms(t.tree) + case l: Lam => Vector.double(l.params, l.body) + case t: App => treeOrSubterms(t.tree) + case IfLike(_, _, split) => Vector.single(split) + case SynthIf(split) => Vector.single(split) + case SynthWhile(split) => Vector.single(split) + case SynthSel(pre, nme) => Vector.double(pre, nme) + case Sel(pre, nme) => Vector.double(pre, nme) + case SelProj(prefix, cls, proj) => Vector.triple(prefix, cls, proj) + case _ => + subTerms // TODO more precise (include located things that aren't terms) + + def show(using Scope, ShowCfg, Raise): Document = + + def showSelTargets(sel: AnySel): Document = + val str = sel.nme.name + val ts = sel.resolvedTargets + ts match + case Nil => + // doc"$str‹?›" + doc"${str}ˀˀˀ" + case t :: Nil => t.show + case ts => doc"$str‹" :: ts.map(_.show).mkDocument(", ") :: doc"›" + + def res: Document = this match + case lit: Lit => lit.lit.idStr + case UnitVal() => doc"()" + case DynTy() => doc"dyn" + case r: SimpleRef => + r.sym match + case _: BuiltinSymbol => r.sym.nme + case _ => r.sym.showName + case r: MemberRef => r.sym.showName + case r: UnresolvedRef => doc"${r.id.name}" + case sr: SelfRef => s"${sr.sym.showName}.this" + case Capture(base, thru) => + // doc"${base.show}⟨${thru.showName}⟩" + doc"${base.show}^${thru.showName}" + case r: Ref => + r.sym match + case _: BuiltinSymbol => r.sym.nme + case _ => r.sym.showName + case sup: Super => doc"super.${sup.id.name}" + case sel: NewSel => + val str = sel.id.name + val pre = sel.cls.fold(doc"${sel.prefix.show}.")(cls => doc"${sel.prefix.show}.${cls.show}#") + if summon[ShowCfg].showFlowSymbols + then doc"$pre${ + sel.resolvedMembers match + case Nil if sel.resolvedTargets.nonEmpty => + doc"$str‹" :: sel.resolvedTargets.distinct.map(_.showName).mkDocument(", ") :: doc"›" + case Nil => doc"${str}ˀˀˀ" + case t :: Nil => t.showName + case ts => doc"$str‹" :: ts.map(_.showName).mkDocument(", ") :: doc"›" + }" + else doc"$pre$str" + case sel: Sel => + if summon[ShowCfg].showFlowSymbols + then doc"${sel.prefix.show}.${sel.sym.fold(doc"${ + showSelTargets(sel)}")(_.showName)}" + else doc"${sel.prefix.show}.${sel.nme.name}" + case sel: SynthSel => + if summon[ShowCfg].showFlowSymbols + then doc"⟨${sel.prefix.show}.⟩${sel.sym.fold(doc"${ + showSelTargets(sel)}")(_.showName)}" + else doc"${sel.prefix.show}.${sel.nme.name}" + case Resolved(trm, sym) => + trm.show + case app: App => + doc"${app.lhs.show}${app.rhs.showAsParams}${ + if summon[ShowCfg].showFlowSymbols + then + summon[ShowCfg].shownSymbols.add(app.resSym) + "‹" :: app.resSym.showPlainName :: "›" + else "" + }" + case lam: Lam => doc"${lam.params.show} => ${lam.body.show}" + case nw: New => doc"new ${nw.cls.show}${nw.args.map(_.showAsParams).mkDocument()}${ + nw.rft.fold(doc"")(doc" with " :: _._2.blk.show)}" + case tup: Tup => bracketed("[", "]", insertBreak = true): + tup.fields.map(_.show).mkDocument(doc", # ") + case blk: Blk => braced: + doc" # " :: (blk.stats ::: + blk.res.match + case Lit(Tree.UnitLit(false)) => Nil + case res => res :: Nil + ).map(_.show).mkDocument(doc", # ") + case ld: LetDecl => + (ld.annotations.map(_.show :: " ") ::: doc"let ${ld.sym.showName}" :: Nil).mkDocument() + case df: DefineVar => + doc"${df.sym.showName} = ${df.rhs.show}" + case td: TermDefinition => + td.annotations.map(_.show :: " ").mkDocument() + :: doc"${td.k.str} ${td.bsym.showName}::${td.tsym.showName}" + :: (if td.tparams.isEmpty then doc"" + else doc"[${td.tparams.get.map(_.sym.showName).mkDocument(", ")}]") + :: td.params.map(_.show).mkDocument() + :: td.sign.fold(doc"")(s => doc": ${s.show}") + :: (if summon[ShowCfg].showFlowSymbols then doc" ‹${td.bsym.flow.showName}›" else doc"") + :: td.body.fold(doc"")(b => doc" = ${b.show}") + case cld: ClassLikeDef => + cld.annotations.map(_.show :: " ").mkDocument() + :: doc"${cld.ctorSym.fold(doc"")("fun "::_.showName::doc" # ")}${cld.kind.str} ${cld.bsym.showName}::${cld.sym.showName}" + :: (if cld.tparams.isEmpty then doc"" + else doc"[${cld.tparams.map(_.sym.showName).mkDocument(", ")}]") + :: cld.paramsOpt.map(_.show).toList.mkDocument() + :: cld.auxParams.map(_.show).mkDocument() + :: cld.ext.fold(doc"")(e => doc" extends ${e.show}") + :: doc" ${cld.body.blk.show}" + case imp: Import => + doc"import ${"\""}.../${imp.file.last}${"\""} as ${imp.sym.showName}" + case LeadingDotSel(name) => doc"_?_.${name.name}" + case Error() => doc"‹error›" + case IfLike(kw, form, split) => + // doc"${kw.name} ${form.headStr} ${split.show}" + given IfLikeForm = form + doc"${form.headStr} { #{ # ${split.show} #} # }" + // val fs = fomr match + case Missing => doc"‹missing›" + case TyApp(lhs, targs) => doc"${lhs.show}[${targs.map(_.show).mkDocument(", ")}]" + case DynSel(prefix, field, arrayIdx, _) => + if arrayIdx then doc"${prefix.show}.[${field.show}]" + else doc"${prefix.show}.(${field.show})" + case SelProj(prefix, cls, field) => doc"${prefix.show}.${cls.show}#${field.name}" + case Mut(underlying) => doc"mut ${underlying.show}" + case CtxTup(fields) => doc"using (${fields.map(_.show).mkDocument(", ")})" + case SynthIf(split) => doc"if { #{ # ${split.show} #} # }" + case SynthWhile(split) => doc"while { #{ # ${split.show} #} # }" + case FunTy(lhs, rhs, eff) => + doc"(${lhs.showAsParams} ->${eff.fold(doc"")(e => doc"{${e.show}}")} ${rhs.show})" + case Forall(tvs, outer, body) => + val variables = tvs.map: tv => + tv.sym.showName + :: tv.lb.fold(doc"")(b => doc" :> ${b.show}") + :: tv.ub.fold(doc"")(b => doc" <: ${b.show}") + doc"forall ${variables.mkDocument(", ")}${outer.fold(doc"")(v => doc", outer ${v.showName}")}: ${body.show}" + case Constrained(constraints, body) => + val bounds = constraints.map(c => doc"${c.lhs.show} ${c.dir.showDbg} ${c.rhs.show}") + doc"[${bounds.mkDocument(", ")}] => ${body.show}" + case WildcardTy(in, out) => + doc"in ${in.fold(doc"⊥")(_.show)} out ${out.fold(doc"⊤")(_.show)}" + case Rcd(mut, stats) => + (if mut then doc"mut " else doc"") :: braced: + doc" # " :: stats.map(_.show).mkDocument(doc", # ") + case RcdField(field, rhs, _) => doc"${field.show}: ${rhs.show}" + case RcdSpread(record) => doc"...${record.show}" + case Quoted(body) => doc"""code"${body.show}"""" + case Unquoted(body) => doc"$${${body.show}}" + case DynNew(cls, args) => doc"new! ${cls.show}${args.map(_.showAsParams).mkDocument()}" + case Asc(term, ty) => doc"(${term.show}: ${ty.show})" + case CompType(lhs, rhs, pol) => doc"(${lhs.show} ${if pol then "|" else "&"} ${rhs.show})" + case Neg(rhs) => doc"~(${rhs.show})" + case Region(name, body) => doc"region ${name.showName} in ${body.show}" + case RegRef(reg, value) => doc"(${reg.show}).ref ${value.show}" + case Assgn(lhs, rhs) => doc"${lhs.show} := ${rhs.show}" + case SetRef(ref, value) => doc"${ref.show} := ${value.show}" + case Drop(term) => doc"drop ${term.show}" + case Deref(ref) => doc"!(${ref.show})" + case Ret(result) => doc"return ${result.show}" + case Throw(result) => doc"throw ${result.show}" + case Label(label, _, body, _) => doc"do ${label.showName}: ${body.show}" + case Break(label, _, value) => doc"${label.showName}.break${value.fold(doc"")(v => doc" ${v.show}")}" + case Continue(label) => doc"${label.showName}.continue" + case Try(body, finallyDo) => doc"try ${body.show} finally ${finallyDo.show}" + case Annotated(annot, target) => doc"${annot.show} ${target.show}" + case Handle(lhs, rhs, args, _, defs, body) => + doc"handle ${lhs.showName} = ${rhs.show}${args.map(_.showAsParams).mkDocument()} with { #{ # ${ + defs.map(_.td.show).mkDocument(doc" # ")} #} # } in ${body.show}" + case td: TypeDef => + td.annotations.map(_.show :: " ").mkDocument() + :: doc"type ${td.bsym.showName}::${td.sym.showName}" + :: (if td.tparams.isEmpty then doc"" + else doc"[${td.tparams.map(_.sym.showName).mkDocument(", ")}]") + :: td.rhs.fold(doc"")(rhs => doc" = ${rhs.show}") + case SetConfig(_) => doc"#config(...)" + + this match + case t: Resolvable => t.expansion match + case S(S(exp)) => + val rhs = exp.show(using summon, summon[ShowCfg].copy(showExpansionMappings = false)) + if summon[ShowCfg].showExpansionMappings then + if exp === t then rhs + // ^ Some expansions only modify meta-data, such as types and symbols; + // we don't print them for conciseness + else res :: doc"{ ~> " :: rhs :: doc" }" + else exp.show + case _ => res + case _ => res + + end show + + def size: Int = this match + case Lit(Tree.StrLit(str)) => str.size / 4 + 1 + case _ => subTerms.iterator.map(_.size).sum + 1 + + def showDbg(using DebugPrinter): Str = this match + case r: Ref => r.sym.showAsPlain + case r: Resolved => + s"${r.showPlain}‹${r.sym}›" + case trm: Term => + // s"$showPlain‹${trm.symbol.getOrElse("")}›" + s"$showPlain${trm.symbol.fold("")("‹"+_+"›")}" + case _ => + showPlain + + def showAsParams(using Scope, ShowCfg, Raise): Document = this match + case tup: Tup => doc"(${tup.fields.map(_.show).mkDocument(", ")})" + case _ => doc"(...$show)" + + def showDbgAsParams(using DebugPrinter): Str = this match + case tup: Tup => s"(${tup.fields.map(_.showDbg).mkString(", ")})" + case _ => s"(...$showDbg)" + + def showPlain(using DebugPrinter): Str = this match + case Term.UnitVal() => "()" + case Term.DynTy() => "dyn" + case Lit(lit) => lit.idStr + case Resolved(t, sym) => t.showPlain + case r @ Ref(symbol) => symbol.showAsPlain + case r @ SimpleRef(symbol) => symbol.showAsPlain + case r @ MemberRef(symbol) => symbol.showAsPlain + case r @ SelfRef(sym) => sym.showAsPlain + case Capture(base, thru) => s"${base.showDbg}^${thru.showDbg}" + case App(lhs, rhs) => s"${lhs.showDbg}${rhs.showDbgAsParams}" + case RcdField(lhs, rhs, _) => s"${lhs.showDbg}: ${rhs.showDbg}" + case RcdSpread(bod) => s"...${bod.showDbg}" + case FunTy(lhs: Tup, rhs, eff) => + s"${lhs.fields.map(_.showDbg).mkString(", ")} ->${ + eff.map(e => s"{${e.showDbg}}").getOrElse("")} ${rhs.showDbg}" + case FunTy(lhs, rhs, eff) => + s"(...${lhs.showDbg}) ->${eff.map(e => s"{${e.showDbg}}").getOrElse("")} ${rhs.showDbg}" + case TyApp(lhs, targs) => s"${lhs.showDbg}[${targs.mkString(", ")}]" + case Forall(tvs, outer, body) => s"forall ${tvs.mkString(", ")}${outer.map(v => s", outer $v").mkString}: ${body.toString}" + case Constrained(constraints, body) => s"[${constraints.map(_.showDbg).mkString(", ")}] => ${body.showDbg}" + case WildcardTy(in, out) => s"in ${in.map(_.toString).getOrElse("⊥")} out ${out.map(_.toString).getOrElse("⊤")}" + case Sel(pre, nme) => s"${pre.showDbg}.${nme.name}" + case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" + case Super(_, owner, id) => s"super[${owner.showDbg}].${id.name}" + case NewSel(pre, nme, cls) => s"${pre.showDbg}.${cls.fold("")(c => s"${c.showDbg}#")}${nme.name}" + case UnresolvedRef(_, id) => s"${id.name}‹open›" + case DynSel(pre, fld, _, _) => s"${pre.showDbg}[${fld.showDbg}]" + case IfLike(kw, _, split) => s"${kw.name} { ${split.showDbg} }" + case SynthIf(split) => s"if { ${split.showDbg} }" + case SynthWhile(split) => s"while { ${split.showDbg} }" + case Lam(params, body) => s"λ${params.showDbg}. ${body.showDbg}" + case Blk(stats, res) => + (stats.map(_.showDbg + "; ") :+ (res match { case Lit(Tree.UnitLit(false)) => "" case x => x.showDbg + " " })) + .mkString("{ ", "", "}") + case Rcd(mut, stats) => + (if mut then "mut " else "") + stats.map(_.showDbg + "; ").mkString("{ ", "", "}") + case Quoted(term) => s"""code"${term.showDbg}"""" + case Unquoted(term) => s"$${${term.showDbg}}" + case New(cls, args, rft) => + s"new ${cls.showDbg}${args.map(_.showDbgAsParams).mkString}${rft.fold("")(r => s"{ ${r._2.blk.showDbg} }")}" + case DynNew(cls, args) => + s"new! ${cls.showDbg}${args.map(_.showDbgAsParams).mkString}" + case SelProj(pre, cls, proj) => s"${pre.showDbg}.${cls.showDbg}#${proj.name}" + case Asc(term, ty) => s"${term.toString}: ${ty.toString}" + case LetDecl(sym, _) => s"let ${sym}" + case DefineVar(sym, rhs) => s"${sym} = ${rhs.showDbg}" + case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => + s"handle ${lhs} = ${rhs}(${args.mkString(", ")}) ${defs} in ${bod}" + case Region(name, body) => s"region ${name.nme} in ${body.showDbg}" + case RegRef(reg, value) => s"(${reg.showDbg}).ref ${value.showDbg}" + case Assgn(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}" + case SetRef(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}" + case Drop(term) => s"drop $term" + case Deref(term) => s"!$term" + case Neg(ty) => s"~${ty.showDbg}" + case CompType(lhs, rhs, pol) => s"${lhs.showDbg} ${if pol then "|" else "&"} ${rhs.showDbg}" + case Error() => "" + case Tup(fields) => fields.map(_.showDbg).mkString("[", ", ", "]") + case Mut(und) => s"mut ${und.showDbg}" + case CtxTup(fields) => fields.map(_.showDbg).mkString("‹using›[", ", ", "]") + case TermDefinition(k, sym, tsym, pss, tps, sign, body, flags, _, _, _) => + s"${flags.showDbg}${k.str} ${sym}${ + tps.map(_.map(_.showDbg)).mkStringOr(", ", "[", "]") + }${ + pss.map(_.showDbg).mkString("") + }${sign.fold("")(": "+_.showDbg)}${ + body match + case S(x) => " = " + x.showDbg + case N => "" + }" + case cls: ClassLikeDef => + s"${cls.kind} ${cls.sym.nme}${ + cls.tparams.map(_.showDbg).mkStringOr(", ", "[", "]")}${ + cls.paramsOpt.fold("")(_.toString)} ${cls.body}" + case Import(sym, str, file) => s"import $str from ${file}" + case Annotated(ann, target) => s"@${ann} ${target.showDbg}" + case Throw(res) => s"throw ${res.showDbg}" + case Label(label, _, body, _) => s"do ${label.nme}: ${body.showDbg}" + case Break(label, _, value) => s"${label.nme}.break${value.fold("")(v => s" ${v.showDbg}")}" + case Continue(label) => s"${label.nme}.continue" + case Try(body, finallyDo) => s"try ${body.showDbg} finally ${finallyDo.showDbg}" + case Ret(res) => s"return ${res.showDbg}" + case TypeDef(sym, _, tparams, rhs, _, _) => + s"type ${sym}${tparams.mkStringOr(", ", "[", "]")} = ${rhs.fold("")(x => x.showDbg)}" + case Missing => "missing" + case LeadingDotSel(nme) => s"_?_.${nme.name}" + case SetConfig(_) => "#config(...)" + +end StatementImpl + From d69b9531ba9e88ff680a8b6286de8f9fe0b0fc7a Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 20:12:41 +0800 Subject: [PATCH 217/228] Handle remaining new-resolution value forms explicitly --- .../hkmc2/semantics/InterfaceExposure.scala | 11 +- .../scala/hkmc2/semantics/NewResolver.scala | 158 +++++++++++++++--- .../hkmc2/semantics/NewResolverState.scala | 4 +- .../src/main/scala/hkmc2/semantics/Term.scala | 12 +- .../src/test/mlscript/basics/PostfixOps.mls | 6 +- .../src/test/mlscript/codegen/Lambdas.mls | 6 +- .../mlscript/newres/ControlFlowResults.mls | 121 ++++++++++++++ .../src/test/mlscript/newres/Dynamic.mls | 8 +- .../test/mlscript/newres/HandlerResults.mls | 16 +- .../src/test/mlscript/newres/Overrides.mls | 4 +- .../src/test/mlscript/newres/ValueForms.mls | 63 +++++++ .../mlscript/ucs/patterns/BooleansOps.mls | 6 +- 12 files changed, 373 insertions(+), 42 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/ValueForms.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index 45b0343644..9a39ade1a1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -102,13 +102,15 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) term(parent, marks, path.via(msg"This parent contributes inherited members." -> parent.toLoc)) private def result(body: Term, sign: Opt[Term], marks: Ls[Marks], path: Path)(using NewResolverState): Unit = sign match - case N => term(body, marks, path.via(msg"This value is returned here." -> body.toLoc)) + case N => + watch((new Identity(body), marks, "result"))(resolver.listenResult(body)): shape => + emit(shape.exit(marks), path.via(msg"This value is returned here." -> body.toLoc)) case S(sign) => // Check returned implementations against the declared calling interface. // Its parameter types constrain returned closures instead of unrestricted // unknowns, and consumers see only the annotated result. val declared = resolver.declaredType(resolver.typeResolution(sign), Map.empty).instantiate(rstate.instances) - watch((new Identity(body), declared, marks))(resolver.listenTerm(body)): shape => + watch((new Identity(body), declared, marks))(resolver.listenResult(body)): shape => resolver.constrainFunction(declared, shape, marks) watch((declared, marks))(resolver.listenTypeInstances(declared)): shape => emit(shape.exit(marks), path) @@ -151,7 +153,10 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) case intro: IntroShape => intro.trm match case Lam(_, body) => parameters(shape.unappliedParams, path) - contextualTerm(body, marks, path.via(msg"This value is returned here." -> body.toLoc), instances) + watch((new Identity(body), marks, instances, "result"))( + listener => resolver.listenResult(body)(listener)(using rstate.withInstances(instances))): shape => + emit(resolver.instantiateShape(shape, instances).exit(marks), + path.via(msg"This value is returned here." -> body.toLoc)) case _ => () case tuple: TupleShape => tuple.segments.foreach: case field: TupleShape.Fixed => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index c1dcd1ac6d..aea308eef4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1323,7 +1323,9 @@ class NewResolver: def checkDeclaredResult(definition: TermDefinition)(using NewResolverState): Unit = definition.body.foreach: body => resultSignature(definition).foreach: sign => - checkAscription(body, sign) + val expected = declaredType(typeResolution(sign), Map.empty) + listenResult(body): shape => + inferTypeArguments(expected.instantiate(rstate.instances), shape, Nil) def checkAscription(body: Term, sign: Term)(using NewResolverState): Unit = val expected = declaredType(typeResolution(sign), Map.empty) @@ -1521,13 +1523,13 @@ class NewResolver: val context = actual.applicationHead._2 def results(listener: Listener)(using NewResolverState): Unit = actual.applicationHead._1 match case intro: IntroShape => intro.trm match - case Lam(_, body) => listenTerm(body)(shape => listener(instantiateShape(shape, instances)))(using rstate.withInstances(instances)) + case Lam(_, body) => listenResult(body)(shape => listener(instantiateShape(shape, instances)))(using rstate.withInstances(instances)) case _ => () case ds: DefnShape => ds.defn match case td: TermDefinition => td.sign match case S(sign) => listenSignatureResult(declaredType(typeResolution(sign), Map.empty).instantiate(instances), if td.flags.hasResultAnnotation then 0 else td.params.length)(listener) - case N => td.body.foreach(body => listenTerm(body)(shape => listener(instantiateShape(shape, instances)))(using rstate.withInstances(instances))) + case N => td.body.foreach(body => listenResult(body)(shape => listener(instantiateShape(shape, instances)))(using rstate.withInstances(instances))) case _ => () case _ => () def matchLists(expected: DeclaredType, remaining: Ls[(ParamList, Ls[Marks])])(using NewResolverState): Unit = @@ -2464,7 +2466,7 @@ class NewResolver: resolError(res, lhs.diagnostic(message)) if sh.isSaturated then def go(body: Term, mss: Ls[Marks])(using NewResolverState) = - listenTerm(body): sh => + listenResult(body): sh => transportShape(sh, mss) match case NoShape => case sh: TermShape => @@ -2938,7 +2940,7 @@ class NewResolver: def showDbg(using DebugPrinter): Str = s"result of ${td.tsym.nme}" rstate.byNameResults(key) = host listenValueHost(host)(listener) - listenTerm(body)(publishActivated(host, _)) + listenResult(body)(publishActivated(host, _)) def pipeTerm(from: Term, to: ShapeHost)(using NewResolverState): Unit = log(s"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.currentShapes}") @@ -3200,6 +3202,88 @@ class NewResolver: if receiver then member.asModOrObj.orElse(member.asTrm).orElse(member.asCls).orElse(member.asPat) else member.asTrm.orElse(member.asModOrObj).orElse(member.asCls).orElse(member.asPat) + /** Syntactic control transfers within one evaluation scope. Calls may return, + * but an explicit return/break/throw cannot also supply a normal value. Keep + * exit operands at their lexical endpoint; the function or label observing + * them applies the same shape transport as for its normal body result. + */ + private case class ResultFlow(normal: Bool, exits: Map[Opt[LabelSymbol], Vector[Term]]): + // None identifies the current function's return; Some(label) identifies a break. + def or(that: ResultFlow): ResultFlow = ResultFlow(normal || that.normal, + exits ++ that.exits.map((target, values) => target -> (exits.getOrElse(target, Vector.empty) ++ values))) + def andThen(that: => ResultFlow): ResultFlow = + if normal then copy(normal = false).or(that) else this + def leave(target: Opt[LabelSymbol], value: Term): ResultFlow = + andThen(ResultFlow(false, Map(target -> Vector.single(value)))) + + private def resultFlow(term: Term): ResultFlow = + val normal = ResultFlow(true, Map.empty) + def sequence(terms: Iterable[Term]): ResultFlow = + terms.foldLeft(normal)((flow, term) => flow.andThen(resultFlow(term))) + def statement(stat: Statement): ResultFlow = stat match + // A definition's body belongs to its own invocation, not to the enclosing + // function. In particular, a nested getter's return must not escape here. + case _: Definition => normal + case term: Term => resultFlow(term) + case _ => sequence(stat.subTerms) + def split(branches: SimpleSplit, onEnd: ResultFlow): ResultFlow = branches match + case SimpleSplit.Else(value) => resultFlow(value) + case SimpleSplit.End => onEnd + case SimpleSplit.Cons(SimpleSplit.Head.Let(_, value), tail) => resultFlow(value).andThen(split(tail, onEnd)) + case SimpleSplit.Cons(SimpleSplit.Head.Match(scrutinee, pattern, consequent), tail) => + // A failed pattern can skip its guards. Retain that path as well as + // any control transfer from a guard which is evaluated. + val guards = pattern.subTerms.foldLeft(normal)((flow, term) => flow.or(resultFlow(term))) + resultFlow(scrutinee).andThen(guards.andThen(split(consequent, onEnd).or(split(tail, onEnd)))) + def quoted(term: Term, depth: Int): ResultFlow = term match + // Only splices crossing back to the current stage execute now. Nested + // quotations delay their own splices, including any returns they contain. + case Unquoted(body) if depth == 1 => resultFlow(body) + case Unquoted(body) => quoted(body, depth - 1) + case Quoted(body) => quoted(body, depth + 1) + case _ => term.subTerms.foldLeft(normal)((flow, term) => flow.andThen(quoted(term, depth))) + term match + case Ret(value) => resultFlow(value).leave(N, value) + case Break(label, _, value) => value.fold(normal)(resultFlow).leave(S(label), value.getOrElse(unit)) + case Throw(value) => resultFlow(value).copy(normal = false) + case _: Continue => ResultFlow(false, Map.empty) + case Blk(stats, value) => stats.foldLeft(normal)((flow, stat) => flow.andThen(statement(stat))).andThen(resultFlow(value)) + case Label(label, _, body, _) => + val flow = resultFlow(body) + ResultFlow(flow.normal || flow.exits.contains(S(label)), flow.exits - S(label)) + case Try(body, cleanup) => + val first = resultFlow(body) + val last = resultFlow(cleanup) + // An abrupt cleanup replaces the pending return/break as well as the + // normal body value. A normally completing cleanup contributes no value. + if last.normal then first.or(last.copy(normal = false)) else last + case IfLike(_, form, branches) => + // Falling off an imperative split returns unit; falling off a returning + // split throws a match error. A loop may finish without running its body. + val flow = split(branches, if form.isImperative then normal else ResultFlow(false, Map.empty)) + if form == IfLikeForm.While then flow.or(normal) else flow + case Quoted(body) => quoted(body, 1) + case _: Lam | _: Unquoted | _: FunTy | _: Forall | _: Constrained | _: WildcardTy + | _: CompType | _: Neg | _: DynTy => normal + case Annotated(Annot.Async(), _) => normal + case Annotated(_, target) => resultFlow(target) + case Handle(_, rhs, args, _, _, _) => sequence(rhs :: args) + case New(cls, args, _) => sequence(cls :: args) + case Rcd(_, stats) => stats.foldLeft(normal)((flow, stat) => flow.andThen(statement(stat))) + case Asc(value, _) => resultFlow(value) + case TyApp(value, _) => resultFlow(value) + case _ => sequence(term.subTerms) + + /** Function results include explicit returns, whereas listenTerm observes only + * normal completion. Share this rule between calls, result checking, getters, + * and interface exposure so a returned closure is checked even when no caller + * observes it locally. + */ + private[semantics] def listenResult(body: Term)(listener: Listener)(using NewResolverState): Unit = + val flow = resultFlow(body) + if flow.normal then listenTerm(body)(listener) + flow.exits.getOrElse(N, Vector.empty).foreach(value => listenTerm(value)(listener)) + def listenTerm(trm: Term)(listener: Listener)(using NewResolverState): Unit = log(s"listenTerm: trm = ${trm.showDbg}") listenValue(trm, false)(listener) @@ -3228,7 +3312,7 @@ class NewResolver: * subscriptions while the node is waiting for a forward definition. Deliver * cached shapes to each listener, which is already registered for future shapes. */ - private def listenAggregate(aggregate: Tup | Rcd, listener: ShapeListener[ShapeEvent]) + private def listenAggregate(aggregate: Tup | CtxTup | Rcd, listener: ShapeListener[ShapeEvent]) (start: (Listener) => Unit)(using NewResolverState): Unit = val first = aggregateProducers.add(new Identity(aggregate)) // An imported definition can already have shapes computed by its own elaborator. @@ -3338,8 +3422,11 @@ class NewResolver: if invoked then listener(value) else instantiate(value)(listener) case mut @ Mut(underlying: Tup) => listener(mutableArray(mut, underlying)) case Mut(underlying) => listenTerm(underlying)(listener) - case tuple: Tup => listenAggregate(tuple, listener): publish => - val fields = tuple.fields.collect { case Fld(_, key, S(value)) => (key, value) } + case tuple: (Tup | CtxTup) => listenAggregate(tuple, listener): publish => + val elements = tuple match + case Tup(fields) => fields + case CtxTup(fields) => fields + val fields = elements.collect { case Fld(_, key, S(value)) => (key, value) } val named = if fields.isEmpty then Nil else // Reuse the defining graph's property symbols when an imported tuple // is observed directly, as well as when its copied listeners fire. @@ -3399,7 +3486,7 @@ class NewResolver: val field = TupleShape.ValueField(UnknownValueShape.spread(term, shape), Nil) val unknown = TupleShape(term, TupleShape.Unknown(term, field) :: Nil)(this) expand(rest, TupleShape.Spread(unknown, marks) :: reversed) - expand(tuple.fields, Nil) + expand(elements, Nil) case record: Rcd => listenAggregate(record, listener): publish => def expand(stats: Ls[Statement], reversed: Ls[RecordShape.Element])(using NewResolverState): Unit = stats match case Nil => @@ -3484,6 +3571,13 @@ class NewResolver: val fs = ref.resSym val sh = symShapes.getOrElseUpdate((sym, fs, Nil), CoreSymShape(sym, fs, Nil)) listener(sh) + case ref @ MemberRef(_: ErrorSymbol) => rstate.markError(ref) + case ref @ MemberRef(sym: TypeAliasSymbol) => + rstate.markError(ref) + resolError(ref, msg"Type alias '${sym.nme}' cannot be used as a runtime value." -> sym.toLoc :: Nil) + case MemberRef(_: InnerSymbol) => + // Ctx.RefElem uses SelfRef for class/module receivers, not MemberRef. + lastWords("Direct inner-symbol references must use SelfRef") case Capture(base, thru) => if discardMarks then listen(base, discardMarks = true)(listener) @@ -3492,25 +3586,45 @@ class NewResolver: case ref @ Ref(sym: InnerSymbol) => // TODO: remove remaining occurrences of such refs sym.addShapeListener(listener) case ref @ Ref(bsym: BlockMemberSymbol) => - ??? - case res: ResolvableImpl => + lastWords("Legacy block member references must not enter new resolution") + case res: (App | New | Ref | Sel | SelProj | Resolved | LeadingDotSel) => res.replayShapes(listener) - // ??? - case sh: ShapeHost => + case sh: (NewSel | UnresolvedRef | Super | IfLike) => sh.replayShapes(listener) - case Blk(sts, rs) => - listen(rs)(listener) - case Try(body, _) => + case block @ Blk(_, result) => + if resultFlow(block).normal then listen(result, discardMarks)(listener) + case cleanup @ Try(body, _) => // Finally is evaluated for effects; normal completion returns the body's // value. Its cleanup value must not contribute candidates to this result. - listen(body)(listener) + if resultFlow(cleanup).normal then listen(body, discardMarks)(listener) + case Label(label, _, body, _) => + val flow = resultFlow(body) + if flow.normal then listen(body, discardMarks)(listener) + flow.exits.getOrElse(S(label), Vector.empty).foreach(value => listen(value, discardMarks)(listener)) + case Annotated(Annot.Async(), _) => + // Lowering returns a JavaScript promise, not the body's value. Until the + // promise interface is declared, it cannot justify a static member lookup. + listener(UnknownValueShape.at(trm)) + case Annotated(_, target) => listen(target, discardMarks)(listener) case _: Assgn | _: Drop => listener(unitResultShape) - case _: Throw | _: Continue => () // These expressions do not complete normally. - // case u: UnitVal => + case _: Ret | _: Break | _: Throw | _: Continue => () // These expressions do not complete normally. + case _: Quoted | _: Unquoted => + // Quotation constructs a runtime syntax value; splicing supplies syntax + // for a different stage. Neither has the value interface of its operand. + listener(UnknownValueShape.at(trm)) + case _: Handle => + // An effect handler can abort or replace the continuation's result. The + // normal body's interface alone therefore cannot describe this value. + listener(UnknownValueShape(trm, fromPublicInterface = false)(ShapeProvenance( + msg"An effect handler can replace the handled body's result." -> trm.toLoc :: Nil))) + case _: DynTy | _: FunTy | _: Forall | _: Constrained | _: WildcardTy | _: CompType | _: Neg => + resolError(trm, msg"A type cannot be used as a runtime value." -> N :: Nil) + case Error() => () // Elaboration has already reported the error; it supplies no candidate. case Missing => () // FIXME: Currently get this from light-elaborated Predef import - case _ => - println(s"TODO: listen for ${trm.describe} (${trm.getClass})") - () + case _: SynthIf | _: SynthWhile => + lastWords("Lowering-generated control flow must not enter resolution") + case _: Region | _: RegRef | _: Deref | _: SetRef => + lastWords("InvalML reference operations require legacy resolution") end NewResolver diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index f3ff561ccc..64c3116e61 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -387,9 +387,9 @@ final class NewResolverState private ( new Cache(inherited.map(_.mutableArrays), identity) // Named tuple fields have stable property identities, shared with consumers // through the tuple's original graph rather than allocated per spread candidate. - val namedTupleRecords: Cache[Identity[Tup], Rcd] = + val namedTupleRecords: Cache[Identity[Tup | CtxTup], Rcd] = new Cache(inherited.map(_.namedTupleRecords), identity) - val aggregateProducers: Seen[Identity[Tup | Rcd]] = + val aggregateProducers: Seen[Identity[Tup | CtxTup | Rcd]] = new Seen(inherited.map(_.aggregateProducers)) // Whether a binder is supplied is fixed by its authoritative syntactic site. // Each definition/site has its own parameter instances; marks distinguish the diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 3e5cddf695..03284d2ea6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -375,7 +375,7 @@ enum Term extends Statement, AutoLocated, ShapePublisher: case DynSel(prefix: Term, fld: Term, arrayIdx: Bool, checked: Bool) case Tup(fields: Ls[Elem])(val tree: Tree.Tup) extends Term, ShapeHost case Mut(underlying: Tup | Rcd | New | DynNew) - case CtxTup(fields: Ls[Elem])(val tree: Tree.Tup) + case CtxTup(fields: Ls[Elem])(val tree: Tree.Tup) extends Term, ShapeHost case IfLike(kw: Keyword.SplitLike, form: IfLikeForm, split: SimpleSplit) extends Term, ShapeHost /** `If` expressions synthesized by the pattern compiler. It should only be * created and used in `Lowering`. One must make sure that all terms in the @@ -651,10 +651,12 @@ enum Term extends Statement, AutoLocated, ShapePublisher: })(term.tree) copyShapes(term, copy) case Mut(underlying) => Mut(underlying.mkClone.asInstanceOf[Tup | Rcd | New | DynNew]) - case term @ CtxTup(fields) => CtxTup(fields.map { - case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) - case s: Spd => s.copy(term = s.term.mkClone) - })(term.tree) + case term @ CtxTup(fields) => + val copy = CtxTup(fields.map { + case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) + case s: Spd => s.copy(term = s.term.mkClone) + })(term.tree) + copyShapes(term, copy) case term @ IfLike(kw, form, split) => copyShapes(term, IfLike(kw, form, split.mkClone)) case SynthIf(split) => SynthIf(split.mkClone) case SynthWhile(split) => SynthWhile(split.mkClone) diff --git a/hkmc2/shared/src/test/mlscript/basics/PostfixOps.mls b/hkmc2/shared/src/test/mlscript/basics/PostfixOps.mls index f45a3de188..08d42a8c2b 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PostfixOps.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PostfixOps.mls @@ -37,6 +37,10 @@ fun inc(x) = x + 1 :todo // ? inc of 1 | + 1 +//│ ╔══[COMPILATION ERROR] Resolution error in alternation +//│ ║ l.39: inc of 1 | + 1 +//│ ║ ^^^^^^^ +//│ ╙── A type cannot be used as a runtime value. //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) //│ ║ l.39: inc of 1 | + 1 //│ ╙── ^^^^^^^ @@ -45,6 +49,6 @@ inc of 1 | + 1 :todo // ? 1 |> inc | + 1 //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) -//│ ║ l.46: 1 |> inc | + 1 +//│ ║ l.50: 1 |> inc | + 1 //│ ╙── ^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls index 4d04191368..7ed2f8e4c8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls @@ -32,6 +32,10 @@ f(true, true) :e x => if x is 0 | 1 then 1 +//│ ╔══[COMPILATION ERROR] Resolution error in alternation +//│ ║ l.34: x => if x is 0 | 1 then 1 +//│ ║ ^^^^^^^^^^ +//│ ╙── A type cannot be used as a runtime value. //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (alternation) //│ ║ l.34: x => if x is 0 | 1 then 1 //│ ╙── ^^^^^^^^^^ @@ -67,7 +71,7 @@ if (foo of x => x + 1) then 1 :e if foo of x => x + 1 then 1 //│ ╔══[COMPILATION ERROR] Unrecognized term split (application) -//│ ║ l.68: if foo of x => x + 1 then 1 +//│ ║ l.72: if foo of x => x + 1 then 1 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/newres/ControlFlowResults.mls b/hkmc2/shared/src/test/mlscript/newres/ControlFlowResults.mls index 765a8d0f11..07f74d026f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ControlFlowResults.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ControlFlowResults.mls @@ -81,3 +81,124 @@ private fun maybeAnswer(fail) = if fail then throw new Error("no answer") else {answer: 42} maybeAnswer(false).answer //│ = 42 + +// Explicit returns supply function results, including returns in statements. +:expect [42, 43] +fun returned() = return {answer: 42} +fun early() = + return {answer: 43} + () +[returned().answer, early().answer] +//│ = [42, 43] + +// Conditional returns and fall-through results preserve the calling context. +:expect [42, 43] +private fun choose(flag, value) = + if flag do return value + value +[choose(true, {first: 42}).first, choose(false, {second: 43}).second] +//│ = [42, 43] + +// Returns in nested definitions belong to those definitions only. +:expect 42 +fun enclosing() = + fun nested() = return {other: 0} + {answer: 42} +enclosing().answer +//│ = 42 + +// Abrupt cleanup replaces the pending return. +:expect 43 +fun cleanupReturn() = + try return {other: 42} + finally return {answer: 43} +cleanupReturn().answer +//│ = 43 + +// A return's operand never becomes an assignment's normal value. +:expect 42 +fun noAssignmentResult() = + let value = return {answer: 42} + value.other +noAssignmentResult().answer +//│ = 42 + +// Labels collect their own break values, not a nested label's values. +:expect [42, 43] +let first = do outerLabel: + do innerLabel: innerLabel.break({other: 0}) + outerLabel.break({answer: 42}) +let second = do exitLabel: {answer: 43} +[first.answer, second.answer] +//│ = [42, 43] +//│ first = {answer: 42} +//│ second = {answer: 43} + +// A break without a value yields unit; statements after it are unreachable. +:e +let stopped = do exitLabel: + exitLabel.break + {answer: 42} +stopped.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.142: stopped.answer +//│ ║ ^^^^^^^^^^^^^^ +//│ ╙── Unit value does not contain member 'answer' +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ stopped = undefined + +// Transparent annotations retain the target's value interface. +:expect 42 +let annotated = @annotations.untyped {answer: 42} +annotated.answer +//│ = 42 +//│ annotated = {answer: 42} + +// Explicit contextual arguments have the same tuple layout as ordinary arguments. +:expect 42 +private fun contextual(using value: {answer: Int}) = value +contextual(using {answer: 42}).answer +//│ = 42 + +// Cleanup can replace a pending labeled break with a different value. +:expect 43 +let cleaned = do exitLabel: + try exitLabel.break({other: 42}) + finally exitLabel.break({answer: 43}) +cleaned.answer +//│ = 43 +//│ cleaned = {answer: 43} + +// Result annotations check a closure returned by an explicit return. +:expect 42 +class ReturnedAnswer(val answer: Int) +fun returnedClosure(): Int -> ReturnedAnswer = return (value => new ReturnedAnswer(value)) +returnedClosure()(42).answer +//│ = 42 + +// Publicly returned closures must not infer their interface only from local calls. +:e +fun exposedClosure() = return value => value.answer +exposedClosure()({answer: 42}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.181: fun exposedClosure() = return value => value.answer +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── Parameter 'value' admits values of unknown shape. +//│ ║ l.181: fun exposedClosure() = return value => value.answer +//│ ║ ^^^^^ +//│ ╟── This value is returned here. +//│ ║ l.181: fun exposedClosure() = return value => value.answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── 'exposedClosure' is exposed by this compilation unit. +//│ ║ l.181: fun exposedClosure() = return value => value.answer +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Pattern guards execute in the enclosing function's return scope. +:expect 42 +fun guardedReturn() = + if 0 is (_ where (return {answer: 42})) then throw new Error("unreachable") + else throw new Error("unreachable") +guardedReturn().answer +//│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls index f5344a602e..c20e16729d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Dynamic.mls @@ -97,6 +97,10 @@ explicit."nested".value :e dyn +//│ ╔══[COMPILATION ERROR] Resolution error in dynamic type +//│ ║ l.99: dyn +//│ ║ ^^^ +//│ ╙── A type cannot be used as a runtime value. //│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (dynamic type) //│ ║ l.99: dyn //│ ╙── ^^^ @@ -120,10 +124,10 @@ fun notCalled(x: dyn) = x.anything(1).result let mixed = if hide(true) then ({value: 1} as dyn) else {other: 2} mixed.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.121: mixed.value +//│ ║ l.125: mixed.value //│ ║ ^^^^^^^^^^^ //│ ╟── Record literal does not contain member 'value' -//│ ║ l.120: let mixed = if hide(true) then ({value: 1} as dyn) else {other: 2} +//│ ║ l.124: let mixed = if hide(true) then ({value: 1} as dyn) else {other: 2} //│ ╙── ^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ mixed = {value: 1} diff --git a/hkmc2/shared/src/test/mlscript/newres/HandlerResults.mls b/hkmc2/shared/src/test/mlscript/newres/HandlerResults.mls index c8fbe55ba1..009cc4bd43 100644 --- a/hkmc2/shared/src/test/mlscript/newres/HandlerResults.mls +++ b/hkmc2/shared/src/test/mlscript/newres/HandlerResults.mls @@ -14,11 +14,21 @@ fun handled() = h.answer() {answer: 0} handled().answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.16: handled().answer +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── An effect handler can replace the handled body's result. +//│ ║ l.12: handle h = AnswerEffect with +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ║ l.13: fun answer()(resume) = {answer: 42} +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.14: h.answer() +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.15: {answer: 0} +//│ ╙── ^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Member reference 'AnswerEffect' has no resolved target //│ ║ l.12: handle h = AnswerEffect with //│ ╙── ^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target -//│ ║ l.16: handled().answer -//│ ╙── ^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls index c7809025e1..e12a7d5c8b 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls @@ -434,7 +434,7 @@ let changingParent = makeParent(4) class SavedParent extends changingParent { override fun f() = super.f() } set changingParent = makeParent(9) (new SavedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2838' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2840' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 4 @@ -449,7 +449,7 @@ let changingHolder = Holder(5) class SavedNestedParent extends changingHolder.Parent { override fun f() = super.f() } set changingHolder = Holder(11) (new SavedNestedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2838' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2840' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 5 diff --git a/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls b/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls new file mode 100644 index 0000000000..0b127174ed --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls @@ -0,0 +1,63 @@ +:qq +:. + +// Quotation constructs syntax; it does not return the quoted integer. +:expect Lit(42) +let syntaxValue = `42 +syntaxValue +//│ = Lit(42) +//│ syntaxValue = Lit(42) + +// Splices supply syntax to the enclosing quotation. +:expect Lam([Symbol("x")], Ref(Symbol("x"))) +x `=> x +//│ = Lam([Symbol("x")], Ref(Symbol("x"))) + +// Unknown syntax interfaces must not silently authorize static selections. +:e +let syntaxValue = `42 +syntaxValue.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.19: syntaxValue.answer +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── The shape of this value is unknown. +//│ ║ l.18: let syntaxValue = `42 +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ syntaxValue = Lit(42) + +// Type syntax is not a runtime value. +:e +let typeValue = dyn +typeValue +//│ ╔══[COMPILATION ERROR] Resolution error in dynamic type +//│ ║ l.32: let typeValue = dyn +//│ ║ ^^^ +//│ ╙── A type cannot be used as a runtime value. +//│ ╔══[COMPILATION ERROR] Unexpected term form in expression position (dynamic type) +//│ ║ l.32: let typeValue = dyn +//│ ╙── ^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ typeValue = undefined + +// Async expression annotations return a promise, not the body's record. +:e +let promise = @annotations.async {answer: 42} +promise.answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.47: promise.answer +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── The shape of this value is unknown. +//│ ║ l.46: let promise = @annotations.async {answer: 42} +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ promise = Promise {} + +// A splice executes while constructing syntax and can return from its function. +:expect 42 +fun returnedFromSplice() = + `let quotedValue = (return {answer: 42}) `in quotedValue +returnedFromSplice().answer +//│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls index f445f2130f..538f31e2e8 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/BooleansOps.mls @@ -21,6 +21,10 @@ if 5 is a & b then a //│ ╔══[COMPILATION ERROR] Name not found: b //│ ║ l.20: if 5 is a & b then a //│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Resolution error in composition +//│ ║ l.20: if 5 is a & b then a +//│ ║ ^^^^^^^^^^ +//│ ╙── A type cannot be used as a runtime value. //│ ╔══[COMPILATION ERROR] Name not found: a //│ ║ l.20: if 5 is a & b then a //│ ╙── ^ @@ -36,7 +40,7 @@ if 5 is (a & b) as c then [a, b, c] :e if 5 is ~a then a //│ ╔══[COMPILATION ERROR] Name not found: a -//│ ║ l.37: if 5 is ~a then a +//│ ║ l.41: if 5 is ~a then a //│ ╙── ^ //│ ═══[RUNTIME ERROR] Error: match error From b2c21ef3a8355f2a895d0700c6b768bc561d9756 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 20:26:21 +0800 Subject: [PATCH 218/228] Explain unsupported async result resolution and cover promise behavior --- .../scala/hkmc2/semantics/NewResolver.scala | 11 ++- .../src/test/mlscript/newres/Promises.mls | 76 +++++++++++++++++++ .../src/test/mlscript/newres/ValueForms.mls | 5 +- 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/Promises.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index aea308eef4..a621d2d7c3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -3601,10 +3601,13 @@ class NewResolver: val flow = resultFlow(body) if flow.normal then listen(body, discardMarks)(listener) flow.exits.getOrElse(S(label), Vector.empty).foreach(value => listen(value, discardMarks)(listener)) - case Annotated(Annot.Async(), _) => - // Lowering returns a JavaScript promise, not the body's value. Until the - // promise interface is declared, it cannot justify a static member lookup. - listener(UnknownValueShape.at(trm)) + case Annotated(annotation @ Annot.Async(), _) => + // Async lowering wraps the result in a promise. Point to the annotation + // that changes the result, rather than suggesting that the body's shape + // is unknown. The declared Promise interface is not yet connected here. + listener(UnknownValueShape(trm, fromPublicInterface = false)(ShapeProvenance( + (msg"This async expression returns a promise, rather than the value of its body." -> annotation.toLoc) :: + (msg"Member resolution for async results is not supported yet." -> N) :: Nil))) case Annotated(_, target) => listen(target, discardMarks)(listener) case _: Assgn | _: Drop => listener(unitResultShape) case _: Ret | _: Break | _: Throw | _: Continue => () // These expressions do not complete normally. diff --git a/hkmc2/shared/src/test/mlscript/newres/Promises.mls b/hkmc2/shared/src/test/mlscript/newres/Promises.mls new file mode 100644 index 0000000000..ed89373795 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/Promises.mls @@ -0,0 +1,76 @@ +:. + +// The prelude's declared Promise interface supports factory calls and chaining. +// Its callback type is Function; inputs inspected by callbacks need explicit types. +:await +:expect 43 +Promise.resolve(42).then((value: dyn) => value + 1) +//│ = 43 + +:await +:expect "recovered: failed" +Promise.reject("failed").then((value: dyn) => value, (reason: dyn) => "recovered: " + reason) +//│ = "recovered: failed" + +:await +:expect 42 +Promise.all([Promise.resolve(20), Promise.resolve(22)]).then((values: dyn) => values.at(0) + values.at(1)) +//│ = 42 + +// Async expressions execute, but do not yet expose the declared Promise interface. +:await +:expect {answer: 42} +@annotations.async {answer: 42} +//│ = {answer: 42} + +:e +let asyncResult = @annotations.async {answer: 42} +asyncResult.then((value: dyn) => value.answer) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.28: asyncResult.then((value: dyn) => value.answer) +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'then' of a value with unknown shape. +//│ ╟── This async expression returns a promise, rather than the value of its body. +//│ ║ l.27: let asyncResult = @annotations.async {answer: 42} +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── Member resolution for async results is not supported yet. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ asyncResult = Promise {} + +// The unknown async result must retain its origin through a function call. +:e +fun makePromise() = @annotations.async {answer: 42} +makePromise().answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.43: makePromise().answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── This async expression returns a promise, rather than the value of its body. +//│ ║ l.42: fun makePromise() = @annotations.async {answer: 42} +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── Member resolution for async results is not supported yet. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// FIXME: An async function's caller currently sees the body's shape, although +// lowering wraps the result in a promise. This selection should be rejected. +:breakme +:e +@annotations.async +fun asyncFunction() = {answer: 42} +asyncFunction().answer + +// FIXME: Await does not yet propagate the fulfilled value's interface. +:fixme +:await +:expect 42 +@annotations.async +fun awaitAnswer() = + let value = handlers.await(Promise.resolve({answer: 42})) + value.answer +awaitAnswer() +//│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target +//│ ║ l.69: value.answer +//│ ╙── ^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'Promise {}' +//│ = Promise {} diff --git a/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls b/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls index 0b127174ed..38fdcf794f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls @@ -49,9 +49,10 @@ promise.answer //│ ║ l.47: promise.answer //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. -//│ ╟── The shape of this value is unknown. +//│ ╟── This async expression returns a promise, rather than the value of its body. //│ ║ l.46: let promise = @annotations.async {answer: 42} -//│ ╙── ^^^^^^^^^^ +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── Member resolution for async results is not supported yet. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ promise = Promise {} From db4ff5f35703a080f15b3c4f4462d90d1d524d9c Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 21:37:32 +0800 Subject: [PATCH 219/228] Resolve async function results as promises and migrate async worksheets --- .../test/scala/hkmc2/TypeRelationTest.scala | 2 +- .../scala/hkmc2/semantics/Elaborator.scala | 1 + .../hkmc2/semantics/InterfaceExposure.scala | 5 +- .../scala/hkmc2/semantics/NewResolver.scala | 109 ++++--- .../main/scala/hkmc2/semantics/Shape.scala | 24 +- .../src/main/scala/hkmc2/semantics/Term.scala | 7 + .../test/mlscript-compile/AsyncLibrary.mls | 15 + .../src/test/mlscript/decls/Prelude.mls | 4 +- .../src/test/mlscript/handlers/Async.mls | 11 +- .../src/test/mlscript/newres/Overrides.mls | 4 +- .../src/test/mlscript/newres/Promises.mls | 270 ++++++++++++++++-- .../src/test/mlscript/newres/ValueForms.mls | 5 +- 12 files changed, 372 insertions(+), 85 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript-compile/AsyncLibrary.mls diff --git a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala index 76e7c63b6e..7496253eda 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/TypeRelationTest.scala @@ -399,7 +399,7 @@ class TypeRelationTest extends AnyFunSuite: val defn = ClassDef.Plain(N, Cls, symbol, BlockMemberSymbol("Closed", Nil), Nil, N, ObjBody(Term.Blk(Nil, Term.UnitVal())), N, Nil, Nil, N)(N) h.state.lexicalTypeBinders(symbol) = Set.empty - val original = NominalInstanceView(defn, Map.empty, N)(N)(h.resolver) + val original = NominalInstanceView(defn, Map.empty, N)(N, ShapeProvenance.empty)(h.resolver) val scheme = h.tpe(TypeShape.Top).resolution val substitutions = (1 to 16).map: index => val (parameter, _) = h.parameter(s"A$index") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 5b351d99f7..de6c0b4520 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -298,6 +298,7 @@ object Elaborator: val Str = assumeBuiltinCls("Str") val BigInt = assumeBuiltinCls("BigInt") val Function = assumeBuiltinCls("Function") + lazy val Promise = assumeBuiltinCls("Promise") val Error = assumeBuiltinCls("Error") val Bool = assumeBuiltinCls("Bool") val Object = assumeBuiltinCls("Object") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index 9a39ade1a1..33a6e25942 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -179,9 +179,8 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) unknown.enter(marks) match case value: TermShape => resolver.exposeTypeHoles(input, value, marks) case NoShape => () - callable.result.foreach: result => - watch((result, marks))(resolver.listenTypeInstances(result)): shape => - emit(shape.exit(marks), path) + watch((callable, marks, "call result"))(resolver.listenCallableResult(callable)): shape => + emit(shape.exit(marks), path) case nominal: NominalInstanceView => // Array element bindings may contain escaping closures. Follow only the // declared binding; concrete annotations still hide implementation shapes. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index a621d2d7c3..6dd8149dbe 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -875,7 +875,7 @@ class NewResolver: val bindings = instances.filter((parameter, _) => lexical(parameter)) .map((parameter, instance) => parameter -> instanceType(instance)) ++ nominal.bindings.map((parameter, bound) => parameter -> bound.instantiate(instances)) - nominal.copy(bindings = bindings, parent = nominal.parent.map(instantiateShape(_, instances)))(nominal.annotation)(this) + nominal.copy(bindings = bindings, parent = nominal.parent.map(instantiateShape(_, instances)))(nominal.annotation, nominal.provenance)(this) case record: RecordTypeShape => RecordTypeShape(record.fields.map((name, tpe) => name -> tpe.instantiate(instances)))(record.source, record.declarations) case contextual: ContextualShape => contextual.copy(instances = instances.withOverrides(contextual.instances)) @@ -996,7 +996,7 @@ class NewResolver: val parameters = nominalLexicalBinders(defn) ++ defn.tparams.map(_.sym) val bindings = bind(defn.tparams).filter((parameter, _) => parameters(parameter)) defn.ext match - case N => publish(NominalInstanceView(defn, bindings, implicitParent(defn))(S(source))(this)) + case N => publish(NominalInstanceView(defn, bindings, implicitParent(defn))(S(source), ShapeProvenance.empty)(this)) case S(parent) => // Only the parent's declared type is relevant here. Evaluating // its constructor arguments would reintroduce implementation flow. @@ -1007,7 +1007,7 @@ class NewResolver: withTypeDependencies(parentType): dependencies => val local = captureNominalBindings(defn, bindings.filter((symbol, _) => dependencies(symbol))) listenTypeViews(declaredType(parentType, local), mergeIntersections): ext => - publish(NominalInstanceView(defn, bindings, S(ext))(S(source))(this)) + publish(NominalInstanceView(defn, bindings, S(ext))(S(source), ShapeProvenance.empty)(this)) case TypeShape.Alias(symbol, rhs) => // Revisiting the same alias reference without reaching a structural // shape supplies no additional interface. Track @@ -1053,7 +1053,7 @@ class NewResolver: , fields.exists(!_.isInstanceOf[Fld]), N) case single => DeclaredParams(S(declaredType(typeResolution(single), current.copy(positive = !current.positive)(current.origin))) :: Nil, false, N) publish(CallableTypeShape(source, ps ne_:: Nil, - S(declaredType(ret, current)), N, N, N)) + S(declaredType(ret, current)), N, N, N, N)) case TypeShape.Polymorphic(params, _, body) => val symbols = params.map(_.parameter.symbol) val lexical = current.copy(bindings = current.bindings -- symbols, instances = current.instances.without(symbols))(current.origin) @@ -1117,6 +1117,50 @@ class NewResolver: case S(p: Param) => p.sign case _ => td.resultSignature + private def promiseShape(annotation: Annot.Async): NominalInstanceView = + val cls = prelude.builtins.Promise.defn.get + // The prelude currently exposes an unparameterized Promise interface. Do + // not propagate the body's fields to the wrapper or invent a payload type. + softAssert(cls.tparams.isEmpty && cls.ext.isEmpty, + "The builtin Promise must expose a non-generic interface with the implicit Object parent") + val provenance = ShapeProvenance( + msg"This async annotation wraps the body's result in a promise." -> N :: Nil).withTypeOrigin(annotation) + NominalInstanceView(cls, Map.empty, implicitParent(cls))(N, provenance)(this) + + /** Result annotations constrain the body before async lowering. Consumers of + * the function observe the wrapper, including when the body always throws. + * Keep this distinction shared by calls and higher-order inference. + * Declared types use the supplied instances, while inferred values stay at + * their lexical endpoint: callers must exit the definition's scope before + * substituting them, so candidates from distinct calls remain separate. + */ + private def listenDefinitionResult(td: TermDefinition, instances: TypeSubstitution)(listener: Listener)(using NewResolverState): Unit = + td.asyncAnnotation match + case S(annotation) => listener(promiseShape(annotation)) + case N => td.sign match + case S(sign) => listenSignatureResult(declaredType(typeResolution(sign), Map.empty).instantiate(instances), + if td.flags.hasResultAnnotation then 0 else td.params.length)(listener) + case N => td.body.foreach(body => listenResult(body)(listener)) + + /** Observe one application of a declared callable. Partial application keeps + * the async wrapper pending, even across the nested arrows of a full signature. + */ + private[semantics] def listenCallableResult(callable: CallableTypeShape)(listener: Listener)(using NewResolverState): Unit = + callable.asyncResult match + case S(AsyncResult(annotation, 1)) => + softAssert(callable.paramLists.tail.isEmpty) + listener(promiseShape(annotation)) + case pending => + val remaining = pending.map(info => info.copy(remainingLists = info.remainingLists - 1)) + callable.paramLists.tail match + case tail: NELs[DeclaredParams] => listener(callable.copy(paramLists = tail, asyncResult = remaining)) + case Nil => callable.result match + case S(tpe) if remaining.nonEmpty => listenTypeViews(tpe): + case Marked(next: CallableTypeShape, marks) => listener(Marked(next.copy(asyncResult = remaining), marks)) + case other => listener(other) + case S(tpe) => listenTypeInstances(tpe)(listener) + case N => listener(UnknownValueShape.at(callable.source)) + /** Retain a scope transfer on the reference, including deferred fields and * both argument parts. Flatten existing transfers before interning, so a * recursive projection cannot build a tower of transport wrappers. @@ -1189,7 +1233,8 @@ class NewResolver: val generic = interface match case callable: CallableTypeShape if !isByName(td) => val parameters = td.tparams.toList.flatten.map(p => declaredParameter(p.sym)) ::: callable.tparams - callable.copy(scheme = if parameters.isEmpty then N else S(TypeScheme(td.tsym, parameters)), declaration = S(td)) + callable.copy(scheme = if parameters.isEmpty then N else S(TypeScheme(td.tsym, parameters)), declaration = S(td), + asyncResult = td.asyncAnnotation.map(AsyncResult(_, td.params.length))) case instance: InstanceShape if !isByName(td) => InstanceShape(quantifiedType(instance.tpe, td.tparams.toList.flatten.map(_.sym))) case _ => interface @@ -1211,7 +1256,9 @@ class NewResolver: else td.params match case params: NELs[ParamList] => publish(CallableTypeShape(source, params.ne_map(ps => DeclaredParams(ps.params.map(_.sign.map(signature(_, !positive))), - ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature(_, !positive)))), result, N, N, S(td))) + ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature(_, !positive)))), + result, N, N, S(td), N)) + case Nil if td.asyncAnnotation.nonEmpty => publish(promiseShape(td.asyncAnnotation.get)) case Nil => result match case S(tpe) => listenTypeInstances(tpe)(publish) case N => @@ -1526,10 +1573,8 @@ class NewResolver: case Lam(_, body) => listenResult(body)(shape => listener(instantiateShape(shape, instances)))(using rstate.withInstances(instances)) case _ => () case ds: DefnShape => ds.defn match - case td: TermDefinition => td.sign match - case S(sign) => listenSignatureResult(declaredType(typeResolution(sign), Map.empty).instantiate(instances), - if td.flags.hasResultAnnotation then 0 else td.params.length)(listener) - case N => td.body.foreach(body => listenResult(body)(shape => listener(instantiateShape(shape, instances)))(using rstate.withInstances(instances))) + case td: TermDefinition => listenDefinitionResult(td, instances)(shape => + listener(instantiateShape(shape, instances)))(using rstate.withInstances(instances)) case _ => () case _ => () def matchLists(expected: DeclaredType, remaining: Ls[(ParamList, Ls[Marks])])(using NewResolverState): Unit = @@ -1592,9 +1637,7 @@ class NewResolver: def receive(shape: TermShape)(using NewResolverState): Unit = shape.exit(actualContext).enter(expectedContext) match case value: TermShape => inferTypeArguments(ret, value, marks) case NoShape => () - givenType.paramLists.tail match - case Nil => givenType.result.foreach(listenTypeInstances(_)(receive)) - case tail: NELs[DeclaredParams] => receive(givenType.copy(paramLists = tail)) + listenCallableResult(givenType)(receive) case _ => () case _ => matchLists(expected, actual.unappliedParams) @@ -1617,7 +1660,7 @@ class NewResolver: val cls = prelude.builtins.Array.defn.get softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") val elements = new TypeResolution(tuple.source, messages => resolError(tuple.source, messages)) - val parent = NominalInstanceView(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), implicitParent(cls))(N)(this) + val parent = NominalInstanceView(cls, Map(cls.tparams.head.sym -> declaredType(elements, Map.empty)), implicitParent(cls))(N, ShapeProvenance.empty)(this) tupleArrayParents(new Identity(tuple)) = parent tuple.segments.foreach: segment => listenTupleField(segment.element): value => @@ -1645,7 +1688,7 @@ class NewResolver: // on the whole interface would also exit a selected method's class scope // twice, once here and once in NominalInstanceView.getMemberImpl. val element = transportType(declaredType(elements, Map.empty), context :: Nil) - val shape = NominalInstanceView(cls, Map(param -> element), implicitParent(cls))(N)(this) + val shape = NominalInstanceView(cls, Map(param -> element), implicitParent(cls))(N, ShapeProvenance.empty)(this) rstate.mutableArrays(new Identity(source)) = shape listenTerm(underlying): tuple => listenArrayElements(tuple): element => @@ -2432,11 +2475,7 @@ class NewResolver: case value: TermShape => publishViewed(res, value, TypeSubstitution.empty)(using rstate.withInstances(callerInstances)) case NoShape => () - callable.paramLists.tail match - case Nil => callable.result match - case S(tpe) => listenTypeInstances(tpe)(publish) - case N => publish(UnknownValueShape.at(callable.source)) - case tail: NELs[DeclaredParams] => publish(callable.copy(paramLists = tail)) + listenCallableResult(callable)(publish) return case _ => () // log(s"appShape? lhs = $lhs, args = $args, res = $res") @@ -2488,15 +2527,10 @@ class NewResolver: register case _ => log(s"appShape: td.body = ${td.body}") - td.sign match - case S(sign) => - val count = if td.flags.hasResultAnnotation then 0 else td.params.length - listenSignatureResult(declaredType(typeResolution(sign), Map.empty).instantiate(instances), count): result => - transportShape(result, mss) match - case value: TermShape => - publish(value) - case NoShape => () - case N => td.body.foreach(go(_, mss)) + listenDefinitionResult(td, instances): result => + transportShape(result, mss) match + case value: TermShape => publish(value) + case NoShape => () case _ => softAssert(rstate.hasError(res)) case (sh: IntroShape, mss) => @@ -2940,7 +2974,9 @@ class NewResolver: def showDbg(using DebugPrinter): Str = s"result of ${td.tsym.nme}" rstate.byNameResults(key) = host listenValueHost(host)(listener) - listenResult(body)(publishActivated(host, _)) + td.asyncAnnotation match + case S(annotation) => publishActivated(host, promiseShape(annotation)) + case N => listenResult(body)(publishActivated(host, _)) def pipeTerm(from: Term, to: ShapeHost)(using NewResolverState): Unit = log(s"pipeTerm: from = ${from.showDbg}, to = ${to.showDbg}; ${to.currentShapes}") @@ -2950,7 +2986,7 @@ class NewResolver: // Classes without an explicit parent expose Object's declarations. Share this // root interface between inferred instances, nominal annotations, and this-values; // Object itself must terminate the chain. This does not add a runtime constructor call. - private lazy val objectShape = NominalInstanceView(prelude.builtins.Object.defn.get, Map.empty, N)(N)(this) + private lazy val objectShape = NominalInstanceView(prelude.builtins.Object.defn.get, Map.empty, N)(N, ShapeProvenance.empty)(this) private def implicitParent(defn: ClassLikeDef): Opt[TermShape] = if defn.sym is prelude.builtins.Object then N else S(objectShape) @@ -3091,7 +3127,8 @@ class NewResolver: else callerInstances.without(td.tparams.toList.flatten.map(_.sym)) def receive(shape: TermShape)(using NewResolverState): Unit = wrappedListener(instantiateShape(shape, instances))(using rstate.withInstances(callerInstances)) - resultSignature(td) match + if td.asyncAnnotation.nonEmpty then receive(promiseShape(td.asyncAnnotation.get)) + else resultSignature(td) match case S(sign) => val result = declaredType(typeResolution(sign), Map.empty).instantiate(instances) val tpe = if isByName(td) then result else quantifiedType(result, td.tparams.toList.flatten.map(_.sym)) @@ -3601,13 +3638,7 @@ class NewResolver: val flow = resultFlow(body) if flow.normal then listen(body, discardMarks)(listener) flow.exits.getOrElse(S(label), Vector.empty).foreach(value => listen(value, discardMarks)(listener)) - case Annotated(annotation @ Annot.Async(), _) => - // Async lowering wraps the result in a promise. Point to the annotation - // that changes the result, rather than suggesting that the body's shape - // is unknown. The declared Promise interface is not yet connected here. - listener(UnknownValueShape(trm, fromPublicInterface = false)(ShapeProvenance( - (msg"This async expression returns a promise, rather than the value of its body." -> annotation.toLoc) :: - (msg"Member resolution for async results is not supported yet." -> N) :: Nil))) + case Annotated(annotation @ Annot.Async(), _) => listener(promiseShape(annotation)) case Annotated(_, target) => listen(target, discardMarks)(listener) case _: Assgn | _: Drop => listener(unitResultShape) case _: Ret | _: Break | _: Throw | _: Continue => () // These expressions do not complete normally. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala index 213c59ae42..73fce7489f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Shape.scala @@ -27,7 +27,7 @@ sealed trait Shape extends ShapeEvent, ShapeLike: */ final def diagnostic(message: Message): Ls[(Message, Opt[Loc])] = this match case value: TermShape => value.applicationHead._1 match - case view: NominalInstanceView => (message -> view.annotation.flatMap(_.toLoc).orElse(toLoc)) :: Nil + case view: NominalInstanceView => view.provenance.diagnostic(message, view.annotation.flatMap(_.toLoc).orElse(toLoc)) case view: ContextualShape => view.source.diagnostic(message) case view: SpecializedShape => view.declaration.diagnostic(message) case unknown: UnknownValueShape => unknown.provenance.diagnostic(message, toLoc) @@ -561,9 +561,11 @@ final case class ActivatedShapeEvent(value: Shape, instances: TypeSubstitution) * In particular, selecting an unannotated field does not inspect its initializer. * The annotation is a diagnostic witness, excluded from shape equality; synthesized * interfaces such as a tuple's Array parent have no written annotation. + * Provenance can instead identify the construct that introduces an interface, + * such as the async annotation that creates a Promise result. */ final case class NominalInstanceView(defn: ClassLikeDef, bindings: Map[VarSymbol, DeclaredType], - parent: Opt[TermShape])(val annotation: Opt[Term])(resolver: NewResolver) extends CoreHeadShape: + parent: Opt[TermShape])(val annotation: Opt[Term], val provenance: ShapeProvenance)(resolver: NewResolver) extends CoreHeadShape: def describe: Str = s"value of type '${defn.sym.nme}'" def toLoc: Opt[Loc] = defn.toLoc override def isInstanceOfClass(cls: ClassLikeDef)(using NewResolverState): Bool = @@ -584,6 +586,13 @@ final case class NominalInstanceView(defn: ClassLikeDef, bindings: Map[VarSymbol */ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool, rest: Opt[DeclaredType]) +/** Async lowering wraps the result only after all written parameter lists have + * been applied. A separate signature can encode those lists as nested arrows, + * so the pending wrapper must survive observing the next callable interface. + */ +final case class AsyncResult(annotation: Annot.Async, remainingLists: Int): + require(remainingLists > 0) + /** Calls through annotations expose only the declared result. Argument shapes * constrain type parameters in the interface; they do not recover its implementation. * `scheme` retains binders that are still available for instantiation; capture @@ -594,7 +603,7 @@ final case class DeclaredParams(params: Ls[Opt[DeclaredType]], hasRest: Bool, re */ final case class CallableTypeShape(source: Term, paramLists: NELs[DeclaredParams], result: Opt[DeclaredType], scheme: Opt[TypeScheme], supplied: Opt[Ls[DeclaredType]], - declaration: Opt[TermDefinition]) extends CoreHeadShape: + declaration: Opt[TermDefinition], asyncResult: Opt[AsyncResult]) extends CoreHeadShape: def tparams: Ls[DeclaredTypeParameter] = scheme.toList.flatMap(_.parameters) def describe: Str = declaration.fold("function with a declared signature")(d => s"function '${d.bsym.nme}'") def toLoc: Opt[Loc] = declaration.fold(source.toLoc)(_.toLoc) @@ -796,7 +805,7 @@ final case class DynShape() extends CoreHeadShape: * Discovery retains one witness per reached shape to bound recursive paths. */ final class ShapeProvenance private (path: => Ls[(Message, Opt[Loc])], notes: => Ls[(Message, Opt[Loc])], - typeOrigin: Opt[Term]): + typeOrigin: Opt[Located]): private lazy val pathNotes = path private lazy val originNotes = notes /** A type origin anchors the operation's explanation: first trace the value @@ -809,10 +818,11 @@ final class ShapeProvenance private (path: => Ls[(Message, Opt[Loc])], notes: => case N => (message -> fallback) :: pathNotes ::: originNotes def via(note: => (Message, Opt[Loc])): ShapeProvenance = new ShapeProvenance(note :: pathNotes, originNotes, typeOrigin) - /** Keep the written type restricting this observation separate from the path - * through parameters, returns, and storage that led to the observation. + /** Keep the annotation providing this interface separate from the path through + * parameters, returns, and storage that led to the observation. Besides written + * types, an async annotation provides the Promise interface of its result. */ - def withTypeOrigin(source: Term): ShapeProvenance = + def withTypeOrigin(source: Located): ShapeProvenance = new ShapeProvenance(pathNotes, originNotes, S(source)) object ShapeProvenance: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 03284d2ea6..22b21925d8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -888,6 +888,13 @@ final case class TermDefinition( require(k is tsym.k) def bsym: BlockMemberSymbol = sym val owner = tsym.owner + /** Only function bodies are wrapped by AsyncLowering; annotations on other + * definitions are ignored with a warning and must not change their interface. + */ + def asyncAnnotation: Opt[Annot.Async] = + if (k is syntax.Fun) && body.nonEmpty then annotations.collectFirst: + case annotation: Annot.Async => annotation + else N /** A result annotation denotes the whole result; a separate function signature * includes the arrows corresponding to the definition's written parameter lists. */ diff --git a/hkmc2/shared/src/test/mlscript-compile/AsyncLibrary.mls b/hkmc2/shared/src/test/mlscript-compile/AsyncLibrary.mls new file mode 100644 index 0000000000..ce8f15c74b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/AsyncLibrary.mls @@ -0,0 +1,15 @@ +#lang(0.3.x, strictResolution: true) + +module AsyncLibrary with + @annotations.async + fun inferred() = {answer: 53} + @annotations.async + fun typed(value: Int): {answer: Int} = {answer: value} + fun curried: Int -> Int -> Int + @annotations.async + fun curried(x)(y) = x + y + @annotations.async + fun getter: Int = 56 + class Methods with + @annotations.async + fun result(value: Int): Int = value diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 54f1cb00a9..3b9665641a 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -176,7 +176,7 @@ declare module BigInt64Array declare class BigUint64Array declare module BigUint64Array -declare class Promise { +declare class Promise(executor: (Function, Function) -> Any) { fun id"then"(onFulfilled: Function, ...onRejected): Promise } declare module Promise { @@ -560,7 +560,7 @@ declare module annotations { } declare module handlers { - fun await + fun await(promise: Promise): Any } declare module scope { diff --git a/hkmc2/shared/src/test/mlscript/handlers/Async.mls b/hkmc2/shared/src/test/mlscript/handlers/Async.mls index 383602c03f..97c8599306 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Async.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Async.mls @@ -1,3 +1,4 @@ +:. :js :lift @@ -7,9 +8,9 @@ open annotations open handlers @async -fun f(print_result) = +fun f(print_result: Bool) = print("Hello, world!") - let r = await(fs.promises.readFile("./README.md")) + let r = (await(fs.promises.readFile("./README.md")) as dyn) let readme = r.toString() let first = readme.slice(0, readme.indexOf("\n")) if print_result do @@ -41,7 +42,7 @@ f() fun f() = await(Promise.resolve(1)) //│ ╔══[COMPILATION ERROR] Only await inside of async bodies are allowed if effect handlers are not enabled. -//│ ║ l.42: await(Promise.resolve(1)) +//│ ║ l.43: await(Promise.resolve(1)) //│ ╙── ^^^^^ :re @@ -75,11 +76,11 @@ f() fun f() = print(0) - let r = await(fs.promises.readFile("./README.md")) + let r = (await(fs.promises.readFile("./README.md")) as dyn) let readme = r.toString() let first = readme.slice(0, readme.indexOf("\n")) print(first) - let r2 = await(fs.promises.readdir(".")) + let r2 = (await(fs.promises.readdir(".")) as dyn) print(r2.indexOf("README.md") >= 0) @async diff --git a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls index e12a7d5c8b..5ec290b830 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls @@ -434,7 +434,7 @@ let changingParent = makeParent(4) class SavedParent extends changingParent { override fun f() = super.f() } set changingParent = makeParent(9) (new SavedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2840' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2874' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 4 @@ -449,7 +449,7 @@ let changingHolder = Holder(5) class SavedNestedParent extends changingHolder.Parent { override fun f() = super.f() } set changingHolder = Holder(11) (new SavedNestedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2840' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2874' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 5 diff --git a/hkmc2/shared/src/test/mlscript/newres/Promises.mls b/hkmc2/shared/src/test/mlscript/newres/Promises.mls index ed89373795..63d87101b1 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Promises.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Promises.mls @@ -17,47 +17,45 @@ Promise.reject("failed").then((value: dyn) => value, (reason: dyn) => "recovered Promise.all([Promise.resolve(20), Promise.resolve(22)]).then((values: dyn) => values.at(0) + values.at(1)) //│ = 42 -// Async expressions execute, but do not yet expose the declared Promise interface. +// Async expressions expose the same Promise interface as declared factories. :await :expect {answer: 42} @annotations.async {answer: 42} //│ = {answer: 42} -:e +:await +:expect 42 let asyncResult = @annotations.async {answer: 42} asyncResult.then((value: dyn) => value.answer) -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.28: asyncResult.then((value: dyn) => value.answer) -//│ ║ ^^^^^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'then' of a value with unknown shape. -//│ ╟── This async expression returns a promise, rather than the value of its body. -//│ ║ l.27: let asyncResult = @annotations.async {answer: 42} -//│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ╙── Member resolution for async results is not supported yet. -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ = 42 //│ asyncResult = Promise {} -// The unknown async result must retain its origin through a function call. +// The Promise interface and its async origin survive a function call. :e fun makePromise() = @annotations.async {answer: 42} makePromise().answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.43: makePromise().answer +//│ ║ l.36: makePromise().answer //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. -//│ ╟── This async expression returns a promise, rather than the value of its body. -//│ ║ l.42: fun makePromise() = @annotations.async {answer: 42} +//│ ╟── Value of type 'Promise' does not contain member 'answer' +//│ ║ l.35: fun makePromise() = @annotations.async {answer: 42} //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ╙── Member resolution for async results is not supported yet. +//│ ╙── This async annotation wraps the body's result in a promise. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -// FIXME: An async function's caller currently sees the body's shape, although -// lowering wraps the result in a promise. This selection should be rejected. -:breakme +// Async functions expose Promise, so selecting a field of their body is invalid. :e @annotations.async fun asyncFunction() = {answer: 42} asyncFunction().answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.50: asyncFunction().answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Promise' does not contain member 'answer' +//│ ║ l.48: @annotations.async +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── This async annotation wraps the body's result in a promise. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. // FIXME: Await does not yet propagate the fulfilled value's interface. :fixme @@ -68,9 +66,235 @@ fun awaitAnswer() = let value = handlers.await(Promise.resolve({answer: 42})) value.answer awaitAnswer() -//│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target -//│ ║ l.69: value.answer -//│ ╙── ^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.67: value.answer +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ║ in ../decls/Prelude.mls +//│ ║ l.562: fun await(promise: Promise): Any +//│ ║ ^^^ +//│ ╟── Type 'Any' is declared without a member interface. +//│ ║ l.2: declare type Any +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ ═══[RUNTIME ERROR] Expected: '42', got: 'Promise {}' //│ = Promise {} + +// Both inferred and annotated bodies expose Promise at the call boundary. +:await +:expect 42 +@annotations.async +fun inferred() = {answer: 42} +inferred().then((value: {answer: Int}) => value.answer) +//│ = 42 + +:await +:expect 43 +@annotations.async +fun typed(value: Int): {answer: Int} = {answer: value} +typed(43).then((value: {answer: Int}) => value.answer) +//│ = 43 + +:e +typed(43).answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.99: typed(43).answer +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Promise' does not contain member 'answer' +//│ ║ l.93: @annotations.async +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── This async annotation wraps the body's result in a promise. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Returning explicitly, or returning a closure, must not expose the payload. +:await +:expect 44 +@annotations.async +fun explicitReturn() = return {answer: 44} +explicitReturn().then((value: {answer: Int}) => value.answer) +//│ = 44 + +:e +@annotations.async +fun returnedClosure() = () => 42 +returnedClosure()() +//│ ╔══[COMPILATION ERROR] Resolution error in application +//│ ║ l.120: returnedClosure()() +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Promise' cannot be called like a function. +//│ ║ l.118: @annotations.async +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── This async annotation wraps the body's result in a promise. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Async getters and methods obey the same rule as ordinary functions. +:await +:expect 45 +@annotations.async +fun asyncGetter: {answer: Int} = {answer: 45} +asyncGetter.then((value: {answer: Int}) => value.answer) +//│ = 45 + +:await +:expect 46 +class AsyncMethods with + @annotations.async + fun fetch(value: Int): {answer: Int} = {answer: value} +(new AsyncMethods).fetch(46).then((value: {answer: Int}) => value.answer) +//│ = 46 + +// Partial application is still a function; only the last call returns Promise. +:await +:expect 47 +@annotations.async +fun curried(x: Int)(y: Int): Int = x + y +let remaining = curried(20) +remaining(27).then((value: Int) => value) +//│ = 47 +//│ remaining = fun + +:e +curried(20).then((value: Int) => value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.157: curried(20).then((value: Int) => value) +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Function 'curried' does not contain member 'then' +//│ ║ l.149: @annotations.async +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ║ l.150: fun curried(x: Int)(y: Int): Int = x + y +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// Generic result inference through a callback must infer Promise, not the body. +:await +:expect 42 +fun invoke[T](callback: () -> T): T = callback() +invoke(inferred).then((value: {answer: Int}) => value.answer) +//│ = 42 + +:await +:expect 48 +invoke(() => typed(48)).then((value: {answer: Int}) => value.answer) +//│ = 48 + +// Throwing bodies still have the async calling interface, even without returns. +@annotations.async +fun throws() = throw new Error("failed") +fun inspectThrows() = throws().then((value: Any) => 0) + +// The built-in Promise must be used even when a local declaration shadows it. +:await +:expect 49 +fun shadowed() = + class Promise + @annotations.async + fun result() = 49 + result().then((value: Int) => value) +shadowed() +//│ = 49 + +// Separate signatures describe the body's result, including curried arrows. +:await +:expect 50 +fun separatelyTyped: Int -> Int -> Int +@annotations.async +fun separatelyTyped(x)(y) = x + y +let typedRemaining = separatelyTyped(20) +typedRemaining(30).then((value: Int) => value) +//│ = 50 +//│ typedRemaining = fun + +:e +separatelyTyped(20).then((value: Int) => value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.208: separatelyTyped(20).then((value: Int) => value) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Function with a declared signature does not contain member 'then' +//│ ║ l.199: fun separatelyTyped: Int -> Int -> Int +//│ ╙── ^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// A generic async signature must retain Promise after explicit specialization. +:await +:expect 51 +@annotations.async +fun generic[T](value: T): T = value +generic[Int](51).then((value: Int) => value) +//│ = 51 + +// Typed higher-order inference must also see the wrapped result. +:await +:expect 52 +fun invokeWith[T](callback: Int -> T): T = callback(52) +invokeWith(typed).then((value: {answer: Int}) => value.answer) +//│ = 52 + +// Imported interfaces retain async annotations through cloning and completion. +import "../../mlscript-compile/AsyncLibrary.mls" + +:await +:expect 53 +AsyncLibrary.inferred().then((value: {answer: Int}) => value.answer) +//│ = 53 + +:await +:expect 54 +AsyncLibrary.typed(54).then((value: {answer: Int}) => value.answer) +//│ = 54 + +:await +:expect 55 +AsyncLibrary.curried(20)(35).then((value: Int) => value) +//│ = 55 + +:await +:expect 56 +AsyncLibrary.getter.then((value: Int) => value) +//│ = 56 + +:await +:expect 57 +fun nominalMethod(receiver: AsyncLibrary.Methods) = + receiver.result(57).then((value: Int) => value) +nominalMethod(new AsyncLibrary.Methods) +//│ = 57 + +:e +AsyncLibrary.inferred().answer +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.263: AsyncLibrary.inferred().answer +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Promise' does not contain member 'answer' +//│ ║ in ../../mlscript-compile/AsyncLibrary.mls +//│ ║ l.3: @annotations.async +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╙── This async annotation wraps the body's result in a promise. +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. + +// The pending wrapper survives higher-order inference after partial application. +:await +:expect 58 +fun finish[T](callback: Int -> T): T = callback(38) +finish(separatelyTyped(20)).then((value: Int) => value) +//│ = 58 + +// An arrow in the payload type is not an extra parameter list of the definition. +:await +:expect 59 +fun signedClosure: () -> () -> Int +@annotations.async +fun signedClosure() = () => 59 +signedClosure().then((callback: () -> Int) => callback()) +//│ = 59 + +// An ineffective annotation on a value must not change its interface. +:w +:expect 60 +@annotations.async +val plainValue = {answer: 60} +plainValue.answer +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.293: @annotations.async +//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ = 60 +//│ plainValue = {answer: 60} diff --git a/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls b/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls index 38fdcf794f..b628d99ea8 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ValueForms.mls @@ -48,11 +48,10 @@ promise.answer //│ ╔══[COMPILATION ERROR] Resolution error in selection //│ ║ l.47: promise.answer //│ ║ ^^^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. -//│ ╟── This async expression returns a promise, rather than the value of its body. +//│ ╟── Value of type 'Promise' does not contain member 'answer' //│ ║ l.46: let promise = @annotations.async {answer: 42} //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ╙── Member resolution for async results is not supported yet. +//│ ╙── This async annotation wraps the body's result in a promise. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ promise = Promise {} From 866e903e05675167df493b6435e79f1ae98443ae Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 22:10:40 +0800 Subject: [PATCH 220/228] Preserve promise payload types through async resolution and await --- doc/new-resolution-type-value-flow.md | 25 +- .../scala/hkmc2/semantics/Elaborator.scala | 1 + .../hkmc2/semantics/InterfaceExposure.scala | 2 + .../scala/hkmc2/semantics/NewResolver.scala | 139 ++++++-- .../hkmc2/semantics/NewResolverState.scala | 8 + .../test/mlscript/basics/MiscArrayTests.mls | 2 +- .../src/test/mlscript/decls/Prelude.mls | 14 +- .../src/test/mlscript/handlers/Async.mls | 19 +- .../src/test/mlscript/interop/Arrays.mls | 24 +- .../test/mlscript/meta/CompilerFiction.mls | 2 +- .../src/test/mlscript/newres/Arrays.mls | 24 +- .../test/mlscript/newres/GenericMethods.mls | 2 +- .../src/test/mlscript/newres/Overrides.mls | 4 +- .../newres/PatternDefinitionBindings.mls | 2 +- .../src/test/mlscript/newres/Promises.mls | 299 +++++++++++++++--- .../annotations/AnnotationPrecedence.mls | 2 +- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 17 files changed, 442 insertions(+), 131 deletions(-) diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 32e5cb70d8..487210b7cc 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -475,6 +475,27 @@ convergence. Depth limits and dropped marks do not establish a fixed point. These representations are internal to resolution. Lowering consumes completed targets and value shapes; runtime values acquire no type-argument objects. +## Async fulfillment interfaces + +`Promise[A]` retains its settlement value type. The intrinsic `Awaited[A]` +projects the eventual fulfillment interface, recursively adopting nested promises. +Both `await` and `.then` observe this projection; constructors may receive a promise +through their resolve callback. Factories and async results expose +`Promise[Awaited[A]]`, while `Promise.all` applies the projection to its element type. + +An async definition's result annotation describes its body before the async wrapper. +Inferred bodies feed a shared deferred type edge, preserving their lexical marks. +The `Awaited` application stays symbolic until the caller supplies its substitution: +expanding it at the definition would lose inferred generic result parameters. +Fulfillment observers are interned before following nested promises, retaining +late bounds and sharing recursive edges. Exposing a promise also exposes callbacks +inside its payload, so their parameter obligations cannot escape checking. + +Structural thenables currently produce an unknown fulfillment interface. Treating +the original object's fields as the fulfilled value would be incorrect because +JavaScript can replace that object with the value passed to its `then` callback. +`Promises.mls` retains an explicit regression for the unsupported callback inference. + ## Validation Graph tests cover bounded instance allocation and replay (`TypeInstantiationTest`), directed @@ -488,5 +509,7 @@ These algebraic checks cover combinations that worksheet examples cannot exhaust Worksheet coverage under `newres` includes `MutableArrays`, `ContextualInference`, `InstantiationSites`, `StoredSpecializations`, `SpecializationCaptures`, `TypeArgumentVariance`, `VarianceSubstitution`, `AnnotationContexts`, and -`TypeGraphTermination`. Deferred cases retain explicit regression expectations; +`TypeGraphTermination`. `Promises` covers async fulfillment, nested adoption, +constructor inference, generic call isolation, and escaping payloads. Deferred cases +retain explicit regression expectations; see the [future-work reference](new-resolution-future-work.md). diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index de6c0b4520..f5644dab0b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -299,6 +299,7 @@ object Elaborator: val BigInt = assumeBuiltinCls("BigInt") val Function = assumeBuiltinCls("Function") lazy val Promise = assumeBuiltinCls("Promise") + lazy val Awaited = assumeBuiltinTpe("Awaited") val Error = assumeBuiltinCls("Error") val Bool = assumeBuiltinCls("Bool") val Object = assumeBuiltinCls("Object") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala index 33a6e25942..dcd47838f6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/InterfaceExposure.scala @@ -182,6 +182,8 @@ final class InterfaceExposure(resolver: NewResolver)(using NewResolverState, TL) watch((callable, marks, "call result"))(resolver.listenCallableResult(callable)): shape => emit(shape.exit(marks), path) case nominal: NominalInstanceView => + watch((shape, "promise payload"))(listener => { resolver.listenPromisePayload(shape)(listener); () }): payload => + emit(payload, path.via(msg"This value is fulfilled by this promise." -> nominal.toLoc)) // Array element bindings may contain escaping closures. Follow only the // declared binding; concrete annotations still hide implementation shapes. resolver.arrayElementType(nominal).foreach: binding => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 6dd8149dbe..f455068b68 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -1008,6 +1008,10 @@ class NewResolver: val local = captureNominalBindings(defn, bindings.filter((symbol, _) => dependencies(symbol))) listenTypeViews(declaredType(parentType, local), mergeIntersections): ext => publish(NominalInstanceView(defn, bindings, S(ext))(S(source), ShapeProvenance.empty)(this)) + case TypeShape.Alias(symbol, _) if symbol is prelude.builtins.Awaited => + val arguments = typeArguments(current, args, symbol.defn.get.tparams) + softAssert(arguments.length == 1) + listenTypeViews(arguments.head)(value => listenAwaitedValue(value, source)(publish)) case TypeShape.Alias(symbol, rhs) => // Revisiting the same alias reference without reaching a structural // shape supplies no additional interface. Track @@ -1117,30 +1121,87 @@ class NewResolver: case S(p: Param) => p.sign case _ => td.resultSignature - private def promiseShape(annotation: Annot.Async): NominalInstanceView = + /** Materialize a deferred value result as a type edge, preserving the value's + * lexical marks until its consumer leaves the definition scope. In particular, + * observing an async result must not mix payloads from separate generic calls. + */ + private def resultType(source: Term, instances: TypeSubstitution)(connect: Listener => Unit) + (using NewResolverState): DeclaredType = + val key = (new Identity(source), instances) + rstate.resultTypes.get(key) match + case S(tpe) => tpe + case N => + val resolution = new TypeResolution(source, messages => resolError(source, messages)) + val tpe = declaredType(resolution, Map.empty) + rstate.resultTypes(key) = tpe + connect(value => resolution.publish(TypeShape.Inferred(value))) + tpe + + private def promiseShape(annotation: Annot.Async, payload: DeclaredType)(using NewResolverState): NominalInstanceView = val cls = prelude.builtins.Promise.defn.get - // The prelude currently exposes an unparameterized Promise interface. Do - // not propagate the body's fields to the wrapper or invent a payload type. - softAssert(cls.tparams.isEmpty && cls.ext.isEmpty, - "The builtin Promise must expose a non-generic interface with the implicit Object parent") + softAssert(cls.tparams.length == 1 && cls.ext.isEmpty, + "The builtin Promise must have one fulfillment parameter and the implicit Object parent") val provenance = ShapeProvenance( msg"This async annotation wraps the body's result in a promise." -> N :: Nil).withTypeOrigin(annotation) - NominalInstanceView(cls, Map.empty, implicitParent(cls))(N, provenance)(this) + NominalInstanceView(cls, Map(cls.tparams.head.sym -> awaitedType(payload)), implicitParent(cls))(N, provenance)(this) + + /** Reify the projection before observing its operand. Expanding an inferred + * body's type parameter here would freeze the declaration's abstract view + * before a caller can substitute its own type argument. + */ + private def awaitedType(tpe: DeclaredType)(using NewResolverState): DeclaredType = + rstate.awaitedTypes.getOrElseUpdate(tpe, { + val symbol = prelude.builtins.Awaited match + case symbol: TypeAliasSymbol => symbol + case _ => lastWords("The builtin Awaited must be an abstract type alias") + val constructor = new TypeResolution(tpe.resolution.source, tpe.resolution.fail) + constructor.publish(TypeShape.Alias(symbol, N)) + val resolution = new TypeResolution(tpe.resolution.source, tpe.resolution.fail) + resolution.publish(TypeShape.Applied(constructor, tpe.resolution :: Nil)) + declaredType(resolution, tpe) + }) + + private def listenAwaitedValue(value: TermShape, source: Term)(listener: Listener)(using NewResolverState): Unit = + listenInstanceViews(value)(view => listenAwaitedView(view, source)(listener)) + + private def listenAwaitedView(value: TermShape, source: Term)(listener: Listener)(using NewResolverState): Unit = + // Intern before following nested promises, so recursive fulfillment edges + // share a host and late candidates still reach the existing consumers. + val key = (value, rstate.instances) + val tpe = rstate.awaitedValues.get(key) match + case S(result) => result + case N => + val resolution = new TypeResolution(source, messages => resolError(source, messages)) + val result = declaredType(resolution, Map.empty) + rstate.awaitedValues(key) = result + def publish(value: TermShape)(using NewResolverState): Unit = resolution.publish(TypeShape.Inferred(value)) + if !listenPromisePayload(value)(payload => listenAwaitedValue(payload, source)(publish)) then + value.applicationHead._1 match + case _: (DynShape | UnknownValueShape | OpaqueTypeShape | RigidTypeShape) => publish(value) + // JavaScript may adopt a structural thenable instead of returning + // the object itself. Until its callback interface can be inferred, + // preserve uncertainty rather than exposing the object's fields. + case _ => value.getMember("then") match + case MemberLookup.Missing => publish(value) + case _ => publish(UnknownValueShape(source, fromPublicInterface = false)(ShapeProvenance( + msg"This value may be a thenable; its fulfillment interface is not known." -> value.toLoc :: Nil))) + result + listenTypeViews(tpe)(listener) /** Result annotations constrain the body before async lowering. Consumers of - * the function observe the wrapper, including when the body always throws. - * Keep this distinction shared by calls and higher-order inference. - * Declared types use the supplied instances, while inferred values stay at - * their lexical endpoint: callers must exit the definition's scope before - * substituting them, so candidates from distinct calls remain separate. + * the function observe Promise[Awaited[result]], including explicit returns. + * Inferred values retain their lexical endpoint until callers leave the scope. */ private def listenDefinitionResult(td: TermDefinition, instances: TypeSubstitution)(listener: Listener)(using NewResolverState): Unit = + def bodyResult(listener: Listener): Unit = td.sign match + case S(sign) => listenSignatureResult(declaredType(typeResolution(sign), Map.empty).instantiate(instances), + if td.flags.hasResultAnnotation then 0 else td.params.length)(listener) + case N => td.body.foreach(body => listenResult(body)(listener)) td.asyncAnnotation match - case S(annotation) => listener(promiseShape(annotation)) - case N => td.sign match - case S(sign) => listenSignatureResult(declaredType(typeResolution(sign), Map.empty).instantiate(instances), - if td.flags.hasResultAnnotation then 0 else td.params.length)(listener) - case N => td.body.foreach(body => listenResult(body)(listener)) + case S(annotation) => + val source = td.sign.orElse(td.body).get + listener(promiseShape(annotation, resultType(source, instances)(bodyResult))) + case N => bodyResult(listener) /** Observe one application of a declared callable. Partial application keeps * the async wrapper pending, even across the nested arrows of a full signature. @@ -1149,7 +1210,9 @@ class NewResolver: callable.asyncResult match case S(AsyncResult(annotation, 1)) => softAssert(callable.paramLists.tail.isEmpty) - listener(promiseShape(annotation)) + // A nominal member without a result annotation still returns Promise, + // but its interface must not recover a payload type from the hidden body. + listener(promiseShape(annotation, callable.result.getOrElse(extremeType(true)))) case pending => val remaining = pending.map(info => info.copy(remainingLists = info.remainingLists - 1)) callable.paramLists.tail match @@ -1258,7 +1321,8 @@ class NewResolver: params.ne_map(ps => DeclaredParams(ps.params.map(_.sign.map(signature(_, !positive))), ps.restParam.nonEmpty, ps.restParam.flatMap(_.sign).map(signature(_, !positive)))), result, N, N, S(td), N)) - case Nil if td.asyncAnnotation.nonEmpty => publish(promiseShape(td.asyncAnnotation.get)) + case Nil if td.asyncAnnotation.nonEmpty => + publish(promiseShape(td.asyncAnnotation.get, result.getOrElse(extremeType(true)))) case Nil => result match case S(tpe) => listenTypeInstances(tpe)(publish) case N => @@ -1868,27 +1932,35 @@ class NewResolver: * Arrays are mutable, and the one-number constructor creates empty slots. */ private[semantics] def listenArrayElements(shape: TermShape)(listener: Listener)(using NewResolverState): Bool = - val cls = prelude.builtins.Array.defn.get - softAssert(cls.tparams.length == 1, "The builtin Array must have one element type parameter") + shape.applicationHead._1 match + case tuple: TupleShape => + listenArrayElements(tuple.arrayParent)(element => + transportShape(element, shape.applicationHead._2) match + case value: TermShape => listener(value) + case NoShape => ()) + case _ => listenClassArgument(shape, prelude.builtins.Array.defn.get)(listener) + + private[semantics] def listenPromisePayload(shape: TermShape)(listener: Listener)(using NewResolverState): Bool = + listenClassArgument(shape, prelude.builtins.Promise.defn.get)(listener) + + /** Read the sole type argument of a nominal instance or an inferred construction. + * Marks leave the allocation and enclosing scopes before the payload is observed. + */ + private def listenClassArgument(shape: TermShape, cls: ClassLikeDef)(listener: Listener)(using NewResolverState): Bool = + softAssert(cls.tparams.length == 1, "The observed class must have one type parameter") val param = cls.tparams.head.sym val ShapeParts(source, instances, _) = shapeParts(shape) - val Marked(value, _) = source - // Supplied and inferred element types share the allocation's parameter. - // Outer marks transport its bounds through callers without changing which - // parameter instance the allocation selected. - val head = value.applicationHead._1 + val head = source.applicationHead._1 val context = source.applicationHead._2 def receive(element: TermShape)(using NewResolverState): Unit = transportShape(element, context) match case value: TermShape => listener(value) case NoShape => () head match - case tuple: TupleShape => - listenArrayElements(tuple.arrayParent)(receive) case nominal: NominalInstanceView => nominal.ancestor(cls) match - case S(array) => - array.bindings.get(param) match + case S(instance) => + instance.bindings.get(param) match case S(bound) => - listenTypeInstances(bound)(receive) + listenTypeInstances(bound.instantiate(instances))(receive) true case N => false case N => false @@ -2975,7 +3047,7 @@ class NewResolver: rstate.byNameResults(key) = host listenValueHost(host)(listener) td.asyncAnnotation match - case S(annotation) => publishActivated(host, promiseShape(annotation)) + case S(_) => listenDefinitionResult(td, rstate.instances)(publishActivated(host, _)) case N => listenResult(body)(publishActivated(host, _)) def pipeTerm(from: Term, to: ShapeHost)(using NewResolverState): Unit = @@ -3127,7 +3199,7 @@ class NewResolver: else callerInstances.without(td.tparams.toList.flatten.map(_.sym)) def receive(shape: TermShape)(using NewResolverState): Unit = wrappedListener(instantiateShape(shape, instances))(using rstate.withInstances(callerInstances)) - if td.asyncAnnotation.nonEmpty then receive(promiseShape(td.asyncAnnotation.get)) + if td.asyncAnnotation.nonEmpty then listenDefinitionResult(td, instances)(receive) else resultSignature(td) match case S(sign) => val result = declaredType(typeResolution(sign), Map.empty).instantiate(instances) @@ -3638,7 +3710,8 @@ class NewResolver: val flow = resultFlow(body) if flow.normal then listen(body, discardMarks)(listener) flow.exits.getOrElse(S(label), Vector.empty).foreach(value => listen(value, discardMarks)(listener)) - case Annotated(annotation @ Annot.Async(), _) => listener(promiseShape(annotation)) + case Annotated(annotation @ Annot.Async(), body) => + listener(promiseShape(annotation, resultType(body, rstate.instances)(listenResult(body)))) case Annotated(_, target) => listen(target, discardMarks)(listener) case _: Assgn | _: Drop => listener(unitResultShape) case _: Ret | _: Break | _: Throw | _: Continue => () // These expressions do not complete normally. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 64c3116e61..4b18abebd0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -270,6 +270,14 @@ final class NewResolverState private ( // identity so imported listeners use the consumer's private publisher data. val byNameResults: Cache[(TermSymbol, TypeSubstitution), ShapeHost] = new Cache(inherited.map(_.byNameResults), identity) + // Async payloads are deferred type edges, not snapshots of the body's current + // candidates. Register each host before following recursive function results. + val resultTypes: Cache[(Identity[Term], TypeSubstitution), DeclaredType] = + new Cache(inherited.map(_.resultTypes), identity) + val awaitedTypes: Cache[DeclaredType, DeclaredType] = + new Cache(inherited.map(_.awaitedTypes), identity) + val awaitedValues: Cache[(TermShape, TypeSubstitution), DeclaredType] = + new Cache(inherited.map(_.awaitedValues), identity) val typeInterpretations: Cache[Identity[Term], TypeResolution] = new Cache(inherited.map(_.typeInterpretations), identity) // Qualification can deliver the same declaration in several environments. diff --git a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls index f07782b733..57cdae65a8 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls @@ -31,7 +31,7 @@ xs.reverse() :re xs.map(x => x * 2) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in MiscArrayTests.mls:32 diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 3b9665641a..46c709b822 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -176,13 +176,15 @@ declare module BigInt64Array declare class BigUint64Array declare module BigUint64Array -declare class Promise(executor: (Function, Function) -> Any) { - fun id"then"(onFulfilled: Function, ...onRejected): Promise +// Awaited recursively unwraps promises, as JavaScript's promise resolution does. +declare type Awaited[out A] +declare class Promise[out A](executor: (A -> Any, Any -> Any) -> Any) { + fun id"then"[B](onFulfilled: Awaited[A] -> B, ...onRejected: Array[Any -> B]): Promise[Awaited[B]] } declare module Promise { - fun resolve(value): Promise - fun reject(reason): Promise - fun all(values): Promise + fun resolve[A](value: A): Promise[Awaited[A]] + fun reject(reason: Any): Promise[Nothing] + fun all[A](values: Array[A]): Promise[Array[Awaited[A]]] } // MLscript-specific types @@ -560,7 +562,7 @@ declare module annotations { } declare module handlers { - fun await(promise: Promise): Any + fun await[A](promise: Promise[A]): Awaited[A] } declare module scope { diff --git a/hkmc2/shared/src/test/mlscript/handlers/Async.mls b/hkmc2/shared/src/test/mlscript/handlers/Async.mls index 97c8599306..e72ac64b74 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Async.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Async.mls @@ -4,13 +4,20 @@ import "fs" +// Describe the foreign API at the import boundary; await preserves these payloads. +:silent +let fileSystem = fs as {promises: { + readFile: Str -> Promise[{toString: () -> Str}], + readdir: Str -> Promise[Array[Str]] +}} + open annotations open handlers @async fun f(print_result: Bool) = print("Hello, world!") - let r = (await(fs.promises.readFile("./README.md")) as dyn) + let r = await(fileSystem.promises.readFile("./README.md")) let readme = r.toString() let first = readme.slice(0, readme.indexOf("\n")) if print_result do @@ -42,7 +49,7 @@ f() fun f() = await(Promise.resolve(1)) //│ ╔══[COMPILATION ERROR] Only await inside of async bodies are allowed if effect handlers are not enabled. -//│ ║ l.43: await(Promise.resolve(1)) +//│ ║ l.50: await(Promise.resolve(1)) //│ ╙── ^^^^^ :re @@ -76,11 +83,11 @@ f() fun f() = print(0) - let r = (await(fs.promises.readFile("./README.md")) as dyn) + let r = await(fileSystem.promises.readFile("./README.md")) let readme = r.toString() let first = readme.slice(0, readme.indexOf("\n")) print(first) - let r2 = (await(fs.promises.readdir(".")) as dyn) + let r2 = await(fileSystem.promises.readdir(".")) print(r2.indexOf("README.md") >= 0) @async @@ -110,13 +117,13 @@ fun g() = ) await(work1) print("f1 done") - work2resolve() + work2resolve(()) @async fun f2() = print("f2 started") let work2 = new Promise((resolve, reject) => set work2resolve = resolve - work1resolve() + work1resolve(()) ) await(work2) print("f2 done") diff --git a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls index c423692e2e..ca8c1be7f1 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls @@ -11,7 +11,7 @@ let arr = [true, false] :re arr.map(_ === false) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:12 @@ -53,7 +53,7 @@ arr.map((..., e) => e) //│ ║ l.51: arr.map((..., e) => e) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:51 @@ -86,15 +86,15 @@ Array() //│ ║ ^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.230: declare class Array[T](val length: Int) { +//│ ║ l.232: declare class Array[T](val length: Int) { //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.231: fun join(...separators): Str +//│ ║ l.233: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.232: fun reverse(): Array[T] +//│ ║ l.234: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.233: fun sort(...compare): Array[T] +//│ ║ l.235: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.234: ... (more lines omitted) ... +//│ ║ l.236: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -105,15 +105,15 @@ Array(1, 2, 3) //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 3 //│ ║ in ../decls/Prelude.mls -//│ ║ l.230: declare class Array[T](val length: Int) { +//│ ║ l.232: declare class Array[T](val length: Int) { //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.231: fun join(...separators): Str +//│ ║ l.233: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.232: fun reverse(): Array[T] +//│ ║ l.234: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.233: fun sort(...compare): Array[T] +//│ ║ l.235: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.234: ... (more lines omitted) ... +//│ ║ l.236: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2, 3] diff --git a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls index 1bf49d5689..b8103df79e 100644 --- a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls +++ b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls @@ -25,7 +25,7 @@ js.oops //│ ║ ^^^^^^^ //│ ╟── Module 'js' does not contain member 'oops' //│ ║ in ../decls/Prelude.mls -//│ ║ l.427: declare module js { +//│ ║ l.429: declare module js { //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index f634f64a29..2e94c8dc69 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -141,15 +141,15 @@ new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.230: declare class Array[T](val length: Int) { +//│ ║ l.232: declare class Array[T](val length: Int) { //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.231: fun join(...separators): Str +//│ ║ l.233: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.232: fun reverse(): Array[T] +//│ ║ l.234: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.233: fun sort(...compare): Array[T] +//│ ║ l.235: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.234: ... (more lines omitted) ... +//│ ║ l.236: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2] @@ -160,15 +160,15 @@ new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.230: declare class Array[T](val length: Int) { +//│ ║ l.232: declare class Array[T](val length: Int) { //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.231: fun join(...separators): Str +//│ ║ l.233: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.232: fun reverse(): Array[T] +//│ ║ l.234: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.233: fun sort(...compare): Array[T] +//│ ║ l.235: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.234: ... (more lines omitted) ... +//│ ║ l.236: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.264: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.266: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 95d131e5ea..4a6a594c15 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.250: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls index 5ec290b830..06777b02a5 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls @@ -434,7 +434,7 @@ let changingParent = makeParent(4) class SavedParent extends changingParent { override fun f() = super.f() } set changingParent = makeParent(9) (new SavedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2874' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2946' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 4 @@ -449,7 +449,7 @@ let changingHolder = Holder(5) class SavedNestedParent extends changingHolder.Parent { override fun f() = super.f() } set changingHolder = Holder(11) (new SavedNestedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2874' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2946' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 5 diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls index 67abc25ce7..3b24d11be7 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls @@ -99,7 +99,7 @@ pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Int' does not contain member 'toUpperCase' //│ ║ in ../decls/Prelude.mls -//│ ║ l.195: val length: Int +//│ ║ l.197: val length: Int //│ ╙── ^^^ // Conjunction produces a pair, not the string tested by its left operand. diff --git a/hkmc2/shared/src/test/mlscript/newres/Promises.mls b/hkmc2/shared/src/test/mlscript/newres/Promises.mls index 63d87101b1..b3c3b8182d 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Promises.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Promises.mls @@ -1,20 +1,20 @@ :. // The prelude's declared Promise interface supports factory calls and chaining. -// Its callback type is Function; inputs inspected by callbacks need explicit types. +// Callback parameters and await results inherit the promise's payload type. :await :expect 43 -Promise.resolve(42).then((value: dyn) => value + 1) +Promise.resolve(42).then(value => value + 1) //│ = 43 :await :expect "recovered: failed" -Promise.reject("failed").then((value: dyn) => value, (reason: dyn) => "recovered: " + reason) +Promise.reject("failed").then(value => value, reason => "recovered: " + reason) //│ = "recovered: failed" :await :expect 42 -Promise.all([Promise.resolve(20), Promise.resolve(22)]).then((values: dyn) => values.at(0) + values.at(1)) +Promise.all([Promise.resolve(20), Promise.resolve(22)]).then(values => values.at(0) + values.at(1)) //│ = 42 // Async expressions expose the same Promise interface as declared factories. @@ -26,7 +26,7 @@ Promise.all([Promise.resolve(20), Promise.resolve(22)]).then((values: dyn) => va :await :expect 42 let asyncResult = @annotations.async {answer: 42} -asyncResult.then((value: dyn) => value.answer) +asyncResult.then(value => value.answer) //│ = 42 //│ asyncResult = Promise {} @@ -57,8 +57,7 @@ asyncFunction().answer //│ ╙── This async annotation wraps the body's result in a promise. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -// FIXME: Await does not yet propagate the fulfilled value's interface. -:fixme +// Await propagates the fulfilled value's interface. :await :expect 42 @annotations.async @@ -66,42 +65,30 @@ fun awaitAnswer() = let value = handlers.await(Promise.resolve({answer: 42})) value.answer awaitAnswer() -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.67: value.answer -//│ ║ ^^^^^^^^^^^^ -//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. -//│ ║ in ../decls/Prelude.mls -//│ ║ l.562: fun await(promise: Promise): Any -//│ ║ ^^^ -//│ ╟── Type 'Any' is declared without a member interface. -//│ ║ l.2: declare type Any -//│ ╙── ^^^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '42', got: 'Promise {}' -//│ = Promise {} +//│ = 42 // Both inferred and annotated bodies expose Promise at the call boundary. :await :expect 42 @annotations.async fun inferred() = {answer: 42} -inferred().then((value: {answer: Int}) => value.answer) +inferred().then(value => value.answer) //│ = 42 :await :expect 43 @annotations.async fun typed(value: Int): {answer: Int} = {answer: value} -typed(43).then((value: {answer: Int}) => value.answer) +typed(43).then(value => value.answer) //│ = 43 :e typed(43).answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.99: typed(43).answer +//│ ║ l.86: typed(43).answer //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Promise' does not contain member 'answer' -//│ ║ l.93: @annotations.async +//│ ║ l.80: @annotations.async //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╙── This async annotation wraps the body's result in a promise. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -111,7 +98,7 @@ typed(43).answer :expect 44 @annotations.async fun explicitReturn() = return {answer: 44} -explicitReturn().then((value: {answer: Int}) => value.answer) +explicitReturn().then(value => value.answer) //│ = 44 :e @@ -119,10 +106,10 @@ explicitReturn().then((value: {answer: Int}) => value.answer) fun returnedClosure() = () => 42 returnedClosure()() //│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.120: returnedClosure()() +//│ ║ l.107: returnedClosure()() //│ ║ ^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Promise' cannot be called like a function. -//│ ║ l.118: @annotations.async +//│ ║ l.105: @annotations.async //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╙── This async annotation wraps the body's result in a promise. //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -132,7 +119,7 @@ returnedClosure()() :expect 45 @annotations.async fun asyncGetter: {answer: Int} = {answer: 45} -asyncGetter.then((value: {answer: Int}) => value.answer) +asyncGetter.then(value => value.answer) //│ = 45 :await @@ -140,7 +127,7 @@ asyncGetter.then((value: {answer: Int}) => value.answer) class AsyncMethods with @annotations.async fun fetch(value: Int): {answer: Int} = {answer: value} -(new AsyncMethods).fetch(46).then((value: {answer: Int}) => value.answer) +(new AsyncMethods).fetch(46).then(value => value.answer) //│ = 46 // Partial application is still a function; only the last call returns Promise. @@ -149,19 +136,19 @@ class AsyncMethods with @annotations.async fun curried(x: Int)(y: Int): Int = x + y let remaining = curried(20) -remaining(27).then((value: Int) => value) +remaining(27).then(value => value) //│ = 47 //│ remaining = fun :e -curried(20).then((value: Int) => value) +curried(20).then(value => value) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.157: curried(20).then((value: Int) => value) +//│ ║ l.144: curried(20).then(value => value) //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Function 'curried' does not contain member 'then' -//│ ║ l.149: @annotations.async +//│ ║ l.136: @annotations.async //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.150: fun curried(x: Int)(y: Int): Int = x + y +//│ ║ l.137: fun curried(x: Int)(y: Int): Int = x + y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -169,12 +156,12 @@ curried(20).then((value: Int) => value) :await :expect 42 fun invoke[T](callback: () -> T): T = callback() -invoke(inferred).then((value: {answer: Int}) => value.answer) +invoke(inferred).then(value => value.answer) //│ = 42 :await :expect 48 -invoke(() => typed(48)).then((value: {answer: Int}) => value.answer) +invoke(() => typed(48)).then(value => value.answer) //│ = 48 // Throwing bodies still have the async calling interface, even without returns. @@ -189,7 +176,7 @@ fun shadowed() = class Promise @annotations.async fun result() = 49 - result().then((value: Int) => value) + result().then(value => value) shadowed() //│ = 49 @@ -200,17 +187,17 @@ fun separatelyTyped: Int -> Int -> Int @annotations.async fun separatelyTyped(x)(y) = x + y let typedRemaining = separatelyTyped(20) -typedRemaining(30).then((value: Int) => value) +typedRemaining(30).then(value => value) //│ = 50 //│ typedRemaining = fun :e -separatelyTyped(20).then((value: Int) => value) +separatelyTyped(20).then(value => value) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.208: separatelyTyped(20).then((value: Int) => value) +//│ ║ l.195: separatelyTyped(20).then(value => value) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function with a declared signature does not contain member 'then' -//│ ║ l.199: fun separatelyTyped: Int -> Int -> Int +//│ ║ l.186: fun separatelyTyped: Int -> Int -> Int //│ ╙── ^^^^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -219,14 +206,14 @@ separatelyTyped(20).then((value: Int) => value) :expect 51 @annotations.async fun generic[T](value: T): T = value -generic[Int](51).then((value: Int) => value) +generic[Int](51).then(value => value) //│ = 51 // Typed higher-order inference must also see the wrapped result. :await :expect 52 fun invokeWith[T](callback: Int -> T): T = callback(52) -invokeWith(typed).then((value: {answer: Int}) => value.answer) +invokeWith(typed).then(value => value.answer) //│ = 52 // Imported interfaces retain async annotations through cloning and completion. @@ -234,35 +221,35 @@ import "../../mlscript-compile/AsyncLibrary.mls" :await :expect 53 -AsyncLibrary.inferred().then((value: {answer: Int}) => value.answer) +AsyncLibrary.inferred().then(value => value.answer) //│ = 53 :await :expect 54 -AsyncLibrary.typed(54).then((value: {answer: Int}) => value.answer) +AsyncLibrary.typed(54).then(value => value.answer) //│ = 54 :await :expect 55 -AsyncLibrary.curried(20)(35).then((value: Int) => value) +AsyncLibrary.curried(20)(35).then(value => value) //│ = 55 :await :expect 56 -AsyncLibrary.getter.then((value: Int) => value) +AsyncLibrary.getter.then(value => value) //│ = 56 :await :expect 57 fun nominalMethod(receiver: AsyncLibrary.Methods) = - receiver.result(57).then((value: Int) => value) + receiver.result(57).then(value => value) nominalMethod(new AsyncLibrary.Methods) //│ = 57 :e AsyncLibrary.inferred().answer //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.263: AsyncLibrary.inferred().answer +//│ ║ l.250: AsyncLibrary.inferred().answer //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Value of type 'Promise' does not contain member 'answer' //│ ║ in ../../mlscript-compile/AsyncLibrary.mls @@ -275,7 +262,7 @@ AsyncLibrary.inferred().answer :await :expect 58 fun finish[T](callback: Int -> T): T = callback(38) -finish(separatelyTyped(20)).then((value: Int) => value) +finish(separatelyTyped(20)).then(value => value) //│ = 58 // An arrow in the payload type is not an extra parameter list of the definition. @@ -284,7 +271,7 @@ finish(separatelyTyped(20)).then((value: Int) => value) fun signedClosure: () -> () -> Int @annotations.async fun signedClosure() = () => 59 -signedClosure().then((callback: () -> Int) => callback()) +signedClosure().then(callback => callback()) //│ = 59 // An ineffective annotation on a value must not change its interface. @@ -294,7 +281,215 @@ signedClosure().then((callback: () -> Int) => callback()) val plainValue = {answer: 60} plainValue.answer //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.293: @annotations.async +//│ ║ l.280: @annotations.async //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ = 60 //│ plainValue = {answer: 60} + +// Awaiting an inferred async result retains its record interface. +:await +:expect 61 +@annotations.async +fun awaitInferred() = handlers.await(inferred()).answer + 19 +awaitInferred() +//│ = 61 + +// Both async bodies and Promise.resolve adopt an already promised result. +:await +:expect 62 +@annotations.async +fun nestedAsync() = Promise.resolve(Promise.resolve({answer: 62})) +nestedAsync().then(value => value.answer) +//│ = 62 + +// Chaining flattens promised callback results before the next callback or await. +:await +:expect 63 +@annotations.async +fun awaitChain() = + let value = handlers.await(Promise.resolve({answer: 63}).then(value => @annotations.async value)) + value.answer +awaitChain() +//│ = 63 + +// Promise.all accepts plain values and promises and exposes fulfilled elements. +:await +:expect 64 +class PromiseAnswer(val answer: Int) +Promise.all([PromiseAnswer(32), Promise.resolve(PromiseAnswer(32))]).then(values => + values.[0].answer + values.[1].answer) +//│ = 64 + +// The constructor infers its payload from calls to its resolve callback. +:await +:expect 65 +(new Promise((resolve, reject) => resolve({answer: 65}))).then(value => value.answer) +//│ = 65 + +// Generic async calls retain independent payloads, including nested fields. +:await +:expect "independent" +@annotations.async +fun asyncIdentity[T](value: T): T = value +@annotations.async +fun independentPayloads() = + let first = handlers.await(asyncIdentity({first: {answer: 66}})) + let second = handlers.await(asyncIdentity({second: "independent"})) + first.first.answer + second.second +independentPayloads() +//│ = "independent" + +// Inferred bodies must also keep call-site payloads apart. +:await +:expect "inferred" +@annotations.async +fun inferredIdentity(value) = value +@annotations.async +fun independentInferredPayloads() = + let first = handlers.await(inferredIdentity({first: {answer: 67}})) + let second = handlers.await(inferredIdentity({second: "inferred"})) + first.first.answer + second.second +independentInferredPayloads() +//│ = "inferred" + +// Promise-returning generic async calls flatten their payload per instantiation. +:await +:expect 70 +asyncIdentity(Promise.resolve({answer: 70})).then(value => value.answer) +//│ = 70 + +// Promise constructor resolution also adopts promised values. +:await +:expect 71 +(new Promise((resolve, reject) => resolve(Promise.resolve({answer: 71})))).then(value => value.answer) +//│ = 71 + +:await +:expect 72 +@annotations.async +fun awaitConstructed() = + let value = handlers.await(new Promise((resolve, reject) => resolve(Promise.resolve({answer: 72})))) + value.answer +awaitConstructed() +//│ = 72 + +// Recursive async calls share a deferred fulfillment interface. +:await +:expect 73 +@annotations.async +fun recursiveAsync(n: Int) = if n == 0 then {answer: 73} else recursiveAsync(n - 1) +recursiveAsync(3).then(value => value.answer) +//│ = 73 + +// Inferred generic bodies preserve their substitution without a result annotation. +:await +:expect "generic body" +@annotations.async +fun inferredGeneric[T](value: T) = value +@annotations.async +fun awaitGenericBody() = + let first = handlers.await(inferredGeneric({first: 74})) + let second = handlers.await(inferredGeneric({second: "generic body"})) + first.first + second.second +awaitGenericBody() +//│ = "generic body" + +// A resolve call discovered after a consumer still supplies that consumer's type. +:await +:expect 75 +let settleLater +let pending = new Promise((resolve, reject) => set settleLater = resolve) +let settled = pending.then(value => value.answer) +settleLater({answer: 75}) +settled +//│ = 75 +//│ pending = Promise {} +//│ settleLater = fun +//│ settled = Promise {} + +// Type arguments are retained through an explicitly declared promise boundary. +:await +:expect 76 +fun declaredPromise(): Promise[{answer: Int}] = Promise.resolve({answer: 76}) +declaredPromise().then(value => value.answer) +//│ = 76 + +// Promise payload annotations hide implementation-only fields. +:!js +:e +@annotations.async +fun hidesField(): {answer: Int} = {answer: 68, hidden: 0} +hidesField().then(value => value.hidden) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.425: hidesField().then(value => value.hidden) +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Record type does not contain member 'hidden' +//│ ║ l.424: fun hidesField(): {answer: Int} = {answer: 68, hidden: 0} +//│ ╙── ^^^^^^^^^^^ + +// Awaiting a Promise does not make an absent payload member dynamic. +:!js +:e +@annotations.async +fun missingPayloadField() = + let value = handlers.await(Promise.resolve({answer: 69})) + value.missing +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.439: value.missing +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Record literal does not contain member 'missing' +//│ ║ l.438: let value = handlers.await(Promise.resolve({answer: 69})) +//│ ╙── ^^^^^^^^^^ + + +// Promise payloads that escape must expose their callbacks' parameter obligations. +:!js +:e +val escapedCallback = @annotations.async {value => value.answer} +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.451: val escapedCallback = @annotations.async {value => value.answer} +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── Parameter 'value' admits values of unknown shape. +//│ ║ l.451: val escapedCallback = @annotations.async {value => value.answer} +//│ ║ ^^^^^ +//│ ╟── This value is fulfilled by this promise. +//│ ║ in ../decls/Prelude.mls +//│ ║ l.180: declare class Promise[out A](executor: (A -> Any, Any -> Any) -> Any) { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.181: fun id"then"[B](onFulfilled: Awaited[A] -> B, ...onRejected: Array[Any -> B]): Promise[Awaited[B]] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.182: } +//│ ║ ^ +//│ ╟── 'escapedCallback' is exposed by this compilation unit. +//│ ║ in Promises.mls:451 +//│ ║ l.451: val escapedCallback = @annotations.async {value => value.answer} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// A promise with no finite fulfillment has no payload member interface. +:!js +:e +@annotations.async +fun noFulfillment() = noFulfillment() +noFulfillment().then(value => value.answer) +//│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target +//│ ║ l.477: noFulfillment().then(value => value.answer) +//│ ╙── ^^^^^^^^^^^^ + +// TODO: Infer structural thenables' fulfillment callbacks. Their original fields +// must not be exposed as the payload while JavaScript adopts the resolved value. +:!js +:fixme +class Thenable with + fun id"then"(resolve: {answer: Int} -> Any, reject: Any -> Any) = resolve({answer: 77}) +Promise.resolve(new Thenable).then(value => value.answer) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.488: Promise.resolve(new Thenable).then(value => value.answer) +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Cannot resolve member 'answer' of a value with unknown shape. +//│ ╟── This value may be a thenable; its fulfillment interface is not known. +//│ ║ l.488: Promise.resolve(new Thenable).then(value => value.answer) +//│ ╙── ^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index 17d50e5b27..5509a51977 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -40,7 +40,7 @@ fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Object 'compile' cannot be called like a function. //│ ║ in ../../decls/Prelude.mls -//│ ║ l.554: object compile +//│ ║ l.556: object compile //│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: y //│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index 9be1681755..f2d43aab08 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.438: fun +//│ ║ l.440: fun //│ ║ ^^^ -//│ ║ l.439: const(c: Int): Int32 +//│ ║ l.441: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From a8d30d896c4267e91b0757c487d0b10f908e8f33 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 23:20:13 +0800 Subject: [PATCH 221/228] Align Promise signatures with TypeScript and use undefined for void callbacks --- doc/new-resolution-type-value-flow.md | 14 ++ .../scala/hkmc2/semantics/Elaborator.scala | 1 + .../scala/hkmc2/semantics/NewResolver.scala | 3 + .../test/mlscript/basics/MiscArrayTests.mls | 2 +- .../src/test/mlscript/decls/Prelude.mls | 14 +- .../src/test/mlscript/handlers/Async.mls | 8 +- .../src/test/mlscript/interop/Arrays.mls | 24 +-- .../test/mlscript/meta/CompilerFiction.mls | 2 +- .../src/test/mlscript/newres/Arrays.mls | 24 +-- .../test/mlscript/newres/GenericMethods.mls | 2 +- .../src/test/mlscript/newres/Overrides.mls | 4 +- .../newres/PatternDefinitionBindings.mls | 2 +- .../mlscript/newres/PromiseSignatures.mls | 149 ++++++++++++++++++ .../src/test/mlscript/newres/Promises.mls | 22 +-- .../annotations/AnnotationPrecedence.mls | 2 +- .../test/mlscript/wasm/BuiltinOperators.mls | 4 +- 16 files changed, 228 insertions(+), 49 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/PromiseSignatures.mls diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index 487210b7cc..a9ba370702 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -483,6 +483,20 @@ Both `await` and `.then` observe this projection; constructors may receive a pro through their resolve callback. Factories and async results expose `Promise[Awaited[A]]`, while `Promise.all` applies the projection to its element type. +The Promise declarations follow TypeScript's library where the current language +can express the signatures. Executor, resolve, reject, and finalizer callbacks use +`undefined` for TypeScript's `void`; only rejection reasons are unrestricted `Any`. +`then` keeps separate fulfillment and rejection result parameters, `catch` includes +the original fulfillment type, and `finally` preserves it. `Nothing` is the empty +fulfillment type of a rejected promise, including through type aliases. + +Optional first callbacks, zero-argument factories, and default type arguments still +require language support. The rejection callback of `then` uses the existing rest +parameter convention; it, `catch`, and `finally` accept explicit `undefined` or +`null`. Promise inputs currently support native promises; structural +`PromiseLike` values, general JS +iterables, and tuple-mapped `all` results remain outside these signatures. + An async definition's result annotation describes its body before the async wrapper. Inferred bodies feed a shared deferred type edge, preserving their lexical marks. The `Awaited` application stays symbolic until the caller supplies its substitution: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f5644dab0b..2fe35681b0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -300,6 +300,7 @@ object Elaborator: val Function = assumeBuiltinCls("Function") lazy val Promise = assumeBuiltinCls("Promise") lazy val Awaited = assumeBuiltinTpe("Awaited") + lazy val Nothing = assumeBuiltinTpe("Nothing") val Error = assumeBuiltinCls("Error") val Bool = assumeBuiltinCls("Bool") val Object = assumeBuiltinCls("Object") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index f455068b68..3f5c1404fb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -76,6 +76,9 @@ class NewResolver: case _ => () def definition(defn: Definition)(using NewResolverState): Unit = defn match case cls: ClassLikeDef => publish(TypeShape.Nominal(cls)) + // Nothing admits no values. In particular, a rejected promise must + // not contribute an unknown fulfillment alternative to catch or any. + case alias: TypeDef if alias.sym is prelude.builtins.Nothing => publish(TypeShape.Bottom) case alias: TypeDef => publish(TypeShape.Alias(alias.sym, alias.rhs.map(typeResolution))) // Validate unused annotations too, once their forward source graph diff --git a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls index 57cdae65a8..e4c6aaded0 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls @@ -31,7 +31,7 @@ xs.reverse() :re xs.map(x => x * 2) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.260: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in MiscArrayTests.mls:32 diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 46c709b822..2e546f1e7f 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -178,13 +178,21 @@ declare module BigUint64Array // Awaited recursively unwraps promises, as JavaScript's promise resolution does. declare type Awaited[out A] -declare class Promise[out A](executor: (A -> Any, Any -> Any) -> Any) { - fun id"then"[B](onFulfilled: Awaited[A] -> B, ...onRejected: Array[Any -> B]): Promise[Awaited[B]] +// Follow TypeScript's Promise declarations, translating void returns to undefined. +// Optional parameters and default type arguments are not supported yet: callers +// must supply the first callback, and then's rejection callback uses a rest list. +// Structural PromiseLike values and tuple-mapped all results remain unsupported. +declare class Promise[out A](executor: ((A | Promise[A]) -> undefined, Any -> undefined) -> undefined) { + fun id"then"[B, C](onFulfilled: Awaited[A] -> (B | Promise[B]), ...onRejected: Array[(Any -> (C | Promise[C])) | undefined | null]): Promise[Awaited[B | C]] + fun id"catch"[B](onRejected: (Any -> (B | Promise[B])) | undefined | null): Promise[Awaited[A | B]] + fun id"finally"(onFinally: (() -> undefined) | undefined | null): Promise[A] } declare module Promise { - fun resolve[A](value: A): Promise[Awaited[A]] + fun resolve[A](value: A | Promise[A]): Promise[Awaited[A]] fun reject(reason: Any): Promise[Nothing] fun all[A](values: Array[A]): Promise[Array[Awaited[A]]] + fun race[A](values: Array[A]): Promise[Awaited[A]] + fun any[A](values: Array[A]): Promise[Awaited[A]] } // MLscript-specific types diff --git a/hkmc2/shared/src/test/mlscript/handlers/Async.mls b/hkmc2/shared/src/test/mlscript/handlers/Async.mls index e72ac64b74..a7c4f348f4 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Async.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Async.mls @@ -112,18 +112,18 @@ fun g() = @async fun f1() = print("f1 started") - let work1 = new Promise((resolve, reject) => + let work1 = new Promise[undefined]((resolve, reject) => set work1resolve = resolve ) await(work1) print("f1 done") - work2resolve(()) + work2resolve(undefined) @async fun f2() = print("f2 started") - let work2 = new Promise((resolve, reject) => + let work2 = new Promise[undefined]((resolve, reject) => set work2resolve = resolve - work1resolve(()) + work1resolve(undefined) ) await(work2) print("f2 done") diff --git a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls index ca8c1be7f1..b6e512bdb9 100644 --- a/hkmc2/shared/src/test/mlscript/interop/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/interop/Arrays.mls @@ -11,7 +11,7 @@ let arr = [true, false] :re arr.map(_ === false) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.260: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:12 @@ -53,7 +53,7 @@ arr.map((..., e) => e) //│ ║ l.51: arr.map((..., e) => e) //│ ╙── ^^^ //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.260: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:51 @@ -86,15 +86,15 @@ Array() //│ ║ ^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.232: declare class Array[T](val length: Int) { +//│ ║ l.240: declare class Array[T](val length: Int) { //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.233: fun join(...separators): Str +//│ ║ l.241: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.234: fun reverse(): Array[T] +//│ ║ l.242: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.235: fun sort(...compare): Array[T] +//│ ║ l.243: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.236: ... (more lines omitted) ... +//│ ║ l.244: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -105,15 +105,15 @@ Array(1, 2, 3) //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 3 //│ ║ in ../decls/Prelude.mls -//│ ║ l.232: declare class Array[T](val length: Int) { +//│ ║ l.240: declare class Array[T](val length: Int) { //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.233: fun join(...separators): Str +//│ ║ l.241: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.234: fun reverse(): Array[T] +//│ ║ l.242: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.235: fun sort(...compare): Array[T] +//│ ║ l.243: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.236: ... (more lines omitted) ... +//│ ║ l.244: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2, 3] diff --git a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls index b8103df79e..b6ba06eccb 100644 --- a/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls +++ b/hkmc2/shared/src/test/mlscript/meta/CompilerFiction.mls @@ -25,7 +25,7 @@ js.oops //│ ║ ^^^^^^^ //│ ╟── Module 'js' does not contain member 'oops' //│ ║ in ../decls/Prelude.mls -//│ ║ l.429: declare module js { +//│ ║ l.437: declare module js { //│ ╙── ^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls index 2e94c8dc69..d5284b4384 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Arrays.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Arrays.mls @@ -86,7 +86,7 @@ values([Item(4)]).map((item, ...) => item.value) :ge [1].map(x => x) //│ ╔══[COMPILATION ERROR] Resolution error in function type -//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.260: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Callback parameter list does not match its declared function type. //│ ║ in Arrays.mls:87 @@ -141,15 +141,15 @@ new Array[Int](1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.232: declare class Array[T](val length: Int) { +//│ ║ l.240: declare class Array[T](val length: Int) { //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.233: fun join(...separators): Str +//│ ║ l.241: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.234: fun reverse(): Array[T] +//│ ║ l.242: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.235: fun sort(...compare): Array[T] +//│ ║ l.243: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.236: ... (more lines omitted) ... +//│ ║ l.244: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1, 2] @@ -160,15 +160,15 @@ new Array[Int]() //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Class 'Array' expected 1 argument, but got 0 //│ ║ in ../decls/Prelude.mls -//│ ║ l.232: declare class Array[T](val length: Int) { +//│ ║ l.240: declare class Array[T](val length: Int) { //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.233: fun join(...separators): Str +//│ ║ l.241: fun join(...separators): Str //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.234: fun reverse(): Array[T] +//│ ║ l.242: fun reverse(): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.235: fun sort(...compare): Array[T] +//│ ║ l.243: fun sort(...compare): Array[T] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.236: ... (more lines omitted) ... +//│ ║ l.244: ... (more lines omitted) ... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [] @@ -224,7 +224,7 @@ class Total(val total: Int) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'reduce' expected 2 arguments, but got 1 //│ ║ in ../decls/Prelude.mls -//│ ║ l.266: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U +//│ ║ l.274: fun reduce[U](f: (U, T, Int, Array[T]) -> U, initial: U): U //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls index 4a6a594c15..81185141b2 100644 --- a/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls +++ b/hkmc2/shared/src/test/mlscript/newres/GenericMethods.mls @@ -61,7 +61,7 @@ class Child extends Base with //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Function 'map' expected 1 type argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.252: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] +//│ ║ l.260: fun map[U](f: (T, Int, Array[T]) -> U): Array[U] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = [1] diff --git a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls index 06777b02a5..14ec29a12a 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls @@ -434,7 +434,7 @@ let changingParent = makeParent(4) class SavedParent extends changingParent { override fun f() = super.f() } set changingParent = makeParent(9) (new SavedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2946' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2949' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 4 @@ -449,7 +449,7 @@ let changingHolder = Holder(5) class SavedNestedParent extends changingHolder.Parent { override fun f() = super.f() } set changingHolder = Holder(11) (new SavedNestedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2946' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2949' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 5 diff --git a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls index 3b24d11be7..e7c7f0d58e 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PatternDefinitionBindings.mls @@ -99,7 +99,7 @@ pattern Bad = ((Str as s => s.length) as n) => n.toUpperCase() //│ ║ ^^^^^^^^^^^^^ //│ ╟── Value of type 'Int' does not contain member 'toUpperCase' //│ ║ in ../decls/Prelude.mls -//│ ║ l.197: val length: Int +//│ ║ l.205: val length: Int //│ ╙── ^^^ // Conjunction produces a pair, not the string tested by its left operand. diff --git a/hkmc2/shared/src/test/mlscript/newres/PromiseSignatures.mls b/hkmc2/shared/src/test/mlscript/newres/PromiseSignatures.mls new file mode 100644 index 0000000000..4ec03776f7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/PromiseSignatures.mls @@ -0,0 +1,149 @@ +:. + +// Explicit callback result arguments distinguish fulfillment from rejection. +:await +:expect 1 +Promise.resolve("start").then[Int, Str](value => 1, reason => "recovered") +//│ = 1 + +:await +:expect "recovered" +Promise.reject("failed").then[Int, Str](value => 1, reason => "recovered") +//│ = "recovered" + +// Rejection handlers may return a promised value, just like fulfillment handlers. +:await +:expect 2 +Promise.reject("failed").catch(reason => Promise.resolve({answer: 2})).then(value => value.answer) +//│ = 2 + +// Catch preserves a successful value and includes the rejection handler's result. +:await +:expect 3 +class Answer(val answer: Int) +Promise.resolve(Answer(3)).catch(reason => Promise.resolve(Answer(0))).then(value => value.answer) +//│ = 3 + +// Finally runs after settlement and preserves the original fulfillment interface. +:await +:expect 4 +Promise.resolve({answer: 4}).finally(() => console.log("finally")).then(value => value.answer) +//│ > finally +//│ = 4 + +:await +:expect "recovered: failure" +Promise.reject("failure").finally(() => console.log("rejected finally")).catch(reason => "recovered: " + reason) +//│ > rejected finally +//│ = "recovered: failure" + +// An explicitly typed constructor accepts a promise for its payload. +:await +:expect 5 +(new Promise[{answer: Int}]((resolve, reject) => resolve(Promise.resolve({answer: 5})))).then(value => value.answer) +//│ = 5 + +// Race and any adopt the winning promise before exposing its payload. +:await +:expect 6 +Promise.race([Promise.resolve({answer: 6})]).then(value => value.answer) +//│ = 6 + +:await +:expect 7 +Promise.any([Promise.reject("failed"), Promise.resolve({answer: 7})]).then(value => value.answer) +//│ = 7 + +// An absent rejection handler contributes no additional fulfillment candidate. +:await +:expect 8 +Promise.resolve({answer: 8}).then(value => value, undefined).then(value => value.answer) +//│ = 8 + +:await +:expect 9 +Promise.resolve({answer: 9}).then(value => value, null).then(value => value.answer) +//│ = 9 + +// A promise used only as a completion signal has an undefined payload. +:await +:expect true +(new Promise[undefined]((resolve, reject) => resolve(undefined))).then(value => value === undefined) +//│ = true + +// Explicitly absent catch/finally callbacks preserve the original settlement. +:await +:expect 14 +Promise.resolve({answer: 14}).catch(null).finally(undefined).then(value => value.answer) +//│ = 14 + +:await +:re +Promise.reject("still rejected").catch(undefined).finally(null) +//│ ═══[RUNTIME ERROR] still rejected +//│ = Promise {} + +// Resolve and reject callbacks return undefined, not an unconstrained value. +:!js +:e +new Promise((resolve, reject) => resolve(10).missing) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.89: new Promise((resolve, reject) => resolve(10).missing) +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ╟── Undefined literal does not contain member 'missing' +//│ ║ in ../decls/Prelude.mls +//│ ║ l.184: declare class Promise[out A](executor: ((A | Promise[A]) -> undefined, Any -> undefined) -> undefined) { +//│ ╙── ^^^^^^^^^ + +:!js +:e +new Promise((resolve, reject) => reject("failed").missing) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.100: new Promise((resolve, reject) => reject("failed").missing) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Undefined literal does not contain member 'missing' +//│ ║ in ../decls/Prelude.mls +//│ ║ l.184: declare class Promise[out A](executor: ((A | Promise[A]) -> undefined, Any -> undefined) -> undefined) { +//│ ╙── ^^^^^^^^^ + +// Both then branches contribute to the resulting promise type. +:!js +:e +Promise.resolve({answer: 11}).then(value => value, reason => 0).then(value => value.answer) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.112: Promise.resolve({answer: 11}).then(value => value, reason => 0).then(value => value.answer) +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Integer literal does not contain member 'answer' +//│ ║ l.112: Promise.resolve({answer: 11}).then(value => value, reason => 0).then(value => value.answer) +//│ ╙── ^ + +// A rejection handler's output does not replace the fulfilled input type. +:!js +:e +Promise.resolve(0).catch(reason => Promise.resolve({answer: 12})).then(value => value.answer) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.123: Promise.resolve(0).catch(reason => Promise.resolve({answer: 12})).then(value => value.answer) +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Integer literal does not contain member 'answer' +//│ ║ l.123: Promise.resolve(0).catch(reason => Promise.resolve({answer: 12})).then(value => value.answer) +//│ ╙── ^ + +// A finalizer's return value does not become the promise's fulfillment value. +:!js +:e +Promise.resolve(0).finally(() => console.log("finally")).then(value => value.answer) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.134: Promise.resolve(0).finally(() => console.log("finally")).then(value => value.answer) +//│ ║ ^^^^^^^^^^^^ +//│ ╟── Integer literal does not contain member 'answer' +//│ ║ l.134: Promise.resolve(0).finally(() => console.log("finally")).then(value => value.answer) +//│ ╙── ^ + +// The bottom type stays empty through aliases used by promise interfaces. +:js +:await +:expect 13 +type NeverFulfilled = Nothing +fun neverFulfilled(): Promise[NeverFulfilled] = Promise.reject("failed") +neverFulfilled().catch(reason => Promise.resolve({answer: 13})).then(value => value.answer) +//│ = 13 diff --git a/hkmc2/shared/src/test/mlscript/newres/Promises.mls b/hkmc2/shared/src/test/mlscript/newres/Promises.mls index b3c3b8182d..b5e1ad91bf 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Promises.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Promises.mls @@ -458,11 +458,15 @@ val escapedCallback = @annotations.async {value => value.answer} //│ ║ ^^^^^ //│ ╟── This value is fulfilled by this promise. //│ ║ in ../decls/Prelude.mls -//│ ║ l.180: declare class Promise[out A](executor: (A -> Any, Any -> Any) -> Any) { -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.181: fun id"then"[B](onFulfilled: Awaited[A] -> B, ...onRejected: Array[Any -> B]): Promise[Awaited[B]] -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.182: } +//│ ║ l.184: declare class Promise[out A](executor: ((A | Promise[A]) -> undefined, Any -> undefined) -> undefined) { +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.185: fun id"then"[B, C](onFulfilled: Awaited[A] -> (B | Promise[B]), ...onRejected: Array[(Any -> (C | Promise[C])) | undefined | null]): Promise[Awaited[B | C]] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.186: fun id"catch"[B](onRejected: (Any -> (B | Promise[B])) | undefined | null): Promise[Awaited[A | B]] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.187: fun id"finally"(onFinally: (() -> undefined) | undefined | null): Promise[A] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.188: ... (more lines omitted) ... //│ ║ ^ //│ ╟── 'escapedCallback' is exposed by this compilation unit. //│ ║ in Promises.mls:451 @@ -476,7 +480,7 @@ val escapedCallback = @annotations.async {value => value.answer} fun noFulfillment() = noFulfillment() noFulfillment().then(value => value.answer) //│ ╔══[COMPILATION ERROR] This selection of member 'answer' has no resolved target -//│ ║ l.477: noFulfillment().then(value => value.answer) +//│ ║ l.481: noFulfillment().then(value => value.answer) //│ ╙── ^^^^^^^^^^^^ // TODO: Infer structural thenables' fulfillment callbacks. Their original fields @@ -484,12 +488,12 @@ noFulfillment().then(value => value.answer) :!js :fixme class Thenable with - fun id"then"(resolve: {answer: Int} -> Any, reject: Any -> Any) = resolve({answer: 77}) + fun id"then"(resolve: {answer: Int} -> undefined, reject: Any -> undefined) = resolve({answer: 77}) Promise.resolve(new Thenable).then(value => value.answer) //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.488: Promise.resolve(new Thenable).then(value => value.answer) +//│ ║ l.492: Promise.resolve(new Thenable).then(value => value.answer) //│ ║ ^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'answer' of a value with unknown shape. //│ ╟── This value may be a thenable; its fulfillment interface is not known. -//│ ║ l.488: Promise.resolve(new Thenable).then(value => value.answer) +//│ ║ l.492: Promise.resolve(new Thenable).then(value => value.answer) //│ ╙── ^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index 5509a51977..9e00b1b27c 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -40,7 +40,7 @@ fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Object 'compile' cannot be called like a function. //│ ║ in ../../decls/Prelude.mls -//│ ║ l.556: object compile +//│ ║ l.564: object compile //│ ╙── ^^^^^^^ //│ ╔══[COMPILATION ERROR] Name not found: y //│ ║ l.22: fun foo(x) = if x is @annotations.compile (A(0) as y) then y diff --git a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls index f2d43aab08..075deb7daf 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/BuiltinOperators.mls @@ -63,9 +63,9 @@ wasm.i32.const(1, 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^ //│ ╟── Function 'const' expected 1 argument, but got 2 //│ ║ in ../decls/Prelude.mls -//│ ║ l.440: fun +//│ ║ l.448: fun //│ ║ ^^^ -//│ ║ l.441: const(c: Int): Int32 +//│ ║ l.449: const(c: Int): Int32 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Wasm intrinsic 'i32.const' called with incorrect arity (2) //│ ║ l.60: wasm.i32.const(1, 2) From 460541929d87d4f710c5f374616b376257378c08 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 3 Oct 2026 23:46:17 +0800 Subject: [PATCH 222/228] Temporarily disable new-resolution for `nofib/lastpiece` --- hkmc2/shared/src/test/mlscript-compile/nofib/lastpiece.mls | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/nofib/lastpiece.mls b/hkmc2/shared/src/test/mlscript-compile/nofib/lastpiece.mls index fb7e6a9cf3..b521f01858 100644 --- a/hkmc2/shared/src/test/mlscript-compile/nofib/lastpiece.mls +++ b/hkmc2/shared/src/test/mlscript-compile/nofib/lastpiece.mls @@ -1,4 +1,6 @@ -#lang(0.3.x) +// #lang(0.3.x) +// ^ Too slow for the CI at the moment, making it flaky; +// needs the perf. fixes at `LPTK/new-resolution-no-hashing` import "./NofibPrelude.mls" import "../../../../../../hkmc2/shared/src/test/mlscript-compile/Predef.mls" From 946d217d8478772abb5833743d49e321b0021831 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 3 Oct 2026 23:59:11 +0800 Subject: [PATCH 223/228] Salvage scratch cases into focused regression tests --- hkmc2/shared/src/test/mlscript/HkScratch.mls | 182 ------------- hkmc2/shared/src/test/mlscript/HkScratch2.mls | 58 ---- .../test/mlscript/newres/PredefInterfaces.mls | 8 + .../src/test/mlscript/newres/ReslScratch.mls | 250 ------------------ .../mlscript/newres/TuplePatternBindings.mls | 10 + .../src/test/mlscript/newres/WIPTest.mls | 184 ------------- .../src/test/mlscript/opt/PrivateFields.mls | 61 +++++ 7 files changed, 79 insertions(+), 674 deletions(-) delete mode 100644 hkmc2/shared/src/test/mlscript/HkScratch2.mls delete mode 100644 hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls delete mode 100644 hkmc2/shared/src/test/mlscript/newres/WIPTest.mls diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index 7c9a50c883..48d7326643 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -9,185 +9,3 @@ // :todo - -:rt -:lot -class Foo(a) -//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— -//│ Blk: -//│ stats = Ls of -//│ Parameterized: -//│ kind = Cls -//│ sym = class:Foo⁰ -//│ bsym = member:Foo¹ -//│ ctorSym = S of term:Foo² -//│ params = ParamList: -//│ params = Ls of -//│ Param: -//│ sym = a⁰ -//│ modulefulness = Modulefulness of N -//│ body = ObjBody of Blk: -//│ stats = Ls of -//│ LetDecl: -//│ sym = term:Foo⁰/a¹ -//│ DefineVar: -//│ sym = term:Foo⁰/a¹ -//│ rhs = Ref{sym=a⁰} of a⁰ -//│ res = Lit of UnitLit of false -//│ res = Lit of UnitLit of false -//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— -//│ Program: -//│ main = Define: -//│ defn = ClsLikeDefn: -//│ isym = class:Foo⁰ -//│ sym = member:Foo¹ -//│ ctorSym = S of term:Foo² -//│ k = Cls -//│ auxParams = Ls of -//│ ParamList: -//│ params = Ls of -//│ Param: -//│ sym = a⁰ -//│ modulefulness = Modulefulness of N -//│ privateFields = Ls of -//│ term:Foo⁰/a¹ -//│ ctor = AssignField: -//│ lhs = This of class:Foo⁰ -//│ nme = Ident of "a" -//│ rhs = SimpleRef of a⁰ -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - -:rt -class Foo(val a) -//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— -//│ Blk: -//│ stats = Ls of -//│ Parameterized: -//│ kind = Cls -//│ sym = class:Foo³ -//│ bsym = member:Foo⁴ -//│ ctorSym = S of term:Foo⁵ -//│ params = ParamList: -//│ params = Ls of -//│ Param: -//│ flags = (val) -//│ sym = a² -//│ modulefulness = Modulefulness of N -//│ body = ObjBody of Blk: -//│ stats = Ls of -//│ TermDefinition: -//│ k = ImmutVal -//│ sym = member:a³ -//│ tsym = term:Foo³/a⁴ -//│ body = S of Ref{sym=a²} of a² -//│ modulefulness = Modulefulness of N -//│ res = Lit of UnitLit of false -//│ res = Lit of UnitLit of false -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - -:rt -:sjs -new Foo(1) -//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— -//│ Blk: -//│ res = New{typ=class:Foo³}: -//│ cls = Resolved{sym=class:Foo³}: -//│ t = Ref{sym=member:Foo⁴} of member:Foo⁴ -//│ sym = class:Foo³ -//│ args = Ls of -//│ Tup of Ls of -//│ Fld: -//│ term = Lit of IntLit of 1 -//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return globalThis.Object.freeze(new Foo3.class(1)) -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = Foo(1) - - -:rt -class Foo { - val b = 1 -} -//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— -//│ Blk: -//│ stats = Ls of -//│ Plain: -//│ kind = Cls -//│ sym = class:Foo⁶ -//│ bsym = member:Foo⁷ -//│ body = ObjBody of Blk: -//│ stats = Ls of -//│ TermDefinition: -//│ k = ImmutVal -//│ sym = member:b⁰ -//│ tsym = term:Foo⁶/b¹ -//│ body = S of Lit of IntLit of 1 -//│ flags = (method) -//│ modulefulness = Modulefulness of N -//│ res = Lit of UnitLit of false -//│ res = Lit of UnitLit of false -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - -:rt -new Foo -//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— -//│ Blk: -//│ res = New{typ=class:Foo⁶}: -//│ cls = Resolved{sym=class:Foo⁶}: -//│ t = Ref{sym=member:Foo⁷} of member:Foo⁷ -//│ sym = class:Foo⁶ -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = Foo { b: 1 } - - - -class Foo(pred) { - val r = pred.id(42) - fun callId(x) = pred.id(x) -} - -class Foo { - // a - fun f = 1 -} - -:sir -:soir -:sjs -class Bar with - private val a = 1 - print(a) -//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define Bar⁰ as class Bar¹ { -//│ private val a⁵; -//│ constructor() { @private define a⁶ as val a⁵ = 1; do Predef⁰.print⁰(Bar¹.this.a⁵); end } -//│ }; -//│ end -//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ define Bar⁰ as class Bar¹ { -//│ private val a⁵; -//│ constructor() { @private define a⁶ as val a⁵ = 1; do Predef⁰.print⁰(Bar¹.this.a⁵); end } -//│ }; -//│ end -//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Bar1; -//│ (class Bar { -//│ static { -//│ Bar1 = this -//│ } -//│ constructor() { -//│ this.#a = 1; -//│ Predef.print(this.#a); -//│ } -//│ #a; -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Bar"]; -//│ }); -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - - -fun id"lollol" = 1 - -id"lollol" -//│ = 1 - diff --git a/hkmc2/shared/src/test/mlscript/HkScratch2.mls b/hkmc2/shared/src/test/mlscript/HkScratch2.mls deleted file mode 100644 index c5a479003d..0000000000 --- a/hkmc2/shared/src/test/mlscript/HkScratch2.mls +++ /dev/null @@ -1,58 +0,0 @@ -:js -// :de -// :sjs -// :pt -// :elt - -:global -// :d -// :todo - - - -:sr -class Foo(val a) -object Bar { val b = 123 } -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun Foo⁰ -//│ class Foo¹::Foo²(val a⁰) { -//│ val a¹::a² ‹flow:a⁰› = a⁰ -//│ }, -//│ object Bar⁰::Bar¹ { val b⁰::b¹ ‹flow:b⁰› = 123 } -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - - -let x = Foo(Bar) -//│ x = Foo(Bar { b: 123, class: object Bar }) - -// :dl -// :de -:sr -// :elt -:sir -:sjs -if x is - Foo(y) then y.b -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ if { x⁰ is Foo¹(_ as y⁰) then y⁰.bˀˀˀ } -//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let y, arg$Foo$0$; -//│ match x⁰ -//│ Foo⁰ => -//│ set arg$Foo$0$ = x⁰.a⁰; -//│ set y = arg$Foo$0$; -//│ return y.b﹖ -//│ else -//│ throw new globalThis⁰.Error⁰("match error") -//│ end -//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let arg$Foo$0$; -//│ if (x instanceof Foo1.class) { -//│ arg$Foo$0$ = x.a; -//│ return arg$Foo$0$.b -//│ } -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = 123 - - diff --git a/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls index 00889c9316..269da9ed05 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls @@ -23,3 +23,11 @@ class Box(val value: Int) fun stringify(n: Int): Str = ["a", "b", "c"].[n] [0, 1, 2].map(pass1(stringify)) //│ = ["a", "b", "c"] + +// A module member alias preserves access to imported polymorphic functions. +module Imported with + val p = Predef + +:expect 42 +Imported.p.id({answer: 42}).answer +//│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls b/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls deleted file mode 100644 index bf2f7e4062..0000000000 --- a/hkmc2/shared/src/test/mlscript/newres/ReslScratch.mls +++ /dev/null @@ -1,250 +0,0 @@ -:. - - -:sir -:soir -:sjs -class Bar(a) -//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define Bar⁰ as class Bar² { -//│ private val a⁰; -//│ constructor Bar¹(a) { @private define a¹ as val a⁰ = a; end } -//│ }; -//│ end -//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ define Bar⁰ as class Bar² { constructor Bar¹ }; end -//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Bar1; -//│ Bar1 = function Bar(a) { -//│ return globalThis.Object.freeze(new Bar.class(a)); -//│ }; -//│ (class Bar { -//│ static { -//│ Bar1.class = this -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Bar", [null]]; -//│ }); -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - - -:sr -:rt -:sir -:soir -:sjs -class Bar(a) with - let x = a - print(x) -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun Bar⁰ -//│ class Bar¹::Bar²(a⁰) { -//│ @private val a¹::a² ‹flow:a⁰› = a⁰^a², -//│ let x⁰, -//│ x⁰ = Bar².this.a¹, -//│ Predef⁰.print⁰^Bar²(x⁰)‹app⁰› -//│ } -//│ ——————————————| Resolved tree |————————————————————————————————————————————————————————————————————— -//│ Blk: -//│ stats = Ls of -//│ Parameterized: -//│ kind = Cls -//│ sym = class:Bar³ -//│ bsym = member:Bar⁴ -//│ ctorSym = S of term:Bar⁵ -//│ params = ParamList: -//│ params = Ls of -//│ Param: -//│ sym = a² -//│ modulefulness = Modulefulness of N -//│ body = ObjBody of Blk: -//│ stats = Ls of -//│ TermDefinition: -//│ k = ImmutVal -//│ sym = member:a³ -//│ tsym = term:Bar³/a⁴ -//│ body = S of Capture: -//│ base = SimpleRef of a² -//│ thru = term:Bar³/a⁴ -//│ modulefulness = Modulefulness of N -//│ annotations = Ls of -//│ Modifier of keyword 'private' -//│ LetDecl: -//│ sym = term:Bar³/x⁰ -//│ DefineVar: -//│ sym = term:Bar³/x⁰ -//│ rhs = NewSel: -//│ prefix = SelfRef of class:Bar³ -//│ id = Ident of "a" -//│ res = App: -//│ lhs = Capture: -//│ base = NewSel: -//│ prefix = MemberRef of member:Predef⁰ -//│ id = Ident of "print" -//│ thru = class:Bar³ -//│ rhs = Tup of Ls of -//│ Fld: -//│ term = MemberRef of term:Bar³/x⁰ -//│ res = Lit of UnitLit of false -//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define Bar⁴ as class Bar³ { -//│ private val a⁴; -//│ private val x⁰; -//│ constructor Bar⁵(a) { -//│ @private -//│ define a³ as val a⁴ = a; -//│ set Bar³.this.x⁰ = Bar³.this.a⁴; -//│ do Predef¹.print⁰(Bar³.this.x⁰); -//│ end -//│ } -//│ }; -//│ end -//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ define Bar⁴ as class Bar³ { -//│ private val a⁴; -//│ private val x⁰; -//│ constructor Bar⁵(a) { -//│ @private -//│ define a³ as val a⁴ = a; -//│ set Bar³.this.x⁰ = Bar³.this.a⁴; -//│ do Predef¹.print⁰(Bar³.this.x⁰); -//│ end -//│ } -//│ }; -//│ end -//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Bar3; -//│ Bar3 = function Bar(a) { -//│ return globalThis.Object.freeze(new Bar.class(a)); -//│ }; -//│ (class Bar2 { -//│ static { -//│ Bar3.class = this -//│ } -//│ constructor(a) { -//│ this.#a = a; -//│ this.#x = this.#a; -//│ Predef.print(this.#x); -//│ } -//│ #a; -//│ #x; -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Bar", [null]]; -//│ }); -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - -Bar(123) -//│ > 123 -//│ = Bar(_) - - -:sir -:soir -:sjs -class Bar with - private val a = 1 -//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ define Bar⁶ as class Bar⁷ { -//│ private val a⁵; -//│ constructor() { @private define a⁶ as val a⁵ = 1; end } -//│ }; -//│ end -//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ define Bar⁶ as class Bar⁷; end -//│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Bar5; -//│ (class Bar4 { -//│ static { -//│ Bar5 = this -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["class", "Bar"]; -//│ }); -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - - - -:sr -fun foo(x) = - if x is - [y] and y > 0 then 1 - 0 then 2 - else 3 -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun foo⁰::foo¹(x¹) ‹flow:foo⁰› = if { -//│ x¹ is [_ as y⁰] and { -//│ let scrut⁰ = >(y⁰, 0)‹app¹›; -//│ scrut⁰ is true then 1 -//│ }; -//│ x¹ is 0 then 2; -//│ else 3 -//│ } -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - - - -// :de -:sr -{a: 1} -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ { let a³, a³ = 1, "a": a³ } -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = {a: 1} - -:expect 1 -{a: 1}.a -//│ = 1 - - - -:e -fun foo[A](x: A) = x.a -foo({a: 1}) -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.201: fun foo[A](x: A) = x.a -//│ ║ ^^^ -//│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ║ l.201: fun foo[A](x: A) = x.a -//│ ║ ^ -//│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.201: fun foo[A](x: A) = x.a -//│ ╙── ^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. - -:e -private fun foo[A](x: A) = x.a -foo({a: 1}) -//│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.215: private fun foo[A](x: A) = x.a -//│ ║ ^^^ -//│ ╟── Cannot resolve member 'a' of a value with unknown shape. -//│ ║ l.215: private fun foo[A](x: A) = x.a -//│ ║ ^ -//│ ╟── Type parameter 'A' does not specify a member interface. -//│ ║ l.215: private fun foo[A](x: A) = x.a -//│ ╙── ^ -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. - - - -fun test[A, B](x: Iterator[A] | Iterator[B]) = - x.next() - -(x: Iterator[Array]) => test(x).value -//│ = fun - - -fun test[A, B](x: Indexable[A] | Indexable[B]) = - x.at(0) - -(x: Indexable[Array]) => test(x).length -//│ = fun - - -fun test[A, B](x: Indexable[A]) = - x.at(0) - -(x: Indexable[Array]) => test(x).length -//│ = fun - - diff --git a/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls b/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls index 2884f8bb9a..63fb3ef1bd 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TuplePatternBindings.mls @@ -49,3 +49,13 @@ private fun peel(values) = if values is [...rest, last] then 1 + peel(rest) peel([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]) //│ = 24 + +// A failed singleton-tuple guard must continue to the literal case or the fallback. +:expect [1, 3, 3, 2, 3, 3, 3] +fun classify(value) = + if value is + [item] and item > 0 then 1 + 0 then 2 + else 3 +[classify([1]), classify([0]), classify([-1]), classify(0), classify(1), classify([]), classify([1, 2])] +//│ = [1, 3, 3, 2, 3, 3, 3] diff --git a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls b/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls deleted file mode 100644 index 35dda488f6..0000000000 --- a/hkmc2/shared/src/test/mlscript/newres/WIPTest.mls +++ /dev/null @@ -1,184 +0,0 @@ -:. - - -:elt -:sr -fun foo = 1 -//│ —————————————| Elaborated tree |———————————————————————————————————————————————————————————————————— -//│ Blk: -//│ stats = Ls of -//│ TermDefinition: -//│ k = Fun -//│ sym = member:foo⁰ -//│ tsym = term:foo¹ -//│ body = S of Lit of IntLit of 1 -//│ modulefulness = Modulefulness of N -//│ res = Lit of UnitLit of false -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ fun foo⁰::foo¹ ‹flow:foo⁰› = 1 -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - - -let p = Predef -//│ p = class Predef { -//│ Symbols: Symbols { -//│ prettyPrint: Symbol("mlscript.prettyPrint"), -//│ definitionMetadata: Symbol("mlscript.definitionMetadata"), -//│ class: object Symbols -//│ }, -//│ Sub: class Sub, -//│ Eq: class Eq, -//│ Refl: Refl, -//│ js_assert: fun assert, -//│ foldl: fun fold, -//│ meta: class meta -//│ } - -p.id -//│ = fun id - - -:sr -// :de -val p = Predef -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ val p⁰::p¹ ‹flow:p⁰› = Predef⁰^p¹ -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ p = class Predef { -//│ Symbols: Symbols { -//│ prettyPrint: Symbol("mlscript.prettyPrint"), -//│ definitionMetadata: Symbol("mlscript.definitionMetadata"), -//│ class: object Symbols -//│ }, -//│ Sub: class Sub, -//│ Eq: class Eq, -//│ Refl: Refl, -//│ js_assert: fun assert, -//│ foldl: fun fold, -//│ meta: class meta -//│ } - -:sr -// :de -p.id -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ p⁰.id⁰ -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = fun id - - -:sr -:de -module M with - val p = Predef -//│ Elab block List(TypeDef(Mod,InfixApp(Ident(M),Keywrd(keyword 'with'),Block(List(TermDef(ImmutVal,Ident(p),Some([...] LocalScope(block:7) -//│ | Processing module/object definition Ident(M) -//│ | | Companion: None -//│ | | Elab block List(TermDef(ImmutVal,Ident(p),Some(Ident(Predef)))) InnerScope(module:M) -//│ | | | Processing term definition Ident(p) -//│ | | | Elab term Ident(Predef) -//│ | | | | Elab subterm Ident(Predef) -//│ | | | | | listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ -//│ | | | | | listen: trm = member:Predef⁰ -//│ | | | | | listenBMS: bms = module -//│ | | | | | listenedBMS: bms = module -//│ | | | | | fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() -//│ | | | | | PUSH module:Predef¹ -//│ | | | | ~> member:Predef⁰^term:M⁰/p⁰ -//│ | | | ~> member:Predef⁰^term:M⁰/p⁰ -//│ | | ~> { val member:p = member:Predef⁰^term:M⁰/p⁰; } -//│ ~> { Mod M ObjBody(Blk(List(TermDefinition(ImmutVal,member:p,term:M/p,List(),None,None,Some(Capture(MemberRef(member:Predef),term:M/p)),‹›,Modulefulness(None),List(),None)),Lit(UnitLit(false)))); } -//│ listenBMS: bms = module -//│ listenedBMS: bms = module -//│ fromBMS: bms = member:M¹, sh = module:M⁰, flow = exposed⁰, markss = List() -//│ PUSH module:M⁰ -//│ listenTerm: trm = undefined -//│ listen: trm = undefined -//│ introShape: intro = Lit(UnitLit(false)) -//│ listenBMS: bms = value -//│ listenedBMS: bms = value -//│ listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ -//│ listenTerm: trm = member:Predef⁰^term:M⁰/p⁰ -//│ listen: trm = member:Predef⁰ -//│ listenBMS: bms = module -//│ listenedBMS: bms = module -//│ fromBMS: bms = member:Predef⁰, sh = module:Predef¹, flow = Predef², markss = List() -//│ PUSH module:Predef¹ -//│ fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = exposed¹, markss = List() -//│ .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, exposed¹) -//│ = module:Predef¹ -//│ MATCH module:Predef¹ for term:M⁰/p⁰ % exposed¹ -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ module M⁰::M¹ { val p²::p³ ‹flow:p¹› = Predef⁰^p³ } -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - -:sr -:de -M.p.id -//│ Elab block List(Sel(Sel(Ident(M),Ident(p)),Ident(id))) LocalScope(block:8) -//│ | Elab term Sel(Sel(Ident(M),Ident(p)),Ident(id)) -//│ | | Elab subterm Sel(Sel(Ident(M),Ident(p)),Ident(id)) -//│ | | | Elab subterm Sel(Ident(M),Ident(p)) -//│ | | | | Elab subterm Ident(M) -//│ | | | | | listen: trm = member:M¹ -//│ | | | | | listenBMS: bms = module -//│ | | | | | listenedBMS: bms = module -//│ | | | | | fromBMS: bms = member:M¹, sh = module:M⁰, flow = M², markss = List() -//│ | | | | | PUSH module:M⁰ -//│ | | | | ~> member:M¹ -//│ | | | | newSel? sel = member:M¹.p -//│ | | | | listen: trm = member:M¹ -//│ | | | | listenBMS: bms = module -//│ | | | | listenedBMS: bms = module -//│ | | | | fromBMS: bms = member:M¹, sh = module:M⁰, flow = M², markss = List() -//│ | | | | PUSH module:M⁰ -//│ | | | | newSel: sel = member:M¹.p, shape = module:M⁰ -//│ | | | | newSel member: bms = member:p¹, mss = List() -//│ | | | | listen: trm = member:M¹.p -//│ | | | | listenBMS: bms = value -//│ | | | | listenedBMS: bms = value -//│ | | | | listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ -//│ | | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = ⋅p⁰, markss = List() -//│ | | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, ⋅p⁰) -//│ | | | | = module:Predef¹ -//│ | | | | MATCH module:Predef¹ for term:M⁰/p⁰ % ⋅p⁰ -//│ | | | ~> member:M¹.p -//│ | | | newSel? sel = member:M¹.p.id -//│ | | | listen: trm = member:M¹.p -//│ | | | listenBMS: bms = value -//│ | | | listenedBMS: bms = value -//│ | | | listenTerm: td.body = member:Predef⁰^term:M⁰/p⁰ -//│ | | | fromBMS: bms = member:p¹, sh = MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), flow = ⋅p⁰, markss = List() -//│ | | | .exit MarkedShape (MarkedShape(module:Predef¹, ↘⟨term:M⁰/p⁰⟩ϵ), term:M⁰/p⁰, ⋅p⁰) -//│ | | | = module:Predef¹ -//│ | | | MATCH module:Predef¹ for term:M⁰/p⁰ % ⋅p⁰ -//│ | | | newSel: sel = member:M¹.p.id, shape = module:Predef¹ -//│ | | | newSel member: bms = member:id⁰, mss = List() -//│ | | | listenTerm: trm = member:M¹.p.id -//│ | | | listen: trm = member:M¹.p.id -//│ | | | listenBMS: bms = function -//│ | | | listenedBMS: bms = function -//│ | | | fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List() -//│ | | | .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) -//│ | | | = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | | PUSH MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ | | ~> member:M¹.p.id -//│ | ~> member:M¹.p.id -//│ ~> { member:M¹.p.id } -//│ listenTerm: trm = member:M¹.p.id -//│ listen: trm = member:M¹.p.id -//│ listenBMS: bms = function -//│ listenedBMS: bms = function -//│ fromBMS: bms = member:id⁰, sh = member:id⁰, flow = ⋅id⁰, markss = List() -//│ .exit MarkedShape (member:id⁰, term:Predef¹/id¹, ⋅id⁰) -//│ = MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ PUSH MarkedShape(member:id⁰, ↗⟨term:Predef¹/id¹⟩%⟨⋅id⁰⟩ϵ) -//│ ————————————————| Resolved |———————————————————————————————————————————————————————————————————————— -//│ M⁰.p².id⁰ -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = fun id - - - - - diff --git a/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls b/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls index 728d776c6a..ba94e5f0d5 100644 --- a/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls +++ b/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls @@ -139,3 +139,64 @@ UnusedObjectPrivate.get + UnusedModulePrivate.get //│ = "()()" +// Unused constructor parameters and explicit private values need no instance fields. +:soir +class UnusedParameter(value) +class UnusedField with + private val value = 1 +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ define UnusedParameter⁰ as class UnusedParameter² { +//│ constructor UnusedParameter¹ +//│ }; +//│ define UnusedField⁰ as class UnusedField¹; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +// A read in the constructor alone keeps the initialized member storage live. +:soir +class ConstructorLocal(value) with + let stored = value + print(stored) +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ define ConstructorLocal⁰ as class ConstructorLocal² { +//│ private val value⁰; +//│ private val stored⁰; +//│ constructor ConstructorLocal¹(value) { +//│ @private +//│ define value¹ as val value⁰ = value; +//│ set ConstructorLocal².this.stored⁰ = ConstructorLocal².this.value⁰; +//│ do Predef⁰.print⁰(ConstructorLocal².this.stored⁰); +//│ end +//│ } +//│ }; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +ConstructorLocal(123) +//│ > 123 +//│ = ConstructorLocal(_) + + +:soir +class ConstructorRead with + private val value = 1 + print(value) +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ define ConstructorRead⁰ as class ConstructorRead¹ { +//│ private val value²; +//│ constructor() { +//│ @private +//│ define value³ as val value² = 1; +//│ do Predef⁰.print⁰(ConstructorRead¹.this.value²); +//│ end +//│ } +//│ }; +//│ end +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +new ConstructorRead +//│ > 1 +//│ = ConstructorRead + + From 2b504edb274bed1212004a3856eec79d7d3118e5 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sun, 4 Oct 2026 10:22:28 +0800 Subject: [PATCH 224/228] Address Copilot's review --- hkmc2/shared/src/test/mlscript-compile/XML.mls | 2 +- hkmc2/shared/src/test/mlscript/lifter/Loops.mls | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/XML.mls b/hkmc2/shared/src/test/mlscript-compile/XML.mls index 988afeb0b1..47cef5204a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/XML.mls +++ b/hkmc2/shared/src/test/mlscript-compile/XML.mls @@ -16,7 +16,7 @@ module XML with ... fun serializeValue(value) = if value is Str then JSON.stringify(value) - // * TOOD: Allow just `StyleAttributeValue then value.toValue()` + // * TODO: Allow just `StyleAttributeValue then value.toValue()` // * Currently, scrutinees are not refined implicitly for resolution. StyleAttributeValue as value then value.toValue() diff --git a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls index d1aab431fb..0a95fd658c 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/Loops.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/Loops.mls @@ -81,13 +81,9 @@ fun foo() = //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :expect 2 -:e -foo()() -//│ ╔══[COMPILATION ERROR] Resolution error in application -//│ ║ l.85: foo()() -//│ ║ ^^^^^^^ -//│ ╙── Unit value cannot be called like a function. -//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. -//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' +foo() as Function +|> + _() +//│ = 2 From 7c3e8ddc2585caf52455028231eff7c0ff368584 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sun, 4 Oct 2026 10:24:37 +0800 Subject: [PATCH 225/228] Misc --- hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls | 7 +++++++ hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls b/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls index 269da9ed05..bc024df9e0 100644 --- a/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls +++ b/hkmc2/shared/src/test/mlscript/newres/PredefInterfaces.mls @@ -24,6 +24,7 @@ fun stringify(n: Int): Str = ["a", "b", "c"].[n] [0, 1, 2].map(pass1(stringify)) //│ = ["a", "b", "c"] + // A module member alias preserves access to imported polymorphic functions. module Imported with val p = Predef @@ -31,3 +32,9 @@ module Imported with :expect 42 Imported.p.id({answer: 42}).answer //│ = 42 + +:expect true +Imported.p.id(Predef).maybe +//│ = true + + diff --git a/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls b/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls index ba94e5f0d5..ab247845d1 100644 --- a/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls +++ b/hkmc2/shared/src/test/mlscript/opt/PrivateFields.mls @@ -153,7 +153,8 @@ class UnusedField with //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -// A read in the constructor alone keeps the initialized member storage live. +// TODO: improve +// A read in the constructor alone currently keeps the initialized member storage live. :soir class ConstructorLocal(value) with let stored = value From 25864b4915c2bd50db533313af3ed52504be33b9 Mon Sep 17 00:00:00 2001 From: Claude Opus Date: Sat, 10 Oct 2026 22:27:13 +0800 Subject: [PATCH 226/228] Key effective class members by definition identity The memoized effective members of each class, and the cycle check guarding their computation, were keyed by class definitions. These are case classes, so every lookup hashed and compared the whole definition body. Key them by identity instead, like the parent constructions collected in the same pass. Co-Authored-By: Claude Opus 5.5 --- .../src/main/scala/hkmc2/codegen/Erasure.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala index 21803d3797..15686a31d7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Erasure.scala @@ -188,22 +188,24 @@ final class Erasure private (using Config, Ctx, State)(using Raise): // Public members replace inherited names; private members retain their declaration identity. // A subclass cannot implement a private declaration by introducing the same name. type MemberKey = Either[TermSymbol, Str] - val effectiveMembers = mutable.Map.empty[ClassLikeDef, Map[MemberKey, TermSymbol]] + // Definitions are keyed by identity: as case classes, they would otherwise + // be hashed and compared through their entire bodies on every lookup. + val effectiveMembers = mutable.Map.empty[Identity[ClassLikeDef], Map[MemberKey, TermSymbol]] def checkConstruction(term: Term & PossiblyErroneous, cls: ClassDef): Unit = if Inheritance.hasModifier(cls, syntax.Keyword.`abstract`) then term.isErroneous = true raise(ErrorReport( msg"Cannot instantiate abstract class '${cls.sym.nme}'" -> term.toLoc :: (msg"Abstract class defined here" -> cls.toLoc) :: Nil, source = Diagnostic.Source.Compilation)) - def members(cls: ClassLikeDef, visiting: Set[ClassLikeDef]): Map[MemberKey, TermSymbol] = - effectiveMembers.getOrElseUpdate(cls, - if visiting(cls) then + def members(cls: ClassLikeDef, visiting: Set[Identity[ClassLikeDef]]): Map[MemberKey, TermSymbol] = + effectiveMembers.getOrElseUpdate(Identity(cls), + if visiting(Identity(cls)) then raise(ErrorReport(msg"Inheritance cycle involving '${cls.sym.nme}'" -> cls.toLoc :: Nil, source = Diagnostic.Source.Compilation)) Map.empty else val inherited = cls.ext.flatMap(parentClass) - .fold(Map.empty[MemberKey, TermSymbol])(members(_, visiting + cls)) + .fold(Map.empty[MemberKey, TermSymbol])(members(_, visiting + Identity(cls))) inherited ++ cls.body.members.toList.flatMap: (name, member) => member.asTrm.map: symbol => val key: MemberKey = if symbol.isPrivate then Left(symbol) else Right(name) From c746b7d84b3e970daa8fb67dffde1057708dd735 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 10 Oct 2026 22:26:56 +0800 Subject: [PATCH 227/228] Fix resolver variance substitution through nested type arguments Retain input and output interpretations in the new resolver, composing declaration variance and preserving alias polarity. Add resolver unsoundness regressions and valid controls, and update documentation and golden outputs. --- doc/new-resolution-regular-types.md | 30 ++-- doc/new-resolution-type-value-flow.md | 63 +++++--- doc/reference.md | 10 +- .../scala/hkmc2/semantics/NewResolver.scala | 141 +++++++++++------- .../hkmc2/semantics/NewResolverState.scala | 4 +- .../scala/hkmc2/semantics/TypeShape.scala | 7 +- .../src/test/mlscript/newres/Overrides.mls | 4 +- .../newres/TypeInterfaceDiagnostics.mls | 8 +- .../test/mlscript/newres/VarianceAliases.mls | 105 +++++++++++++ .../mlscript/newres/VarianceSoundness.mls | 95 ++++++++++++ .../mlscript/newres/VarianceSubstitution.mls | 68 ++++++--- 11 files changed, 415 insertions(+), 120 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/newres/VarianceAliases.mls create mode 100644 hkmc2/shared/src/test/mlscript/newres/VarianceSoundness.mls diff --git a/doc/new-resolution-regular-types.md b/doc/new-resolution-regular-types.md index 0f953f7cb2..f051433709 100644 --- a/doc/new-resolution-regular-types.md +++ b/doc/new-resolution-regular-types.md @@ -104,10 +104,12 @@ normalization does not freeze its current bounds. Combined nodes are interned in the consuming resolution state, using the same inherited-cache discipline as other type references. -Applied aliases reduce before they become another type's argument. Arguments -are interpreted in their caller environment first, then declaration variance is -applied and the alias's formals are substituted. A guard on source alias symbols -stops unproductive recursive expansion. Nested arguments are reduced before adding +Applied aliases reduce before they become another type's argument. Both argument +polarities are interpreted in their caller environment, then declaration variance +retains the applicable parts and the alias's formals are substituted. The body +receives the enclosing polarity once; nominal applications compose that polarity +into their retained argument parts before exposing members. A guard on source alias +symbols stops unproductive recursive expansion. Nested arguments are reduced before adding the outer alias to that guard, so `Identity[Identity[A]]` reduces to `A`. Reduction stops at nominal, tuple, record, and function constructors. @@ -221,22 +223,20 @@ For the parts produced by the current argument interpretation rules: | Argument | Input part | Output part | | --- | --- | --- | -| Invariant `S` | `I_p(S)` | `I_p(S)` | +| Invariant `S` | `I_not-p(S)` | `I_p(S)` | | Written `in T` | `I_not-p(T)` | `Any` | | Written `out U` | `Nothing` | `I_p(U)` | -| Plain `S` with declaration `in` | `I_p(S)` | `Any` | +| Plain `S` with declaration `in` | `I_not-p(S)` | `Any` | | Plain `S` with declaration `out` | `Nothing` | `I_p(S)` | -Written wildcards override declaration variance, as in the existing interpreter. -The difference between the written `in T` row and the declaration-variance row is -intentional: a written input bound is an opposite-polarity syntax occurrence; -declaration variance selects which parts of an already interpreted argument are -available. These are the rules to preserve, not a proposal to change substitution. +Written wildcards override declaration variance. Written and declaration-site +input variance both reverse polarity. An invariant argument retains both +interpretations rather than freezing one endpoint before its two uses are known. -For an invariant actual argument `B`, selecting either part of the callee's formal -still selects the same saved `I_p(B)`. It must not reinterpret `B` with the callee's -current polarity. Likewise, after selecting a written argument part, both subsequent -constraint directions use that fixed type. Function inputs reverse lexical polarity; +For an invariant actual argument `B`, selecting a part of the callee's formal +selects the corresponding saved interpretation of `B` in its caller environment. +After selecting either argument part, both subsequent constraint directions use +that fixed type. Function inputs reverse lexical polarity; record fields, tuple fields, and function results preserve it. Scope transport does not change which argument part is selected and continues to use ordinary marks. diff --git a/doc/new-resolution-type-value-flow.md b/doc/new-resolution-type-value-flow.md index a9ba370702..f14182e215 100644 --- a/doc/new-resolution-type-value-flow.md +++ b/doc/new-resolution-type-value-flow.md @@ -33,8 +33,8 @@ For example, `(Int -> Int) & (Str -> Str) <: A -> B` constrains both arrows, producing `A <: Int`, `Int <: B`, `A <: Str`, and `Str <: B`. These rules collect possible resolution targets without solving alternative constraint sets or forming products of intersection candidates. -They deliberately approximate type inference; concrete incompatibilities such as -`Str <: Int` do not currently produce resolution diagnostics. +They deliberately approximate type inference; diagnosing concrete incompatibilities +such as `Str <: Int` belongs to type checking, not resolution. ## Instance wrappers and interface observations @@ -153,13 +153,18 @@ subclass scope before applying the value's caller path. `nominalParent` shares that exit between ancestor constraints and inherited member lookup. Constructed receivers already carry the exit in their constructor context. -### Substitute at the occurrence before applying argument variance +### Compose substitution polarity through type arguments -First interpret an argument expression at its lexical occurrence polarity. A -formal bound to `in L out U` selects `U` positively and `L` negatively. Function -domains and written wildcard input parts reverse polarity; results and structural -fields preserve it. Then apply the enclosing formal's declaration variance to the -interpreted argument. An invariant formal uses that one type for both parts. +A formal bound to `in L out U` selects `U` positively and `L` negatively. +Function domains and type-argument input parts reverse polarity; results, +structural fields, and type-argument output parts preserve it. Write `I_p(S)` for +interpreting `S` at polarity `p`. For an unqualified argument `S`, an invariant +formal therefore retains both +`I_not-p(S)` as its input and `I_p(S)` as its output. These interpretations can +differ when `S` contains substituted parameters. Declaration-site `in` retains +the input interpretation and supplies `Any` as the output; declaration-site `out` +retains the output interpretation and supplies `Nothing` as the input. Written +wildcards use the same polarity composition and override declaration variance. For example, with `Child <: Base`: @@ -169,20 +174,38 @@ class Receiver[T] with fun accept(box: Box[T]): () = () ``` -On `Receiver[in Child out Base]`, `accept` expects `Box[Child]`: the occurrence of -`T` is negative, and the invariant `Box` argument uses the selected `Child` for -both parts. A declaration's `in` annotation does not itself change the lexical -polarity at which a substituted argument is evaluated. +On `Receiver[in Child out Base]`, `accept` expects `Box[in Base out Child]`. +The domain reverses polarity, and `Box`'s input part reverses it again, selecting +`Base`; its output part selects `Child`. Selecting `Child` once and using it for +both parts would be unsound: a `Receiver[Base]` can be viewed through this receiver +type, but an invariant `Box[Child]` is not a `Box[Base]`. A receiver that writes its +stored `Base` into an `Array[T]` exposes the runtime failure of that rule. + +Resolution propagates these obligations into inferred arguments and preserves +explicitly supplied interfaces. Checking concrete subtype compatibility belongs +to the later type checking phases: an `Array[Child]` annotation remains the read +interface during resolution, while the incompatible call must be rejected by the +type checker. The annotated-array case in `VarianceSoundness.mls` resolves +normally and, with type checking disabled, records the unsafe runtime write. +InvalML regressions check rejection of the corresponding concrete mismatch. + +Aliases bind both argument interpretations before interpreting their bodies at the +enclosing polarity. This avoids applying the enclosing reversal twice: +`Identity[T]`, for `type Identity[A] = A`, must behave like `T` in either polarity. +Captured enclosing binders retain their own bindings. Nominal applications instead +retain the argument pair composed with the application's polarity, since member +signatures subsequently interpret that pair from the member's positive interface. `DeclaredType.positive` records this lexical polarity, independently of a later constraint's direction. `TypeShape.Wildcard` retains written parts; -`TypeShape.Argument` retains contextual references for synthesized variance. +`TypeShape.Argument` retains both contextual argument parts. Neither transplants supplied syntax into the callee's binding environment. -`TypeShape.SelectedArgument` saves a requested part when the argument is deferred. -Both subsequent constraint directions use that same selected type. Selections are -cached by argument reference and polarity; selecting an existing selection is -idempotent. Forwarding cycles retain subscriptions but stop repeated observation. +`TypeShape.SelectedArgument` saves one requested part for a scalar occurrence when +the argument is deferred. Both subsequent constraint directions use that same +selected type; an invariant argument makes separate selections for its two parts. +Selections are cached by argument reference and polarity; selecting an existing +selection is idempotent. Forwarding cycles retain subscriptions but stop repeated observation. For a fixed argument-reference set, selection adds at most two nodes per reference. ## Shared bodies and contextual constraints @@ -522,8 +545,8 @@ These algebraic checks cover combinations that worksheet examples cannot exhaust Worksheet coverage under `newres` includes `MutableArrays`, `ContextualInference`, `InstantiationSites`, `StoredSpecializations`, `SpecializationCaptures`, -`TypeArgumentVariance`, `VarianceSubstitution`, `AnnotationContexts`, and -`TypeGraphTermination`. `Promises` covers async fulfillment, nested adoption, -constructor inference, generic call isolation, and escaping payloads. Deferred cases +`TypeArgumentVariance`, `VarianceSubstitution`, `VarianceSoundness`, `VarianceAliases`, +`AnnotationContexts`, and `TypeGraphTermination`. `Promises` covers async fulfillment, +nested adoption, constructor inference, generic call isolation, and escaping payloads. Deferred cases retain explicit regression expectations; see the [future-work reference](new-resolution-future-work.md). diff --git a/doc/reference.md b/doc/reference.md index 94153c19e8..c401322e00 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -1385,10 +1385,12 @@ Use-site type arguments have input and output parts: For an unqualified argument, the declaration's `in` or `out` annotation selects the corresponding form. An explicitly written use-site variance supplies its own parts. Substitution selects the output part in positive positions and the input -part in negative positions. Function inputs reverse polarity; results preserve it. -Interpret an argument at its occurrence before applying the enclosing declaration's -variance. For example, a method input `Box[T]` on `Receiver[in Child out Base]` -uses `Box[Child]` when `Box` is invariant. +part in negative positions. Function inputs and type-argument input parts reverse +polarity; results and type-argument output parts preserve it. An invariant argument +must be interpreted in both polarities before substitution selects its bounds. +For example, a method input `Box[T]` on `Receiver[in Child out Base]` becomes +`Box[in Base out Child]` when `Box` is invariant. Declaration-site `in` reverses +polarity just like a written input part; declaration-site `out` preserves it. Each explicit generic parameter is instantiated once at the expression that specializes or invokes its definition: `f[T]`, a by-name reference/selection, or diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala index 3f5c1404fb..894ca9f90f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolver.scala @@ -449,7 +449,7 @@ class NewResolver: // the alias's substitution. The guard bounds body expansion; // substitution values are finite immutable reference DAGs. val substitutions = symbol.defn.get.tparams.zip(args).map: (formal, argument) => - formal.sym -> argumentType(declaredType(argument, tpe), formal) + formal.sym -> argumentType(declaredArgument(argument, tpe, true), formal) uses(declaredType(rhs, tpe.bindings ++ substitutions, tpe.positive), wrapped, next, aliases + symbol) case S(_) => dependencies(tpe.resolution).flatMap(bound(_, wrapped)) case N => dependencies(tpe.resolution).flatMap(bound(_, true)) @@ -515,7 +515,22 @@ class NewResolver: private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], positive: Bool) (using NewResolverState): DeclaredType = + declaredType(resolution, bindings, positive, asArgument = false) + + private def declaredType(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], positive: Bool, asArgument: Bool) + (using NewResolverState): DeclaredType = + // Splitting each argument visits both polarities. Share work within this + // traversal so nested alias applications do not repeatedly normalize the + // same source and environment along an exponential number of paths. + val normalized = mutable.Map.empty[(TypeResolution, Map[VarSymbol, DeclaredType], Set[TypeAliasSymbol], Bool), DeclaredType] + def argument(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], aliases: Set[TypeAliasSymbol], positive: Bool): DeclaredType = + resolution.currentShapes.toList match + case TypeShape.Wildcard(_, _) :: Nil | TypeShape.Argument(_) :: Nil => loop(resolution, bindings, aliases, positive) + case _ => normalizedArgument(resolution, TypeArgument( + loop(resolution, bindings, aliases, !positive), loop(resolution, bindings, aliases, positive))) def loop(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], aliases: Set[TypeAliasSymbol], positive: Bool): DeclaredType = + normalized.getOrElseUpdate((resolution, bindings, aliases, positive), interpret(resolution, bindings, aliases, positive)) + def interpret(resolution: TypeResolution, bindings: Map[VarSymbol, DeclaredType], aliases: Set[TypeAliasSymbol], positive: Bool): DeclaredType = def retain: DeclaredType = val relevant = if bindings.isEmpty then bindings else typeDependencies(resolution) match case R(binders) => bindings.filter((symbol, _) => binders(symbol)) @@ -536,13 +551,10 @@ class NewResolver: // step. Preserve the written source: it overrides declaration variance. val parts = TypeArgument(input.fold(extremeType(false, S(resolution)))(loop(_, bindings, aliases, !positive)), output.fold(missingOutput(resolution))(loop(_, bindings, aliases, positive))) - rstate.wildcardTypes.getOrElseUpdate((resolution, parts), { - val normalized = new TypeResolution(resolution.source, resolution.fail) - normalized.publish(TypeShape.Argument(parts)) - DeclaredType(normalized, Map.empty, TypeSubstitution.empty, true)(N) - }) + normalizedArgument(resolution, parts) case TypeShape.Applied(base, arguments) :: Nil => - def applyAlias(base: DeclaredType, supplied: TypeApplication, seen: Set[TypeResolution]): Opt[DeclaredType] = + def applyAlias(base: DeclaredType, arguments: => TypeApplication, seen: Set[TypeResolution]): Opt[DeclaredType] = + lazy val supplied = arguments if seen(base.resolution) then N else base.resolution.currentShapes.toList match case TypeShape.Alias(symbol, S(rhs)) :: Nil if !aliases(symbol) => val parameters = symbol.defn.get.tparams @@ -550,7 +562,7 @@ class NewResolver: // Omitted formals use the same source holes as deferred // interpretation. Retaining a partial forwarding application // would nest its environment at each recursive projection. - S(loop(rhs, typeBindings(base, supplied, parameters), aliases + symbol, positive)) + S(loop(rhs, typeBindings(base, supplied, parameters, true), aliases + symbol, positive)) case TypeShape.Contextual(reference) :: Nil => // Use exactly the interpreter's application rule: move caller // arguments to the template endpoint, substitute there, then @@ -563,9 +575,27 @@ class NewResolver: // Reduce arguments in their caller environment before binding formals. // The alias guard grows only on body expansion, so nested Identity // applications reduce while unproductive recursive aliases stop. - applyAlias(loop(base, bindings, aliases, positive), TypeApplication(arguments.map(loop(_, bindings, aliases, positive))), Set.empty).getOrElse(retain) + lazy val supplied = TypeApplication(arguments.map(res => AppliedTypeArgument( + argument(res, bindings, aliases, false), argument(res, bindings, aliases, true)))) + applyAlias(loop(base, bindings, aliases, positive), supplied, Set.empty).getOrElse(retain) case _ => retain - loop(resolution, bindings, Set.empty, positive) + if asArgument then argument(resolution, bindings, Set.empty, positive) + else loop(resolution, bindings, Set.empty, positive) + + /** Preserve both occurrences of an unqualified argument before substitution + * can select either bound. A nominal application composes these with its own + * polarity; an alias binds them before interpreting its body at that polarity. + */ + private def declaredArgument(resolution: TypeResolution, context: DeclaredType, positive: Bool) + (using NewResolverState): DeclaredType = + declaredType(resolution, context.bindings, positive, asArgument = true).instantiate(context.instances).withOrigin(context.origin) + + private def normalizedArgument(source: TypeResolution, parts: TypeArgument)(using NewResolverState): DeclaredType = + rstate.argumentTypes.getOrElseUpdate((source, parts), { + val normalized = new TypeResolution(source.source, source.fail) + normalized.publish(TypeShape.Argument(parts)) + DeclaredType(normalized, Map.empty, TypeSubstitution.empty, true)(N) + }) /** Normalize Boolean combinations without observing an atom's candidates. * Transport distributes over components using the ordinary transportType @@ -608,9 +638,9 @@ class NewResolver: private def declaredType(resolution: TypeResolution, context: DeclaredType)(using NewResolverState): DeclaredType = declaredType(resolution, context.bindings, context.positive).instantiate(context.instances).withOrigin(context.origin) - /** A substituted argument denotes one type at this occurrence. Freeze its - * selected part before it becomes an invariant nominal argument; later input - * and output uses must not select different parts of the original wildcard. + /** A scalar occurrence selects one endpoint of a substituted argument. Keep + * that endpoint fixed when subsequent constraints inspect the resulting type. + * Nominal arguments select both endpoints separately in declaredArgument. * Only delayed/captured pairs need a node, and selecting a selection is inert. */ private[semantics] def selectArgument(argument: DeclaredType, positive: Bool)(using NewResolverState): DeclaredType = @@ -661,8 +691,9 @@ class NewResolver: private def missingOutput(source: TypeResolution)(using NewResolverState): DeclaredType = extremeType(true, S(source)).withOrigin(S(TypeInterfaceReason.MissingOutput(source.source))) - /** Written wildcards override declaration variance. Unqualified arguments use - * the declared variance; an invariant argument supplies both identical parts. + /** Written wildcards override declaration variance. Unqualified arguments have + * already been interpreted in both polarities; declaration variance keeps the + * appropriate endpoint and supplies an extreme for the unused one. */ private def argumentType(tpe: DeclaredType, parameter: TyParam)(using NewResolverState): DeclaredType = val variance = parameter.vce @@ -676,23 +707,16 @@ class NewResolver: case _ => false if variance.isEmpty || (!selected && tpe.resolution.source.withoutCaptures.isInstanceOf[WildcardTy]) then tpe else val varianceValue = variance.get - // Apply declaration variance to the interpreted type, not another pair. - // Reusing a synthesized argument must select it first, so recursive - // substitutions cannot accumulate repeated variance wrappers. - tpe.resolution.currentShapes.toList match - case TypeShape.Argument(_) :: Nil => argumentType(selectArgument(tpe, tpe.positive), parameter) - case _ => - val variant = rstate.variantTypes.getOrElseUpdate((tpe, varianceValue), { - val resolution = new TypeResolution(tpe.resolution.source, tpe.resolution.fail) - val parts = if varianceValue then TypeArgument(extremeType(false), tpe) - else TypeArgument(tpe, extremeType(true)) - resolution.publish(TypeShape.Argument(parts)) - declaredType(resolution, Map.empty) - }) - // Different parameters can share the same semantic argument pair. - // Keep the declaration witness on this use, outside the interned pair. - if varianceValue then variant else variant.withOrigin( - S(TypeInterfaceReason.ContravariantParameter(parameter, tpe.resolution.source))) + val variant = rstate.variantTypes.getOrElseUpdate((tpe, varianceValue), { + val selected = selectArgument(tpe, varianceValue) + val parts = if varianceValue then TypeArgument(extremeType(false), selected) + else TypeArgument(selected, extremeType(true)) + normalizedArgument(tpe.resolution, parts) + }) + // Different parameters can share the same semantic argument pair. + // Keep the declaration witness on this use, outside the interned pair. + if varianceValue then variant else variant.withOrigin( + S(TypeInterfaceReason.ContravariantParameter(parameter, tpe.resolution.source))) private def listenArgumentParts(tpe: DeclaredType)(listener: ShapeListener[TypeArgument])(using NewResolverState): Unit = tpe.resolution.listen: @@ -741,26 +765,41 @@ class NewResolver: declaredType(resolution, Map.empty) }) - /** Supplied arguments belong to the application scope and move back to the + /** Save both interpretations before traversing the template reference. A + * nominal template uses the application's polarity, whereas an alias binds + * the positive pair and composes polarity while interpreting its body. This + * keeps Identity[A] transparent and preserves captured enclosing binders. + * Each interpretation is itself an input/output argument pair. + * + * Supplied arguments belong to the application scope and move back to the * template's endpoint before substitution. Omitted arguments instead belong * to the source type reference: all views must read and constrain their shared * host in that same scope. Rebasing an omitted host with each observation's * path would reinterpret its bounds in unrelated callers' lexical scopes. */ - private case class TypeApplication(arguments: Ls[DeclaredType]): + private case class AppliedTypeArgument(negative: DeclaredType, positive: DeclaredType): + def at(polarity: Bool): DeclaredType = if polarity then positive else negative + def move(marks: Ls[Marks])(using NewResolverState): AppliedTypeArgument = + AppliedTypeArgument(transportType(negative, marks), transportType(positive, marks)) + + private case class TypeApplication(arguments: Ls[AppliedTypeArgument]): def move(marks: Ls[Marks])(using NewResolverState): TypeApplication = - TypeApplication(arguments.map(transportType(_, marks))) + TypeApplication(arguments.map(_.move(marks))) private val noTypeArguments = TypeApplication(Nil) - private def typeArguments(source: DeclaredType, application: TypeApplication, parameters: Ls[TyParam]) + private def typeApplication(arguments: Ls[TypeResolution], context: DeclaredType)(using NewResolverState): TypeApplication = + TypeApplication(arguments.map(res => AppliedTypeArgument( + declaredArgument(res, context, false), declaredArgument(res, context, true)))) + + private def typeArguments(source: DeclaredType, application: TypeApplication, parameters: Ls[TyParam], positive: Bool) (using NewResolverState): Ls[DeclaredType] = parameters.zipWithIndex.map: (parameter, index) => - application.arguments.lift(index).getOrElse( + application.arguments.lift(index).map(_.at(positive)).getOrElse( omittedType(source.resolution, parameter.sym).instantiate(source.instances)) - private def typeBindings(source: DeclaredType, arguments: TypeApplication, parameters: Ls[TyParam]) + private def typeBindings(source: DeclaredType, arguments: TypeApplication, parameters: Ls[TyParam], positive: Bool) (using NewResolverState): Map[VarSymbol, DeclaredType] = - effectiveBindings(source) ++ parameters.zip(typeArguments(source, arguments, parameters)).map: (parameter, argument) => + effectiveBindings(source) ++ parameters.zip(typeArguments(source, arguments, parameters, positive)).map: (parameter, argument) => parameter.sym -> argumentType(argument, parameter) /** External inputs contribute unknowns only to missing annotation parts. @@ -788,11 +827,11 @@ class NewResolver: follow(reference.tpe.instantiate(current.instances).withOrigin(current.origin), args.move(inverseMarks(reference.marks)), reference.marks ::: captures, next) case TypeShape.Applied(base, arguments) => - follow(declaredType(base, current), TypeApplication(arguments.map(declaredType(_, current))), captures, next) + follow(declaredType(base, current), typeApplication(arguments, current), captures, next) case TypeShape.Alias(symbol, S(rhs)) => - follow(declaredType(rhs, typeBindings(current, args, symbol.defn.get.tparams), current.positive), noTypeArguments, captures, next) + follow(declaredType(rhs, typeBindings(current, args, symbol.defn.get.tparams, true), current.positive), noTypeArguments, captures, next) case TypeShape.Nominal(cls) => - cls.tparams.zip(typeArguments(current, args, cls.tparams)).foreach: (param, argument) => + cls.tparams.zip(typeArguments(current, args, cls.tparams, current.positive)).foreach: (param, argument) => listenArgumentParts(argumentType(argument, param)): parts => exposeTypeHoles(transportType(parts.output, captures), unknown, marks) case TypeShape.Tuple(fields) => fields.foreach(field => descend(declaredType(field, current))) @@ -965,8 +1004,8 @@ class NewResolver: def follow(current: DeclaredType, args: TypeApplication, aliases: Set[TypeResolution], source: Term, publish: Listener)(using NewResolverState): Unit = current.resolution.listen: shape => - def bind(params: Ls[TyParam])(using NewResolverState): Map[VarSymbol, DeclaredType] = - typeBindings(current, args, params) + def bind(params: Ls[TyParam], positive: Bool)(using NewResolverState): Map[VarSymbol, DeclaredType] = + typeBindings(current, args, params, positive) def next(res: TypeResolution)(using NewResolverState): Unit = follow(declaredType(res, current), noTypeArguments, aliases, res.source, publish) // Each type can expose several union alternatives. Form products @@ -997,7 +1036,7 @@ class NewResolver: listenInstanceViews(instantiateShape(value, current.instances), mergeIntersections)(publish) case TypeShape.Nominal(defn) => val parameters = nominalLexicalBinders(defn) ++ defn.tparams.map(_.sym) - val bindings = bind(defn.tparams).filter((parameter, _) => parameters(parameter)) + val bindings = bind(defn.tparams, current.positive).filter((parameter, _) => parameters(parameter)) defn.ext match case N => publish(NominalInstanceView(defn, bindings, implicitParent(defn))(S(source), ShapeProvenance.empty)(this)) case S(parent) => @@ -1012,7 +1051,7 @@ class NewResolver: listenTypeViews(declaredType(parentType, local), mergeIntersections): ext => publish(NominalInstanceView(defn, bindings, S(ext))(S(source), ShapeProvenance.empty)(this)) case TypeShape.Alias(symbol, _) if symbol is prelude.builtins.Awaited => - val arguments = typeArguments(current, args, symbol.defn.get.tparams) + val arguments = typeArguments(current, args, symbol.defn.get.tparams, true) softAssert(arguments.length == 1) listenTypeViews(arguments.head)(value => listenAwaitedValue(value, source)(publish)) case TypeShape.Alias(symbol, rhs) => @@ -1021,11 +1060,11 @@ class NewResolver: // references, not symbols: Id[Id[T]] has two distinct references. if aliases(current.resolution) then publish(OpaqueTypeShape(source)(TypeInterfaceReason.RecursiveAlias(symbol))) else rhs match - case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams), current.positive), noTypeArguments, + case S(rhs) => follow(declaredType(rhs, bind(symbol.defn.get.tparams, true), current.positive), noTypeArguments, aliases + current.resolution, source, publish) case N => publish(OpaqueTypeShape(source)(TypeInterfaceReason.AbstractDeclaration(symbol, source))) case TypeShape.Applied(base, params) => - follow(declaredType(base, current), TypeApplication(params.map(declaredType(_, current))), aliases, source, publish) + follow(declaredType(base, current), typeApplication(params, current), aliases, source, publish) case TypeShape.Parameter(symbol, host) => current.bindings.get(symbol) match case S(bound) => follow(selectArgument(bound.instantiate(current.instances), current.positive), noTypeArguments, aliases, source, publish) case N => @@ -1494,9 +1533,9 @@ class NewResolver: follow(reference.tpe.instantiate(tpe.instances).withOrigin(tpe.origin), args.move(inverseMarks(reference.marks)), reference.marks ::: captures, next) case TypeShape.Applied(base, params) => - follow(declaredType(base, tpe), TypeApplication(params.map(declaredType(_, tpe))), captures, next) + follow(declaredType(base, tpe), typeApplication(params, tpe), captures, next) case TypeShape.Alias(symbol, S(rhs)) => - follow(declaredType(rhs, typeBindings(tpe, args, symbol.defn.get.tparams), tpe.positive), noTypeArguments, captures, next) + follow(declaredType(rhs, typeBindings(tpe, args, symbol.defn.get.tparams, true), tpe.positive), noTypeArguments, captures, next) case TypeShape.Hole(host) => value.enter(captures) match case value: TermShape => host.publish(value) case NoShape => () @@ -1548,7 +1587,7 @@ class NewResolver: rstate.exposeValue(instantiateShape(actual, rstate.instances), provenance) case NoShape => () case _ => () - val arguments = typeArguments(tpe, args, cls.tparams).map(transportType(_, captures)) + val arguments = typeArguments(tpe, args, cls.tparams, tpe.positive).map(transportType(_, captures)) def constrainNominal(value: TermShape)(using NewResolverState): Unit = val ShapeParts(actual, instances, _) = shapeParts(value) val (head, context) = actual.applicationHead diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala index 4b18abebd0..164cfe2ffd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/NewResolverState.scala @@ -300,8 +300,8 @@ final class NewResolverState private ( new Cache(inherited.map(_.regularTypes), identity) val combinedTypes: Cache[TypeFormula[DeclaredType], DeclaredType] = new Cache(inherited.map(_.combinedTypes), identity) - val wildcardTypes: Cache[(TypeResolution, TypeArgument), DeclaredType] = - new Cache(inherited.map(_.wildcardTypes), identity) + val argumentTypes: Cache[(TypeResolution, TypeArgument), DeclaredType] = + new Cache(inherited.map(_.argumentTypes), identity) val pendingTypeDependencies: Cache[TypeResolution, TypeDependencyHost] = new Cache(inherited.map(_.pendingTypeDependencies), identity) val dependencySubscriptions: Seen[(TypeResolution, TypeResolution)] = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala index 45437a2323..d03e02711f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/TypeShape.scala @@ -25,7 +25,7 @@ enum TypeShape: case Polymorphic(params: Ls[TypeQuantifier], outer: Opt[VarSymbol], body: TypeResolution) case Applied(base: TypeResolution, args: Ls[TypeResolution]) case Wildcard(input: Opt[TypeResolution], output: Opt[TypeResolution]) - // Synthesized declaration variance retains the argument's lexical environment. + // Interpreted arguments retain both polarities in the argument's lexical environment. case Argument(parts: TypeArgument) // Substitution selects one part before the type is used in further constraints. // The argument stays in its own lexical environment, including delayed parts. @@ -149,8 +149,9 @@ final case class DeclaredType(resolution: TypeResolution, bindings: Map[VarSymbo */ final case class ContextualType(tpe: DeclaredType, marks: Ls[Marks]) -/** Invariant S has S in both positions. Missing wildcard parts are represented - * by Nothing on input and Any on output, never by inference holes. +/** An unqualified argument is interpreted separately in the two polarities; + * these parts coincide only when substitution gives the same type in both. + * Missing wildcard parts are Nothing on input and Any on output, never holes. */ final case class TypeArgument(input: DeclaredType, output: DeclaredType) diff --git a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls index 14ec29a12a..58093d269f 100644 --- a/hkmc2/shared/src/test/mlscript/newres/Overrides.mls +++ b/hkmc2/shared/src/test/mlscript/newres/Overrides.mls @@ -434,7 +434,7 @@ let changingParent = makeParent(4) class SavedParent extends changingParent { override fun f() = super.f() } set changingParent = makeParent(9) (new SavedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2949' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2988' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 4 @@ -449,7 +449,7 @@ let changingHolder = Holder(5) class SavedNestedParent extends changingHolder.Parent { override fun f() = super.f() } set changingHolder = Holder(11) (new SavedNestedParent).f() -//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2949' +//│ ╔══[INTERNAL ERROR] Compiler reached an unexpected state at 'NewResolver.scala:2988' //│ ╟── The compilation result may be incorrect. //│ ╙── This is a compiler bug; please report it to the maintainers. //│ = 5 diff --git a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls index 504e7deda7..a04ffc96bc 100644 --- a/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls +++ b/hkmc2/shared/src/test/mlscript/newres/TypeInterfaceDiagnostics.mls @@ -144,8 +144,8 @@ pair.1.item.value //│ ║ ^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This type argument supplies only an input bound for 'R'. -//│ ║ l.140: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) -//│ ║ ^^^^ +//│ ║ l.139: type Pair[A] = [Left[A], Right[A]] +//│ ║ ^ //│ ╟── Type parameter 'R' is declared contravariant here. //│ ║ l.138: class Right[in R](val item: R) //│ ║ ^ @@ -167,8 +167,8 @@ delay(pair)().1.item.value //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Cannot resolve member 'value' of a value with unknown shape. //│ ╟── This type argument supplies only an input bound for 'R'. -//│ ║ l.163: let pair = ([new Left(Item(1)), new Right(Item(2))] as Pair[Item]) -//│ ║ ^^^^ +//│ ║ l.161: type Pair[A] = [Left[A], Right[A]] +//│ ║ ^ //│ ╟── Type parameter 'R' is declared contravariant here. //│ ║ l.160: class Right[in R](val item: R) //│ ║ ^ diff --git a/hkmc2/shared/src/test/mlscript/newres/VarianceAliases.mls b/hkmc2/shared/src/test/mlscript/newres/VarianceAliases.mls new file mode 100644 index 0000000000..b0b536ce33 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/VarianceAliases.mls @@ -0,0 +1,105 @@ +:. + +// Identity aliases preserve a negative occurrence, including nested aliases. +// The callback must handle Base, just as it would for a direct T -> Int domain. +:ge +class Base +class Child(val value: Int) extends Base +type Identity[A] = A +type Callback[A] = Identity[Identity[A]] -> Int +class Receiver[T](val value: T) with + fun use(f: Callback[T]): Int = f(value) +let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +receiver.use(x => x.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.13: receiver.use(x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.12: let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ receiver = Receiver(Base) + +// A negative nominal argument has the same bounds through a forward alias. +:ge +class Base +class Child(val value: Int) extends Base +class Receiver[T](val value: T) with + fun accept(xs: Items[T]): () = + xs.push(value) + () +type Items[A] = Array[Identity[A]] +type Identity[A] = A +let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +let xs = mut [Child(1)] +receiver.accept(xs) +xs.1.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.36: xs.1.value +//│ ║ ^^^^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.33: let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ receiver = Receiver(Base) +//│ xs = [Child(1), Base] + +// Captured and local alias binders must see the same composed polarity. +:ge +class Base +class Child(val value: Int) extends Base +class Receiver[T](val value: T) with + type Callbacks[A] = {direct: T -> Int, indirect: A -> Int} + fun use(callbacks: Callbacks[T]): Int = + callbacks.direct(value) + callbacks.indirect(value) +let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +receiver.use({direct: x => x.value, indirect: x => x.value}) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.56: receiver.use({direct: x => x.value, indirect: x => x.value}) +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.55: let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +//│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.56: receiver.use({direct: x => x.value, indirect: x => x.value}) +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.55: let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ receiver = Receiver(Base) + +// Valid callbacks retain the same behavior after alias expansion. +:expect 7 +class Base +class Child(val value: Int) extends Base +type Identity[A] = A +type Callback[A] = Identity[Identity[A]] -> Int +class Receiver[T](val value: T) with + fun use(f: Callback[T]): Int = f(value) +let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +receiver.use(x => 7) +//│ = 7 +//│ receiver = Receiver(Base) + +// A covariant declaration keeps the output projection of the nested argument. +:expect 8 +class Base +class Child(val value: Int) extends Base +class Box[out T](val item: T) +class Holder[T](val box: Box[T]) +let holder = (new Holder[Child](new Box[Child](Child(8))) as Holder[out Child]) +holder.box.item.value +//│ = 8 +//│ holder = Holder(Box(Child(8))) + +// A written output wildcard overrides declaration-site contravariance. +:expect 9 +class Base +class Child(val value: Int) extends Base +class Box[in T](val item: T) +class Holder[T](val box: Box[out T]) +let holder = (new Holder[Child](new Box[out Child](Child(9))) as Holder[out Child]) +holder.box.item.value +//│ = 9 +//│ holder = Holder(Box(Child(9))) diff --git a/hkmc2/shared/src/test/mlscript/newres/VarianceSoundness.mls b/hkmc2/shared/src/test/mlscript/newres/VarianceSoundness.mls new file mode 100644 index 0000000000..1dc8e13c27 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/newres/VarianceSoundness.mls @@ -0,0 +1,95 @@ +:. + +// The receiver can insert a Base. Its nested invariant argument must propagate +// that possibility back to the array instead of preserving an all-Child view. +:ge +class Base +class Child(val value: Int) extends Base +class Receiver[T](val value: T) with + fun accept(xs: Array[T]): () = + xs.push(value) + () +let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +let xs = mut [Child(1)] +receiver.accept(xs) +xs.1.value +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.15: xs.1.value +//│ ║ ^^^^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.12: let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ receiver = Receiver(Base) +//│ xs = [Child(1), Base] + +// A positive invariant argument must retain the holder's input bound too. +// The actual cell calls its argument on a Base, so a Child-only callback is unsafe. +:ge +class Base +class Child(val value: Int) extends Base +class Cell[T](val consume: T -> Int) with + fun put(value: T): Int = consume(value) +class Holder[T](val cell: Cell[T]) +let holder = (new Holder[Base -> Int](new Cell[Base -> Int](f => f(new Base))) + as Holder[in (Base -> Int) out (Child -> Int)]) +holder.cell.put(x => x.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.36: holder.cell.put(x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.35: as Holder[in (Base -> Int) out (Child -> Int)]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ holder = Holder(Cell(fun)) + +// A declaration-site input annotation composes polarity just like written in. +:ge +class Base +class Child(val value: Int) extends Base +class Sink[in T](val consume: T -> Int) with + fun put(value: T): Int = consume(value) +class Holder[T](val sink: Sink[T]) +let holder = (new Holder[Base -> Int](new Sink[Base -> Int](f => f(new Base))) + as Holder[in (Base -> Int) out (Child -> Int)]) +holder.sink.put(x => x.value) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.55: holder.sink.put(x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.54: as Holder[in (Base -> Int) out (Child -> Int)]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. +//│ holder = Holder(Sink(fun)) + +// A callback that handles every Base remains valid through both nested views. +:expect [3, 4] +class Base +class Child(val value: Int) extends Base +class Cell[T](val consume: T -> Int) with + fun put(value: T): Int = consume(value) +class Sink[in T](val consume: T -> Int) with + fun put(value: T): Int = consume(value) +class Holder[T](val cell: Cell[T], val sink: Sink[T]) +let holder = (new Holder[Base -> Int]( + new Cell[Base -> Int](f => f(new Base)), new Sink[Base -> Int](f => f(new Base))) + as Holder[in (Base -> Int) out (Child -> Int)]) +[holder.cell.put(x => 3), holder.sink.put(x => 4)] +//│ = [3, 4] +//│ holder = Holder(Cell(fun), Sink(fun)) + +// Resolution honors the supplied Child interface. Rejecting the incompatible +// array argument is the type checker's job, covered in InvalMLVarianceSubstitution. +// With type checking disabled, execution still inserts a Base into this array. +class Base +class Child(val value: Int) extends Base +class Receiver[T](val value: T) with + fun accept(xs: Array[T]): () = + xs.push(value) + () +let receiver = (new Receiver[Base](new Base) as Receiver[in Child out Base]) +let xs = (mut [Child(5)] as Array[Child]) +receiver.accept(xs) +xs.1.value +//│ receiver = Receiver(Base) +//│ xs = [Child(5), Base] diff --git a/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls index fc727a4cdd..a61be23b48 100644 --- a/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls +++ b/hkmc2/shared/src/test/mlscript/newres/VarianceSubstitution.mls @@ -1,8 +1,8 @@ :. -// A type parameter inside a callback input is in the member's output polarity. -// Substitute the output type before using it as an invariant nominal argument. -:expect 1 +// The callback receives Sink[in (Base -> Int) out (Child -> Int)]. Its put +// input uses the lower bound, so the supplied function must handle every Base. +:ge class Base class Child(val value: Int) extends Base class Sink[T] with @@ -14,12 +14,18 @@ producer.use(sink => sink.put(x => x.value) 1 ) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.14: sink.put(x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.12: let producer = (new Producer as Producer[in (Base -> Int) out (Child -> Int)]) +//│ ╙── ^^^^ //│ = 1 //│ producer = Producer -// An invariant argument in a member input uses the outer parameter's input type -// in both directions, so it must not contribute the unrelated output supertype. -:expect 2 +// The projected input is Box[in Base out Child]. The input part contributes +// Base to the inferred box argument, so its item cannot retain a Child-only view. +:ge class Base class Child(val value: Int) extends Base class Box[T](val item: T) @@ -29,11 +35,17 @@ let receiver = (new Receiver as Receiver[in Child out Base]) let box = Box(Child(2)) receiver.accept(box) box.item.value -//│ = 2 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.37: box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.34: let receiver = (new Receiver as Receiver[in Child out Base]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Box(Child(2)) //│ receiver = Receiver -// Control: writing the positive substitution directly gives the callback Child. +// Control: an explicitly fixed argument gives the callback Child. :expect 3 class Base class Child(val value: Int) extends Base @@ -49,7 +61,7 @@ producer.use(sink => //│ = 3 //│ producer = Producer -// Control: writing the negative substitution directly preserves Box[Child]. +// Control: an explicitly fixed argument preserves Box[Child]. :expect 4 class Base class Child(val value: Int) extends Base @@ -64,8 +76,8 @@ box.item.value //│ box = Box(Child(4)) //│ receiver = Receiver -// Tuple fields retain the enclosing input polarity for nested nominal arguments. -:expect 5 +// Tuple fields preserve the domain polarity, including both nominal argument parts. +:ge class Base class Child(val value: Int) extends Base class Box[T](val item: T) @@ -75,12 +87,18 @@ let receiver = (new Receiver as Receiver[in Child out Base]) let box = Box(Child(5)) receiver.accept([box]) box.item.value -//│ = 5 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.89: box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.86: let receiver = (new Receiver as Receiver[in Child out Base]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Box(Child(5)) //│ receiver = Receiver -// Structural fields retain the same substitution polarity as their record. -:expect 6 +// Structural fields preserve the domain polarity and the nested input bound. +:ge class Base class Child(val value: Int) extends Base class Box[T](val item: T) @@ -90,12 +108,18 @@ let receiver = (new Receiver as Receiver[in Child out Base]) let box = Box(Child(6)) receiver.accept({item: box}) box.item.value -//│ = 6 +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.110: box.item.value +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.107: let receiver = (new Receiver as Receiver[in Child out Base]) +//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Box(Child(6)) //│ receiver = Receiver -// Declaration-site contravariance applies after the occurrence selects Child -> Int. -:expect 7 +// Declaration-site contravariance reverses polarity and selects Base -> Int. +:ge class Base class Child(val value: Int) extends Base class Sink[in T] with @@ -107,6 +131,12 @@ producer.use(sink => sink.put(x => x.value) 7 ) +//│ ╔══[COMPILATION ERROR] Resolution error in selection +//│ ║ l.131: sink.put(x => x.value) +//│ ║ ^^^^^^^ +//│ ╟── Value of type 'Base' does not contain member 'value' +//│ ║ l.129: let producer = (new Producer as Producer[in (Base -> Int) out (Child -> Int)]) +//│ ╙── ^^^^ //│ = 7 //│ producer = Producer @@ -123,10 +153,10 @@ let box = Box(Child(8)) receiver.accept(box) box.item.value //│ ╔══[COMPILATION ERROR] Resolution error in selection -//│ ║ l.124: box.item.value +//│ ║ l.154: box.item.value //│ ║ ^^^^^^^^^^^^^^ //│ ╟── Value of type 'Base' does not contain member 'value' -//│ ║ l.121: let receiver = (new Receiver as Receiver[in Child out Base]) +//│ ║ l.151: let receiver = (new Receiver as Receiver[in Child out Base]) //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ box = Box(Child(8)) From a068c87555c624944d55e4aaff576083abcf7099 Mon Sep 17 00:00:00 2001 From: Claude Opus Date: Sat, 10 Oct 2026 22:57:30 +0800 Subject: [PATCH 228/228] Lower class selections and constructions on computed class values A `.class` selection or construction whose class operand is a computed value other than a local variable, such as a call result in `f(x).class`, reached a soft assertion and compiled to an internal error. Lower it like a local variable instead: the operand is evaluated and the class interpretation the resolver recorded on the enclosing selection or construction is used, with an error reported unless exactly one class was recorded. This change and its test were first made in the merge of new-resolution into new-resolution-no-hashing (64037b9b5), without a separate commit. Co-Authored-By: Claude Opus 5.5 --- .../src/main/scala/hkmc2/codegen/Lowering.scala | 6 +++--- hkmc2/shared/src/test/mlscript/newres/ClassValues.mls | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 384a35b0e9..6131961693 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -474,9 +474,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter)(using Erasu ErrorReport(msg"The class instantiated here is ambiguous" -> trm.toLoc :: targets.map(target => msg"target: ${target.describeKind} '${target.nme}'" -> target.toLoc), source = Diagnostic.Source.Compilation) - case _ => - softAssert(nw.isErroneous, "Unexpected class term shape") - compError + // Calls and other computed values retain their class interpretation on + // the enclosing selection or construction, just like local variables. + case _ => classValue else subTerm(trm)(p => k(p, getClassParamLists(p))) private def classParamLists(cls: ClassLikeDef): Ls[ParamList] = cls.paramsOpt.toList ::: cls.auxParams diff --git a/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls index de48717729..e351e9ec97 100644 --- a/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls +++ b/hkmc2/shared/src/test/mlscript/newres/ClassValues.mls @@ -442,3 +442,14 @@ new mixedRepresentation //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ mixedRepresentation = class Primary + +// A class selection on a call result reads the class interpretation recorded on +// the selection, as for a local variable; recursion must reach a fixed point. +:expect 36 +private fun classThroughRecursion(value, n: Int) = + if n == 0 then value.class + else classThroughRecursion(value, n - 1).class +let recursiveClass = classThroughRecursion(Primary, 3) +new recursiveClass(36).x +//│ = 36 +//│ recursiveClass = class Primary